470 lines
16 KiB
Rust
470 lines
16 KiB
Rust
use connection_cache::{CachedBranding, CachedStatus};
|
|
use crossbeam::atomic::AtomicCell;
|
|
use hmac::digest::consts::U6;
|
|
use key_store::KeyStore;
|
|
use pumpkin_config::{ADVANCED_CONFIG, BASIC_CONFIG};
|
|
use pumpkin_data::entity::EntityType;
|
|
use pumpkin_inventory::drag_handler::DragHandler;
|
|
use pumpkin_inventory::{Container, OpenContainer};
|
|
use pumpkin_protocol::client::login::CEncryptionRequest;
|
|
use pumpkin_protocol::{client::config::CPluginMessage, ClientPacket};
|
|
use pumpkin_registry::{DimensionType, Registry};
|
|
use pumpkin_util::math::boundingbox::{BoundingBox, BoundingBoxSize};
|
|
use pumpkin_util::math::position::BlockPos;
|
|
use pumpkin_util::math::vector2::Vector2;
|
|
use pumpkin_util::math::vector3::Vector3;
|
|
use pumpkin_util::GameMode;
|
|
use pumpkin_world::block::block_registry::Block;
|
|
use pumpkin_world::dimension::Dimension;
|
|
use pumpkin_world::entity::entity_registry::get_entity_by_id;
|
|
use rand::prelude::SliceRandom;
|
|
use std::collections::HashMap;
|
|
use std::sync::atomic::AtomicU32;
|
|
use std::{
|
|
sync::{
|
|
atomic::{AtomicI32, Ordering},
|
|
Arc,
|
|
},
|
|
time::Duration,
|
|
};
|
|
use tokio::sync::{Mutex, RwLock};
|
|
use uuid::Uuid;
|
|
|
|
use crate::block::block_manager::BlockManager;
|
|
use crate::block::default_block_manager;
|
|
use crate::entity::ai::path::Navigator;
|
|
use crate::entity::living::LivingEntity;
|
|
use crate::entity::mob::MobEntity;
|
|
use crate::entity::{Entity, EntityId};
|
|
use crate::net::EncryptionError;
|
|
use crate::world::custom_bossbar::CustomBossbars;
|
|
use crate::{
|
|
command::{default_dispatcher, dispatcher::CommandDispatcher},
|
|
entity::player::Player,
|
|
net::Client,
|
|
world::World,
|
|
};
|
|
|
|
mod connection_cache;
|
|
mod key_store;
|
|
pub mod ticker;
|
|
|
|
pub const CURRENT_MC_VERSION: &str = "1.21.4";
|
|
|
|
/// Represents a Minecraft server instance.
|
|
pub struct Server {
|
|
/// Handles cryptographic keys for secure communication.
|
|
key_store: KeyStore,
|
|
/// Manages server status information.
|
|
server_listing: Mutex<CachedStatus>,
|
|
/// Saves server branding information.
|
|
server_branding: CachedBranding,
|
|
/// Saves and Dispatches commands to appropriate handlers.
|
|
pub command_dispatcher: RwLock<CommandDispatcher>,
|
|
/// Saves and calls blocks blocks
|
|
pub block_manager: Arc<BlockManager>,
|
|
/// Manages multiple worlds within the server.
|
|
pub worlds: Vec<Arc<World>>,
|
|
// All the dimensions that exists on the server,
|
|
pub dimensions: Vec<DimensionType>,
|
|
/// Caches game registries for efficient access.
|
|
pub cached_registry: Vec<Registry>,
|
|
/// Tracks open containers used for item interactions.
|
|
// TODO: should have per player open_containers
|
|
pub open_containers: RwLock<HashMap<u64, OpenContainer>>,
|
|
pub drag_handler: DragHandler,
|
|
/// Assigns unique IDs to entities.
|
|
entity_id: AtomicI32,
|
|
/// Assigns unique IDs to containers.
|
|
container_id: AtomicU32,
|
|
/// Manages authentication with a authentication server, if enabled.
|
|
pub auth_client: Option<reqwest::Client>,
|
|
/// The server's custom bossbars
|
|
pub bossbars: Mutex<CustomBossbars>,
|
|
}
|
|
|
|
impl Server {
|
|
#[allow(clippy::new_without_default)]
|
|
#[must_use]
|
|
pub fn new() -> Self {
|
|
let auth_client = BASIC_CONFIG.online_mode.then(|| {
|
|
reqwest::Client::builder()
|
|
.connect_timeout(Duration::from_millis(
|
|
ADVANCED_CONFIG.networking.authentication.connect_timeout as u64,
|
|
))
|
|
.read_timeout(Duration::from_millis(
|
|
ADVANCED_CONFIG.networking.authentication.read_timeout as u64,
|
|
))
|
|
.build()
|
|
.expect("Failed to to make reqwest client")
|
|
});
|
|
|
|
// First register default command, after that plugins can put in their own
|
|
let command_dispatcher = RwLock::new(default_dispatcher());
|
|
|
|
let world = World::load(
|
|
Dimension::OverWorld.into_level(
|
|
// TODO: load form config
|
|
"./world".parse().unwrap(),
|
|
),
|
|
DimensionType::Overworld,
|
|
);
|
|
|
|
// Spawn chunks are never unloaded
|
|
for x in -1..=1 {
|
|
for z in -1..=1 {
|
|
world.level.mark_chunk_as_newly_watched(Vector2::new(x, z));
|
|
}
|
|
}
|
|
|
|
Self {
|
|
cached_registry: Registry::get_synced(),
|
|
open_containers: RwLock::new(HashMap::new()),
|
|
drag_handler: DragHandler::new(),
|
|
// 0 is invalid
|
|
entity_id: 2.into(),
|
|
container_id: 0.into(),
|
|
worlds: vec![Arc::new(world)],
|
|
dimensions: vec![
|
|
DimensionType::Overworld,
|
|
DimensionType::OverworldCaves,
|
|
DimensionType::TheNether,
|
|
DimensionType::TheEnd,
|
|
],
|
|
command_dispatcher,
|
|
block_manager: default_block_manager(),
|
|
auth_client,
|
|
key_store: KeyStore::new(),
|
|
server_listing: Mutex::new(CachedStatus::new()),
|
|
server_branding: CachedBranding::new(),
|
|
bossbars: Mutex::new(CustomBossbars::new()),
|
|
}
|
|
}
|
|
|
|
/// Adds a new player to the server.
|
|
///
|
|
/// This function takes an `Arc<Client>` 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 player's configuration.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `client`: An `Arc<Client>` representing the connected client.
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// A tuple containing:
|
|
///
|
|
/// - `Arc<Player>`: A reference to the newly created player object.
|
|
/// - `Arc<World>`: A reference to the world the player was added to.
|
|
///
|
|
/// # Note
|
|
///
|
|
/// You still have to spawn the Player in the World to make then to let them Join and make them Visible
|
|
pub async fn add_player(&self, client: Arc<Client>) -> (Arc<Player>, Arc<World>) {
|
|
let entity_id = self.new_entity_id();
|
|
let gamemode = match BASIC_CONFIG.default_gamemode {
|
|
GameMode::Undefined => GameMode::Survival,
|
|
game_mode => game_mode,
|
|
};
|
|
// Basically the default world
|
|
// TODO: select default from config
|
|
let world = &self.worlds[0];
|
|
|
|
let player = Arc::new(Player::new(client, world.clone(), entity_id, gamemode).await);
|
|
world
|
|
.add_player(player.gameprofile.id, player.clone())
|
|
.await;
|
|
// TODO: Config if we want increase online
|
|
if let Some(config) = player.client.config.lock().await.as_ref() {
|
|
// TODO: Config so we can also just ignore this hehe
|
|
if config.server_listing {
|
|
self.server_listing.lock().await.add_player();
|
|
}
|
|
}
|
|
|
|
(player, world.clone())
|
|
}
|
|
|
|
pub async fn remove_player(&self) {
|
|
// TODO: Config if we want decrease online
|
|
self.server_listing.lock().await.remove_player();
|
|
}
|
|
|
|
pub async fn save(&self) {
|
|
for world in &self.worlds {
|
|
world.save().await;
|
|
}
|
|
}
|
|
|
|
pub async fn add_mob_entity(
|
|
&self,
|
|
entity_type: EntityType,
|
|
position: Vector3<f64>,
|
|
world: &Arc<World>,
|
|
) -> (Arc<MobEntity>, Uuid) {
|
|
let (living_entity, uuid) = self.add_living_entity(position, entity_type, world);
|
|
|
|
let mob = Arc::new(MobEntity {
|
|
living_entity,
|
|
goals: Mutex::new(vec![]),
|
|
navigator: Mutex::new(Navigator::default()),
|
|
});
|
|
world.add_mob_entity(uuid, mob.clone()).await;
|
|
(mob, uuid)
|
|
}
|
|
/// Adds a new living entity to the server. This does not Spawn the entity
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// A tuple containing:
|
|
///
|
|
/// - `Arc<LivingEntity>`: A reference to the newly created living entity.
|
|
/// - `Arc<World>`: A reference to the world that the living entity was added to.
|
|
/// - `Uuid`: The uuid of the newly created living entity to be used to send to the client.
|
|
fn add_living_entity(
|
|
&self,
|
|
position: Vector3<f64>,
|
|
entity_type: EntityType,
|
|
world: &Arc<World>,
|
|
) -> (Arc<LivingEntity>, Uuid) {
|
|
let entity_id = self.new_entity_id();
|
|
|
|
// TODO: this should be resolved to a integer using a macro when calling this function
|
|
let bounding_box_size = get_entity_by_id(entity_type as u16).map_or(
|
|
BoundingBoxSize {
|
|
width: 0.6,
|
|
height: 1.8,
|
|
},
|
|
|entity| BoundingBoxSize {
|
|
width: f64::from(entity.dimension[0]),
|
|
height: f64::from(entity.dimension[1]),
|
|
},
|
|
);
|
|
|
|
// TODO: standing eye height should be per mob
|
|
let new_uuid = uuid::Uuid::new_v4();
|
|
let mob = Arc::new(LivingEntity::new(Entity::new(
|
|
entity_id,
|
|
new_uuid,
|
|
world.clone(),
|
|
position,
|
|
entity_type,
|
|
1.62,
|
|
AtomicCell::new(BoundingBox::new_default(&bounding_box_size)),
|
|
AtomicCell::new(bounding_box_size),
|
|
)));
|
|
|
|
(mob, new_uuid)
|
|
}
|
|
|
|
pub async fn try_get_container(
|
|
&self,
|
|
player_id: EntityId,
|
|
container_id: u64,
|
|
) -> Option<Arc<Mutex<Box<dyn Container>>>> {
|
|
let open_containers = self.open_containers.read().await;
|
|
open_containers
|
|
.get(&container_id)?
|
|
.try_open(player_id)
|
|
.cloned()
|
|
}
|
|
|
|
/// Returns the first id with a matching location and block type. If this is used with unique
|
|
/// blocks, the output will return a random result.
|
|
pub async fn get_container_id(&self, location: BlockPos, block: Block) -> Option<u32> {
|
|
let open_containers = self.open_containers.read().await;
|
|
// TODO: do better than brute force
|
|
for (id, container) in open_containers.iter() {
|
|
if container.is_location(location) {
|
|
if let Some(container_block) = container.get_block() {
|
|
if container_block.id == block.id {
|
|
log::debug!("Found container id: {}", id);
|
|
return Some(*id as u32);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
drop(open_containers);
|
|
|
|
None
|
|
}
|
|
|
|
pub async fn get_all_container_ids(
|
|
&self,
|
|
location: BlockPos,
|
|
block: Block,
|
|
) -> Option<Vec<u32>> {
|
|
let open_containers = self.open_containers.read().await;
|
|
let mut matching_container_ids: Vec<u32> = vec![];
|
|
// TODO: do better than brute force
|
|
for (id, container) in open_containers.iter() {
|
|
if container.is_location(location) {
|
|
if let Some(container_block) = container.get_block() {
|
|
if container_block.id == block.id {
|
|
log::debug!("Found matching container id: {}", id);
|
|
matching_container_ids.push(*id as u32);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
drop(open_containers);
|
|
|
|
Some(matching_container_ids)
|
|
}
|
|
|
|
/// 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 async fn broadcast_packet_all<P>(&self, packet: &P)
|
|
where
|
|
P: ClientPacket,
|
|
{
|
|
for world in &self.worlds {
|
|
world.broadcast_packet_all(packet).await;
|
|
}
|
|
}
|
|
|
|
/// 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<Player>` reference to that player. Otherwise, it returns `None`.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `name`: The username of the player to search for.
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// An `Option<Arc<Player>>` containing the player if found, or `None` if not found.
|
|
pub async fn get_player_by_name(&self, name: &str) -> Option<Arc<Player>> {
|
|
for world in &self.worlds {
|
|
if let Some(player) = world.get_player_by_name(name).await {
|
|
return Some(player);
|
|
}
|
|
}
|
|
None
|
|
}
|
|
|
|
/// Returns all players from all worlds.
|
|
pub async fn get_all_players(&self) -> Vec<Arc<Player>> {
|
|
let mut players = Vec::<Arc<Player>>::new();
|
|
|
|
for world in &self.worlds {
|
|
for (_, player) in world.current_players.lock().await.iter() {
|
|
players.push(player.clone());
|
|
}
|
|
}
|
|
|
|
players
|
|
}
|
|
|
|
/// Returns a random player from any of the worlds or None if all worlds are empty.
|
|
pub async fn get_random_player(&self) -> Option<Arc<Player>> {
|
|
let players = self.get_all_players().await;
|
|
|
|
players.choose(&mut rand::thread_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<Player>` reference to that player. Otherwise, it returns `None`.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `id`: The UUID of the player to search for.
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// An `Option<Arc<Player>>` containing the player if found, or `None` if not found.
|
|
pub async fn get_player_by_uuid(&self, id: uuid::Uuid) -> Option<Arc<Player>> {
|
|
for world in &self.worlds {
|
|
if let Some(player) = world.get_player_by_uuid(id).await {
|
|
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 async fn get_player_count(&self) -> usize {
|
|
let mut count = 0;
|
|
for world in &self.worlds {
|
|
count += world.current_players.lock().await.len();
|
|
}
|
|
count
|
|
}
|
|
|
|
/// Similar to [`Server::get_player_count`] >= n, but may be more efficient since it stops it's iteration through all worlds as soon as n players were found.
|
|
pub async fn has_n_players(&self, n: usize) -> bool {
|
|
let mut count = 0;
|
|
for world in &self.worlds {
|
|
count += world.current_players.lock().await.len();
|
|
if count >= n {
|
|
return true;
|
|
}
|
|
}
|
|
false
|
|
}
|
|
|
|
/// Generates a new entity id
|
|
/// This should be global
|
|
pub fn new_entity_id(&self) -> EntityId {
|
|
self.entity_id.fetch_add(1, Ordering::SeqCst)
|
|
}
|
|
|
|
/// Generates a new container id
|
|
pub fn new_container_id(&self) -> u32 {
|
|
self.container_id.fetch_add(1, Ordering::SeqCst)
|
|
}
|
|
|
|
pub fn get_branding(&self) -> CPluginMessage<'_> {
|
|
self.server_branding.get_branding()
|
|
}
|
|
|
|
pub fn get_status(&self) -> &Mutex<CachedStatus> {
|
|
&self.server_listing
|
|
}
|
|
|
|
pub fn encryption_request<'a>(
|
|
&'a self,
|
|
verification_token: &'a [u8; 4],
|
|
should_authenticate: bool,
|
|
) -> CEncryptionRequest<'a> {
|
|
self.key_store
|
|
.encryption_request("", verification_token, should_authenticate)
|
|
}
|
|
|
|
pub fn decrypt(&self, data: &[u8]) -> Result<Vec<u8>, EncryptionError> {
|
|
self.key_store.decrypt(data)
|
|
}
|
|
|
|
pub fn digest_secret(&self, secret: &[u8]) -> String {
|
|
self.key_store.get_digest(secret)
|
|
}
|
|
|
|
async fn tick(&self) {
|
|
for world in &self.worlds {
|
|
world.tick().await;
|
|
}
|
|
}
|
|
}
|