use crate::block::registry::BlockRegistry; use crate::command::commands::default_dispatcher; use crate::command::commands::defaultgamemode::DefaultGamemode; use crate::data::VanillaData; use crate::data::player_server::ServerPlayerData; use crate::entity::{EntityBase, NBTStorage}; use crate::item::registry::ItemRegistry; use crate::net::authentication::fetch_mojang_public_keys; use crate::net::{ClientPlatform, DisconnectReason, EncryptionError, GameProfile, PlayerConfig}; use crate::plugin::PluginManager; use crate::plugin::player::player_login::PlayerLoginEvent; use crate::plugin::server::server_broadcast::ServerBroadcastEvent; use crate::server::tick_rate_manager::ServerTickRateManager; use crate::world::WorldPortal; use crate::world::custom_bossbar::CustomBossbars; use crate::{ command::node::dispatcher::CommandDispatcher, entity::player::Player, world::World, world::map::MapManager, }; use arc_swap::ArcSwap; use connection_cache::{CachedBranding, CachedStatus}; use key_store::KeyStore; use pumpkin_config::{AdvancedConfiguration, BasicConfiguration}; use pumpkin_data::dimension::Dimension; use pumpkin_data::entity::EntityType; use pumpkin_util::permission::{PermissionManager, PermissionRegistry}; use pumpkin_util::text::color::NamedColor; use pumpkin_world::dimension::into_level; use pumpkin_world::world::WorldPortalExt; use tracing::{debug, error, info, warn}; use crate::command::CommandSender; use pumpkin_macros::send_cancellable; use pumpkin_protocol::java::client::login::CEncryptionRequest; use pumpkin_protocol::java::client::play::{CChangeDifficulty, CTabList}; use pumpkin_protocol::{ClientPacket, java::client::config::CPluginMessage}; use pumpkin_util::Difficulty; use pumpkin_util::text::TextComponent; use pumpkin_world::world_info::anvil::{ AnvilLevelInfo, LEVEL_DAT_BACKUP_FILE_NAME, LEVEL_DAT_FILE_NAME, }; use pumpkin_world::world_info::{LevelData, WorldInfoError, WorldInfoReader, WorldInfoWriter}; use rand::seq::{IndexedRandom, SliceRandom}; use rsa::RsaPublicKey; use std::collections::HashSet; use std::fs; use std::net::IpAddr; use std::sync::Arc; use std::sync::atomic::{AtomicBool, AtomicI32, AtomicI64, AtomicU32}; use std::{future::Future, sync::atomic::Ordering, time::Duration}; use tokio::sync::{Mutex, OnceCell, RwLock}; use tokio::task::{JoinHandle, JoinSet}; use tokio_util::task::TaskTracker; mod connection_cache; mod key_store; pub mod recipe; pub mod scheduler; pub mod seasonal_events; pub mod tick_rate_manager; pub mod ticker; pub use recipe::RecipeManager; use crate::command::args::entities::{ EntityFilter, EntityFilterSort, EntitySelectorType, TargetSelector, ValueCondition, }; use crate::server::scheduler::TaskScheduler; /// Represents a Minecraft server instance. pub struct Server { pub basic_config: BasicConfiguration, pub advanced_config: AdvancedConfiguration, pub data: VanillaData, /// Plugin manager pub plugin_manager: Arc, /// Permission manager for the server. pub permission_manager: Arc>, /// Permission registry for the server. pub permission_registry: Arc>, /// Handles cryptographic keys for secure communication. key_store: OnceCell>, /// Bedrock OIDC provider keys, fetched on startup for 1.26.10+ token validation. pub bedrock_oidc_keys: OnceCell<(String, pumpkin_util::jwt::Jwks)>, /// Manages server status information. listing: Mutex, /// Saves server branding information. branding: CachedBranding, /// Saves and dispatches commands to appropriate handlers. pub command_dispatcher: RwLock, /// Block behaviour. pub block_registry: Arc, /// Item behaviour. pub item_registry: Arc, /// Manages multiple worlds within the server. pub worlds: ArcSwap>>, /// All the dimensions that exist on the server. pub dimensions: Vec, /// Assigns unique IDs to containers. container_id: AtomicU32, pub recipe_manager: Arc, /// Assigns unique IDs to maps. map_id: AtomicI32, /// Mojang's public keys, used for chat session signing /// Pulled from Mojang API on startup pub mojang_public_keys: ArcSwap>, /// The server's custom bossbars pub bossbars: Mutex, /// Manages all maps on the server pub map_manager: MapManager, /// The default gamemode when a player joins the server (reset every restart) pub defaultgamemode: Mutex, /// Manages player data storage pub player_data_storage: ServerPlayerData, // Whether the server whitelist is on or off pub white_list: AtomicBool, /// Manages the server's tick rate, freezing, and sprinting pub tick_rate_manager: Arc, /// Stores the duration of the last 100 ticks for performance analysis pub tick_times_nanos: Mutex<[i64; 100]>, /// Aggregated tick times for efficient rolling average calculation pub aggregated_tick_times_nanos: AtomicI64, /// Total number of ticks processed by the server pub tick_count: AtomicI32, /// Random unique Server ID used by Bedrock Edition pub server_guid: u64, /// Player idle timeout in minutes (0 = disabled) pub player_idle_timeout: AtomicI32, /// Manages scheduled tasks (e.g. from plugins) pub task_scheduler: Arc, tasks: TaskTracker, // world stuff which maybe should be put into a struct pub level_info: Arc>, world_info_writer: Arc, } impl Server { #[expect(clippy::too_many_lines)] #[must_use] pub async fn new( basic_config: BasicConfiguration, advanced_config: AdvancedConfiguration, vanilla_data: VanillaData, ) -> Arc { let permission_registry = Arc::new(RwLock::new(PermissionRegistry::new())); // First register the default commands. After that, plugins can put in their own. let command_dispatcher = RwLock::new(default_dispatcher(&permission_registry, &basic_config).await); let world_path = basic_config.get_world_path(); let block_registry = super::block::registry::default_registry(); let level_info = AnvilLevelInfo.read_world_info(&world_path); if let Err(error) = &level_info { match error { // If it doesn't exist, just make a new one WorldInfoError::InfoNotFound => (), WorldInfoError::UnsupportedDataVersion(_version) | WorldInfoError::UnsupportedLevelVersion(_version) => { error!("Failed to load world info!"); error!("{error}"); panic!("Unsupported world version! See the logs for more info."); } e => { panic!("World Error {e}"); } } } else { let dat_path = world_path.join(LEVEL_DAT_FILE_NAME); if dat_path.exists() { let backup_path = world_path.join(LEVEL_DAT_BACKUP_FILE_NAME); fs::copy(dat_path, backup_path).unwrap(); } } let level_info = level_info.unwrap_or_else(|err| { warn!("Failed to get level_info, using default instead: {err}"); let default_data = LevelData::default(basic_config.seed); if let Err(err) = AnvilLevelInfo.write_world_info(&default_data, &world_path) { error!("Failed to save level.dat: {err}"); } default_data }); let seed = level_info.world_gen_settings.seed; let level_info = Arc::new(ArcSwap::new(Arc::new(level_info))); let listing = Mutex::new(CachedStatus::new(&basic_config)); let defaultgamemode = Mutex::new(DefaultGamemode { gamemode: basic_config.default_gamemode, }); let player_data_storage = ServerPlayerData::new( world_path.join("playerdata"), Duration::from_secs(advanced_config.player_data.save_player_cron_interval), advanced_config.player_data.save_player_data, ); let white_list = AtomicBool::new(basic_config.white_list); let tick_rate_manager = Arc::new(ServerTickRateManager::new(basic_config.tps)); let mojang_keys_task = tokio::spawn({ let auth_config = advanced_config.networking.authentication.clone(); let allow_chat = basic_config.allow_chat_reports; async move { if allow_chat { fetch_mojang_public_keys(&auth_config).unwrap_or_else(|e| { error!("Failed to fetch Mojang keys: {e}"); Vec::new() }) } else { Vec::new() } } }); let server = Self { basic_config, advanced_config, data: vanilla_data, plugin_manager: Arc::new(PluginManager::new()), permission_manager: Arc::new(RwLock::new(PermissionManager::new( permission_registry.clone(), ))), permission_registry, container_id: 0.into(), recipe_manager: Arc::new(recipe::RecipeManager::new()), map_id: level_info.load().map_id.into(), worlds: ArcSwap::from_pointee(vec![]), dimensions: vec![ Dimension::OVERWORLD, Dimension::THE_NETHER, Dimension::THE_END, ], command_dispatcher, block_registry: block_registry.clone(), item_registry: super::item::items::default_registry(), key_store: OnceCell::new(), bedrock_oidc_keys: OnceCell::new(), listing, branding: CachedBranding::new(), bossbars: Mutex::new(CustomBossbars::new()), map_manager: MapManager::new(), defaultgamemode, player_data_storage, white_list, tick_rate_manager, tick_times_nanos: Mutex::new([0; 100]), aggregated_tick_times_nanos: AtomicI64::new(0), tick_count: AtomicI32::new(0), tasks: TaskTracker::new(), task_scheduler: Arc::new(TaskScheduler::new()), server_guid: rand::random(), player_idle_timeout: AtomicI32::new(0), mojang_public_keys: ArcSwap::from_pointee(Vec::new()), world_info_writer: Arc::new(AnvilLevelInfo), level_info, }; let server = Arc::new(server); let gen_pool = Arc::new( rayon::ThreadPoolBuilder::new() .thread_name(|i| format!("Gen-Pool-{i}")) .build() .expect("Failed to build generation thread pool"), ); let server_clone = server.clone(); tokio::spawn(async move { server_clone .key_store .get_or_init(|| async { Arc::new(KeyStore::new()) }) .await; }); let world_loader = |dim: Dimension| { let path = world_path.clone(); let registry = block_registry.clone(); let l_info = server.level_info.clone(); // Access from struct let weak = Arc::downgrade(&server); let config = Arc::new(server.advanced_config.world.clone()); let pool = gen_pool.clone(); tokio::task::spawn_blocking(move || { info!( "Loading {}", TextComponent::text(dim.minecraft_name.to_string()) .color_named(NamedColor::DarkGreen) .to_pretty_console() ); let level = into_level(dim.clone(), &config, path, seed, Some(pool)); let world = Arc::new(World::load(level.clone(), l_info, dim, registry, weak)); let portal: Arc = Arc::new(WorldPortal(world.clone())); level.world_portal.store(Arc::new(Some(portal))); world }) }; info!("Starting parallel world load..."); let (overworld, nether, end, keys) = tokio::join!( world_loader(Dimension::OVERWORLD), world_loader(Dimension::THE_NETHER), world_loader(Dimension::THE_END), mojang_keys_task ); let worlds_vec = vec![ overworld.expect("Overworld panicked"), nether.expect("Nether panicked"), end.expect("End panicked"), ]; server.worlds.store(Arc::new(worlds_vec)); if let Ok(k) = keys { server.mojang_public_keys.store(Arc::new(k)); } info!("All worlds loaded successfully."); if server.basic_config.online_mode { let server_clone = server.clone(); tokio::spawn(async move { server_clone .bedrock_oidc_keys .get_or_init(|| async { tokio::task::block_in_place(|| { pumpkin_util::jwt::fetch_oidc_jwks().unwrap_or_else(|e| { error!("Failed to fetch Bedrock OIDC keys: {e}"); (String::new(), pumpkin_util::jwt::Jwks { keys: Vec::new() }) }) }) }) .await; }); } server } /// Spawns a task associated with this server. All tasks spawned with this method are awaited /// when the server stops. This means tasks should complete in a reasonable (no looping) amount of time. pub fn spawn_task(&self, task: F) -> JoinHandle where F: Future + Send + 'static, F::Output: Send + 'static, { self.tasks.spawn(task) } pub fn get_world_from_dimension(&self, dimension: &Dimension) -> Arc { // TODO: this is really bad let world_guard = self.worlds.load(); if dimension == &Dimension::OVERWORLD { world_guard.first() } else if dimension == &Dimension::THE_NETHER { world_guard.get(1) } else { world_guard.get(2) } .cloned() .unwrap() } pub async fn create_world(self: &Arc, name: String, dimension: Dimension) -> Arc { { let worlds = self.worlds.load(); if let Some(world) = worlds .iter() .find(|w| w.get_world_name() == name && w.dimension == dimension) { return world.clone(); } } let server = self.clone(); let name_clone = name.clone(); tokio::task::spawn_blocking(move || { let world_path = server.basic_config.get_world_path().join(name_clone); let registry = server.block_registry.clone(); let l_info = server.level_info.clone(); let weak = Arc::downgrade(&server); let config = Arc::new(server.advanced_config.world.clone()); let seed = server.level_info.load().world_gen_settings.seed; // TODO: gen_pool should be reused let level = pumpkin_world::dimension::into_level( dimension.clone(), &config, world_path, seed, None, ); let world: World = World::load(level.clone(), l_info, dimension, registry, weak); let world = Arc::new(world); let portal: Arc = Arc::new(WorldPortal(world.clone())); level.world_portal.store(Arc::new(Some(portal))); server.worlds.rcu(|worlds| { let mut new_worlds = (**worlds).clone(); new_worlds.push(world.clone()); new_worlds }); world }) .await .expect("World creation panicked") } /// Adds a new player to the server. /// /// This function takes an `Arc` representing the connected client and performs the following actions: /// /// 1. Generates a new entity ID for the player. /// 2. Determines the player's gamemode (defaulting to Survival if not specified in configuration). /// 3. **(TODO: Select default from config)** Selects the world for the player (currently uses the first world). /// 4. Creates a new `Player` instance using the provided information. /// 5. Adds the player to the chosen world. /// 6. **(TODO: Config if we want increase online)** Optionally updates server listing information based on the player's configuration. /// /// # Arguments /// /// * `client`: An `Arc` representing the connected client. /// /// # Returns /// /// A tuple containing: /// /// - `Arc`: A reference to the newly created player object. /// - `Arc`: A reference to the world the player was added to. /// /// # Note /// /// You still have to spawn the `Player` in a `World` to let them join and make them visible. pub async fn add_player( &self, client: ClientPlatform, profile: GameProfile, config: Option, ) -> Option<(Arc, Arc)> { let gamemode = self.defaultgamemode.lock().await.gamemode; let (world, nbt) = if let Ok(Some(data)) = self.player_data_storage.load_data(&profile.id).await { if let Some(dimension_key) = data.get_string("Dimension") { if let Some(dimension) = Dimension::from_name(dimension_key) { let world = self.get_world_from_dimension(dimension); (world, Some(data)) } else { warn!("Invalid dimension key in player data: {dimension_key}"); let default_world = self .worlds .load() .first() .expect("Default world should exist") .clone(); (default_world, Some(data)) } } else { // Player data exists but doesn't have a "Dimension" key. let default_world = self .worlds .load() .first() .expect("Default world should exist") .clone(); (default_world, Some(data)) } } else { // No player data found or an error occurred, default to the Overworld. let default_world = self .worlds .load() .first() .expect("Default world should exist") .clone(); (default_world, None) }; let mut player = Player::new( client, profile, config.clone().unwrap_or_default(), world.clone(), gamemode, ) .await; if let Some(mut nbt_data) = nbt { player.read_nbt(&mut nbt_data).await; } // Wrap in Arc after data is loaded let player = Arc::new(player); send_cancellable! {{ self; PlayerLoginEvent::new(player.clone(), TextComponent::text("You have been kicked from the server")); 'after: { player.screen_handler_sync_handler.store_player(player.clone()).await; if world .add_player(&player) .is_ok() { let mut user_cache = self.data.user_cache.write().await; user_cache.upsert(player.gameprofile.id, player.gameprofile.name.clone()); // TODO: Config if we want increase online if let Some(config) = config { // TODO: Config so we can also just ignore this hehe if config.server_listing { self.listing.lock().await.add_player(&player); } } Some((player, world.clone())) } else { None } } 'cancelled: { player.kick(DisconnectReason::Kicked, event.kick_message).await; None } }} } pub async fn remove_player(&self, player: &Player) { // TODO: Config if we want decrease online self.listing.lock().await.remove_player(player); } pub async fn shutdown(&self) { self.tasks.close(); debug!("Awaiting tasks for server"); self.tasks.wait().await; debug!("Done awaiting tasks for server"); info!("Starting worlds"); for world in self.worlds.load().iter() { world.shutdown().await; } let level_data = self.level_info.load(); // then lets save the world info if let Err(err) = self .world_info_writer .write_world_info(&level_data, &self.basic_config.get_world_path()) { error!("Failed to save level.dat: {err}"); } info!("Completed worlds"); } /// Broadcasts a packet to all players in all worlds. /// /// This function sends the specified packet to every connected player in every world managed by the server. /// /// # Arguments /// /// * `packet`: A reference to the packet to be broadcast. The packet must implement the `ClientPacket` trait. pub fn broadcast_packet_all(&self, packet: &P) { for world in self.worlds.load().iter() { world.broadcast_packet_all(packet); } } pub async fn broadcast_tab_list_header_footer( &self, header: &TextComponent, footer: &TextComponent, ) { let packet = CTabList::new(header, footer); for world in self.worlds.load().iter() { for player in world.players.load().iter() { *player.tab_list_header.lock().await = header.clone(); *player.tab_list_footer.lock().await = footer.clone(); player.client.enqueue_packet(&packet).await; } } } pub async fn broadcast_message( &self, message: &TextComponent, sender_name: &TextComponent, chat_type: u8, target_name: Option<&TextComponent>, ) { send_cancellable! {{ self; ServerBroadcastEvent::new(message.clone(), sender_name.clone()); 'after: { for world in self.worlds.load().iter() { world .broadcast_message(&event.message, &event.sender, chat_type, target_name) .await; } } }} } /// Gets the current difficulty of the server. pub fn get_difficulty(&self) -> Difficulty { self.level_info.load().difficulty } /// Sets the difficulty of the server. /// /// This function updates the difficulty level of the server and broadcasts the change to all players. /// It also iterates through all worlds to ensure the difficulty is applied consistently. /// If `force_update` is `Some(true)`, the difficulty will be set regardless of the current state. /// If `force_update` is `Some(false)` or `None`, the difficulty will only be updated if it is not locked. /// /// # Arguments /// /// * `difficulty`: The new difficulty level to set. This should be one of the variants of the `Difficulty` enum. /// * `force_update`: An optional boolean that, if set to `Some(true)`, forces the difficulty to be updated even if it is currently locked. /// /// # Note /// /// This function does not handle the actual mob spawn options update, which is a TODO item for future implementation. pub fn set_difficulty(&self, difficulty: Difficulty, force_update: bool) { let current_info = self.level_info.load(); if current_info.difficulty_locked && !force_update { return; } let new_difficulty = if self.basic_config.hardcore { Difficulty::Hard } else { difficulty }; let mut new_info = (**current_info).clone(); new_info.difficulty = new_difficulty; let locked = new_info.difficulty_locked; self.level_info.store(Arc::new(new_info)); for world in self.worlds.load().iter() { world.set_difficulty(difficulty); } self.broadcast_packet_all(&CChangeDifficulty::new(difficulty as u8, locked)); } /// Searches for a player by their username across all worlds. /// /// This function iterates through each world managed by the server and attempts to find a player with the specified username. /// If a player is found in any world, it returns an `Arc` reference to that player. Otherwise, it returns `None`. /// /// # Arguments /// /// * `name`: The username of the player to search for. /// /// # Returns /// /// An `Option>` containing the player if found, or `None` if not found. pub fn get_player_by_name(&self, name: &str) -> Option> { for world in self.worlds.load().iter() { if let Some(player) = world.get_player_by_name(name) { return Some(player); } } None } pub async fn get_players_by_ip(&self, ip: IpAddr) -> Vec> { let mut players = Vec::>::new(); for world in self.worlds.load().iter() { for player in world.players.load().iter() { if player.client.address().await.ip() == ip { players.push(player.clone()); } } } players } /// Returns all players from all worlds. pub fn get_all_players(&self) -> Vec> { let mut players = Vec::>::new(); for world in self.worlds.load().iter() { players.extend(world.players.load().iter().cloned()); } players } pub fn for_each_player(&self, mut f: F) where F: FnMut(&Arc), { let worlds = self.worlds.load(); for world in worlds.iter() { let players = world.players.load(); for player in players.iter() { f(player); } } } /// Returns a random player from any of the worlds, or `None` if all worlds are empty. pub fn get_random_player(&self) -> Option> { let players = self.get_all_players(); players.choose(&mut rand::rng()).map(Arc::<_>::clone) } /// Searches for a player by their UUID across all worlds. /// /// This function iterates through each world managed by the server and attempts to find a player with the specified UUID. /// If a player is found in any world, it returns an `Arc` reference to that player. Otherwise, it returns `None`. /// /// # Arguments /// /// * `id`: The UUID of the player to search for. /// /// # Returns /// /// An `Option>` containing the player if found, or `None` if not found. pub fn get_player_by_uuid(&self, id: uuid::Uuid) -> Option> { for world in self.worlds.load().iter() { if let Some(player) = world.get_player_by_uuid(id) { return Some(player); } } None } /// Counts the total number of players across all worlds. /// /// This function iterates through each world and sums up the number of players currently connected to that world. /// /// # Returns /// /// The total number of players connected to the server. pub fn get_player_count(&self) -> usize { let mut count = 0; for world in self.worlds.load().iter() { count += world.players.load().len(); } count } /// Similar to [`Server::get_player_count`] >= n, but may be more efficient since it stops its iteration through all worlds as soon as n players were found. pub fn has_n_players(&self, n: usize) -> bool { let mut count = 0; for world in self.worlds.load().iter() { count += world.players.load().len(); if count >= n { return true; } } false } /// Generates a new container id. pub fn new_container_id(&self) -> u32 { self.container_id.fetch_add(1, Ordering::SeqCst) } /// Generates a new map id. pub fn next_map_id(&self) -> i32 { let id = self.map_id.fetch_add(1, Ordering::SeqCst); self.level_info.rcu(|level_info| { let mut new_level_info = (**level_info).clone(); new_level_info.map_id = self.map_id.load(Ordering::SeqCst); new_level_info }); id } pub fn get_branding(&self) -> CPluginMessage<'_> { self.branding.get_branding() } pub const fn get_status(&self) -> &Mutex { &self.listing } pub async fn encryption_request<'a>( &'a self, verification_token: &'a [u8; 4], should_authenticate: bool, ) -> CEncryptionRequest<'a> { self.key_store .get_or_init(|| async { Arc::new(KeyStore::new()) }) .await .encryption_request("", verification_token, should_authenticate) } pub async fn decrypt(&self, data: &[u8]) -> Result, EncryptionError> { self.key_store .get_or_init(|| async { Arc::new(KeyStore::new()) }) .await .decrypt(data) } pub async fn digest_secret(&self, secret: &[u8]) -> String { self.key_store .get_or_init(|| async { Arc::new(KeyStore::new()) }) .await .get_digest(secret) } /// Main server tick method. This now handles both player/network ticking (which always runs) /// and world/game logic ticking (which is affected by freeze state). pub async fn tick(self: &Arc) { if self.tick_rate_manager.runs_normally() || self.tick_rate_manager.is_sprinting() { self.tick_worlds().await; // Always run player and network ticking, even when game is frozen } else { self.tick_players_and_network().await; } } /// Ticks essential server functions that must run even when the game is frozen. /// This includes player ticking (network, keep-alives) and flushing world updates to clients. pub async fn tick_players_and_network(self: &Arc) { let worlds = self.worlds.load(); for world in worlds.iter() { world.flush_block_updates().await; world.flush_synced_block_events().await; } let mut set = JoinSet::new(); for world in worlds.iter() { let players = world.players.load(); for player in players.iter() { let player_clone = player.clone(); let server_clone = self.clone(); set.spawn(async move { player_clone.tick(&server_clone).await; }); } } set.join_all().await; } /// Ticks the game logic for all worlds. This is the part that is affected by `/tick freeze`. pub async fn tick_worlds(self: &Arc) { self.task_scheduler.tick(self).await; let mut set = JoinSet::new(); for world in self.worlds.load().iter() { let world = world.clone(); let server = self.clone(); set.spawn(async move { world.tick(server).await; }); } set.join_all().await; // Global tasks if let Err(e) = self.player_data_storage.tick(self).await { error!("Error ticking player data: {e}"); } } /// Updates the tick time statistics with the duration of the last tick. pub async fn update_tick_times(&self, tick_duration_nanos: i64) { let tick_count = self.tick_count.fetch_add(1, Ordering::Relaxed); let index = (tick_count % 100) as usize; let mut tick_times = self.tick_times_nanos.lock().await; let old_time = tick_times[index]; tick_times[index] = tick_duration_nanos; drop(tick_times); self.aggregated_tick_times_nanos .fetch_add(tick_duration_nanos - old_time, Ordering::Relaxed); } /// Gets the rolling average tick time over the last 100 ticks, in nanoseconds. pub fn get_average_tick_time_nanos(&self) -> i64 { let tick_count = self.tick_count.load(Ordering::Relaxed); let sample_size = (tick_count as usize).min(100); if sample_size == 0 { return 0; } self.aggregated_tick_times_nanos.load(Ordering::Relaxed) / sample_size as i64 } /// Returns the average Milliseconds Per Tick (MSPT). pub fn get_mspt(&self) -> f64 { let avg_nanos = self.get_average_tick_time_nanos(); // Convert nanoseconds to decimal milliseconds avg_nanos as f64 / 1_000_000.0 } /// Returns the Ticks Per Second (TPS). pub fn get_tps(&self) -> f64 { let mspt = self.get_mspt(); if mspt <= 0.0 { return 0.0; } 1000.0 / mspt } /// Returns a copy of the last 100 tick times. pub async fn get_tick_times_nanos_copy(&self) -> [i64; 100] { *self.tick_times_nanos.lock().await } #[allow(clippy::too_many_lines, clippy::option_if_let_else)] pub fn select_players( &self, target_selector: &TargetSelector, source: Option<&CommandSender>, ) -> Vec> { let mut players = match &target_selector.selector_type { EntitySelectorType::Source => source .and_then(CommandSender::as_player) .map_or_else(Vec::new, |player| vec![player]), EntitySelectorType::NearestPlayer | EntitySelectorType::NearestEntity | EntitySelectorType::RandomPlayer | EntitySelectorType::AllPlayers | EntitySelectorType::AllEntities => self.get_all_players(), EntitySelectorType::NamedPlayer(name) => self .get_player_by_name(name) .map_or_else(Vec::new, |player| vec![player]), EntitySelectorType::Uuid(uuid) => self .get_player_by_uuid(*uuid) .map_or_else(Vec::new, |player| vec![player]), }; let player_type = EntityType::from_name("player").expect("entity type player must exist"); let type_included = target_selector .conditions .iter() .filter_map(|f| { if let EntityFilter::Type(ValueCondition::Equals(entity_type)) = f { Some(*entity_type) } else { None } }) .collect::>(); let type_excluded = target_selector .conditions .iter() .filter_map(|f| { if let EntityFilter::Type(ValueCondition::NotEquals(entity_type)) = f { Some(*entity_type) } else { None } }) .collect::>(); players.retain(|_| { (type_excluded.is_empty() || !type_excluded.contains(player_type)) && (type_included.is_empty() || type_included.contains(player_type)) }); let limit = target_selector.get_limit(); if limit == 0 { return Vec::new(); } match target_selector .get_sort() .unwrap_or(EntityFilterSort::Arbitrary) { EntityFilterSort::Arbitrary => players.into_iter().take(limit).collect(), EntityFilterSort::Random => { players.shuffle(&mut rand::rng()); players.into_iter().take(limit).collect() } EntityFilterSort::Nearest | EntityFilterSort::Furthest => { let center = source.and_then(CommandSender::position).unwrap_or_default(); let nearest_first = target_selector .get_sort() .is_none_or(|sort| sort == EntityFilterSort::Nearest); players.sort_by(|a, b| { let a_distance = a.get_entity().pos.load().squared_distance_to_vec(¢er); let b_distance = b.get_entity().pos.load().squared_distance_to_vec(¢er); if nearest_first { a_distance .partial_cmp(&b_distance) .unwrap_or(core::cmp::Ordering::Equal) } else { b_distance .partial_cmp(&a_distance) .unwrap_or(core::cmp::Ordering::Equal) } }); players.into_iter().take(limit).collect() } } } #[allow(clippy::too_many_lines, clippy::option_if_let_else)] pub fn select_entities( &self, target_selector: &TargetSelector, source: Option<&CommandSender>, ) -> Vec> { let all_entities_and_players = || { let mut entities = Vec::new(); for world in self.worlds.load().iter() { entities.extend(world.entities.load().iter().cloned()); entities.extend( world .players .load() .iter() .cloned() .map(|player| player as Arc), ); } entities }; let all_players_as_entities = || { self.get_all_players() .into_iter() .map(|player| player as Arc) .collect::>() }; let mut entities = match &target_selector.selector_type { EntitySelectorType::Source => source .and_then(CommandSender::as_player) .map_or_else(Vec::new, |player| vec![player as Arc]), EntitySelectorType::NearestPlayer | EntitySelectorType::RandomPlayer | EntitySelectorType::AllPlayers => all_players_as_entities(), EntitySelectorType::NearestEntity | EntitySelectorType::AllEntities => { all_entities_and_players() } EntitySelectorType::NamedPlayer(name) => self .get_player_by_name(name) .map_or_else(Vec::new, |player| vec![player as Arc]), EntitySelectorType::Uuid(uuid) => self .get_player_by_uuid(*uuid) .map_or_else(Vec::new, |player| vec![player as Arc]), }; let type_included = target_selector .conditions .iter() .filter_map(|f| { if let EntityFilter::Type(ValueCondition::Equals(entity_type)) = f { Some(*entity_type) } else { None } }) .collect::>(); let type_excluded = target_selector .conditions .iter() .filter_map(|f| { if let EntityFilter::Type(ValueCondition::NotEquals(entity_type)) = f { Some(*entity_type) } else { None } }) .collect::>(); entities.retain(|entity| { // Filter by entity type (type_excluded.is_empty() || !type_excluded.contains(&entity.get_entity().entity_type)) && (type_included.is_empty() || type_included.contains(&entity.get_entity().entity_type)) }); let limit = target_selector.get_limit(); if limit == 0 { return vec![]; } match target_selector .get_sort() .unwrap_or(EntityFilterSort::Arbitrary) { EntityFilterSort::Arbitrary => entities.into_iter().take(limit).collect(), EntityFilterSort::Random => { entities.shuffle(&mut rand::rng()); entities.into_iter().take(limit).collect() } EntityFilterSort::Nearest | EntityFilterSort::Furthest => { let center = source.and_then(CommandSender::position).unwrap_or_default(); let nearest_first = target_selector .get_sort() .is_none_or(|sort| sort == EntityFilterSort::Nearest); entities.sort_by(|a, b| { let a_distance = a.get_entity().pos.load().squared_distance_to_vec(¢er); let b_distance = b.get_entity().pos.load().squared_distance_to_vec(¢er); if nearest_first { a_distance .partial_cmp(&b_distance) .unwrap_or(core::cmp::Ordering::Equal) } else { b_distance .partial_cmp(&a_distance) .unwrap_or(core::cmp::Ordering::Equal) } }); entities.into_iter().take(limit).collect() } } } }