fix: end spawning
This commit is contained in:
parent
03a90a40b1
commit
1dbfb75ecc
@ -190,16 +190,20 @@ impl PacketRead for SInventoryTransaction {
|
|||||||
fn read<R: Read>(buf: &mut R) -> Result<Self, Error> {
|
fn read<R: Read>(buf: &mut R) -> Result<Self, Error> {
|
||||||
let legacy_request_id = VarInt::read(buf)?;
|
let legacy_request_id = VarInt::read(buf)?;
|
||||||
|
|
||||||
|
let has_legacy_slots = bool::read(buf)?;
|
||||||
let mut legacy_set_item_slots = Vec::new();
|
let mut legacy_set_item_slots = Vec::new();
|
||||||
if legacy_request_id.0 != 0 {
|
if has_legacy_slots {
|
||||||
let len = VarUInt::read(buf)?.0;
|
let len = VarUInt::read(buf)?.0;
|
||||||
for _ in 0..len {
|
for _ in 0..len {
|
||||||
legacy_set_item_slots.push(LegacySetItemSlot::read(buf)?);
|
legacy_set_item_slots.push(LegacySetItemSlot::read(buf)?);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let _has_transaction_type = bool::read(buf)?;
|
||||||
let transaction_type = VarUInt::read(buf)?;
|
let transaction_type = VarUInt::read(buf)?;
|
||||||
|
|
||||||
|
let _has_tr_data = bool::read(buf)?;
|
||||||
|
|
||||||
let has_value = bool::read(buf)?;
|
let has_value = bool::read(buf)?;
|
||||||
let mut actions = Vec::new();
|
let mut actions = Vec::new();
|
||||||
if has_value {
|
if has_value {
|
||||||
|
|||||||
@ -20,6 +20,7 @@ pub mod request_chunk_radius;
|
|||||||
pub mod request_network_settings;
|
pub mod request_network_settings;
|
||||||
pub mod resource_pack_response;
|
pub mod resource_pack_response;
|
||||||
pub mod set_local_player_as_initialized;
|
pub mod set_local_player_as_initialized;
|
||||||
|
pub mod set_player_inventory_options;
|
||||||
pub mod text;
|
pub mod text;
|
||||||
|
|
||||||
pub use actor_event::*;
|
pub use actor_event::*;
|
||||||
@ -44,4 +45,5 @@ pub use request_chunk_radius::*;
|
|||||||
pub use request_network_settings::*;
|
pub use request_network_settings::*;
|
||||||
pub use resource_pack_response::*;
|
pub use resource_pack_response::*;
|
||||||
pub use set_local_player_as_initialized::*;
|
pub use set_local_player_as_initialized::*;
|
||||||
|
pub use set_player_inventory_options::*;
|
||||||
pub use text::*;
|
pub use text::*;
|
||||||
|
|||||||
@ -0,0 +1,24 @@
|
|||||||
|
use crate::serial::PacketRead;
|
||||||
|
use pumpkin_macros::packet;
|
||||||
|
use std::io::{Error, Read};
|
||||||
|
|
||||||
|
#[packet(307)]
|
||||||
|
pub struct SSetPlayerInventoryOptions {
|
||||||
|
pub left_inventory_tab: u8,
|
||||||
|
pub right_inventory_tab: u8,
|
||||||
|
pub filtering: bool,
|
||||||
|
pub inventory_layout: u8,
|
||||||
|
pub crafting_layout: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PacketRead for SSetPlayerInventoryOptions {
|
||||||
|
fn read<R: Read>(reader: &mut R) -> Result<Self, Error> {
|
||||||
|
Ok(Self {
|
||||||
|
left_inventory_tab: u8::read(reader)?,
|
||||||
|
right_inventory_tab: u8::read(reader)?,
|
||||||
|
filtering: bool::read(reader)?,
|
||||||
|
inventory_layout: u8::read(reader)?,
|
||||||
|
crafting_layout: u8::read(reader)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -12,7 +12,7 @@ use pumpkin_data::{Block, chunk::ChunkStatus, fluid::Fluid};
|
|||||||
use pumpkin_nbt::{compound::NbtCompound, nbt_long_array};
|
use pumpkin_nbt::{compound::NbtCompound, nbt_long_array};
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use tracing::debug;
|
use tracing::{debug, trace};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -291,6 +291,9 @@ impl ChunkEntityData {
|
|||||||
}
|
}
|
||||||
let mut map = FxHashMap::default();
|
let mut map = FxHashMap::default();
|
||||||
for entity_nbt in chunk_entity_data.entities {
|
for entity_nbt in chunk_entity_data.entities {
|
||||||
|
if entity_nbt.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let uuid = if let Some(uuid) = entity_nbt.get_int_array("UUID") {
|
let uuid = if let Some(uuid) = entity_nbt.get_int_array("UUID") {
|
||||||
if uuid.len() != 4 {
|
if uuid.len() != 4 {
|
||||||
debug!(
|
debug!(
|
||||||
@ -309,7 +312,7 @@ impl ChunkEntityData {
|
|||||||
| (uuid[3] as u128),
|
| (uuid[3] as u128),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
debug!(
|
trace!(
|
||||||
"Entity in chunk {},{} is missing UUID: {:?}",
|
"Entity in chunk {},{} is missing UUID: {:?}",
|
||||||
position.x, position.y, entity_nbt
|
position.x, position.y, entity_nbt
|
||||||
);
|
);
|
||||||
|
|||||||
@ -314,7 +314,7 @@ impl BlockBehaviour for SignBlock {
|
|||||||
|
|
||||||
fn player_placed<'a>(&'a self, args: PlayerPlacedArgs<'a>) -> BlockFuture<'a, ()> {
|
fn player_placed<'a>(&'a self, args: PlayerPlacedArgs<'a>) -> BlockFuture<'a, ()> {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
match &args.player.client {
|
match args.player.client.as_ref() {
|
||||||
crate::net::ClientPlatform::Java(java) => {
|
crate::net::ClientPlatform::Java(java) => {
|
||||||
java.send_sign_packet(*args.position, true).await;
|
java.send_sign_packet(*args.position, true).await;
|
||||||
}
|
}
|
||||||
@ -454,7 +454,7 @@ impl BlockBehaviour for SignBlock {
|
|||||||
|
|
||||||
let is_facing_front_text =
|
let is_facing_front_text =
|
||||||
is_facing_front_text(args.world, args.position, args.block, args.player);
|
is_facing_front_text(args.world, args.position, args.block, args.player);
|
||||||
match &args.player.client {
|
match args.player.client.as_ref() {
|
||||||
ClientPlatform::Java(java) => {
|
ClientPlatform::Java(java) => {
|
||||||
java.send_sign_packet(*args.position, is_facing_front_text)
|
java.send_sign_packet(*args.position, is_facing_front_text)
|
||||||
.await;
|
.await;
|
||||||
|
|||||||
@ -365,7 +365,7 @@ pub async fn send_bedrock_commands_packet(
|
|||||||
constraints: Vec::new(),
|
constraints: Vec::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let crate::net::ClientPlatform::Bedrock(bedrock_client) = &player.client {
|
if let crate::net::ClientPlatform::Bedrock(bedrock_client) = player.client.as_ref() {
|
||||||
bedrock_client.send_game_packet(&packet).await;
|
bedrock_client.send_game_packet(&packet).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -58,7 +58,7 @@ impl CommandExecutor for TargetSelfExecutor {
|
|||||||
let name = &player.gameprofile.name;
|
let name = &player.gameprofile.name;
|
||||||
info!("[{name}: Transferring {name} to {hostname}:{port}]");
|
info!("[{name}: Transferring {name} to {hostname}:{port}]");
|
||||||
|
|
||||||
match &player.client {
|
match player.client.as_ref() {
|
||||||
ClientPlatform::Java(client) => {
|
ClientPlatform::Java(client) => {
|
||||||
client
|
client
|
||||||
.enqueue_packet(&JavaCTransfer::new(hostname, VarInt(port)))
|
.enqueue_packet(&JavaCTransfer::new(hostname, VarInt(port)))
|
||||||
@ -121,7 +121,7 @@ impl CommandExecutor for TargetPlayerExecutor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for p in players {
|
for p in players {
|
||||||
match &p.client {
|
match p.client.as_ref() {
|
||||||
ClientPlatform::Java(client) => {
|
ClientPlatform::Java(client) => {
|
||||||
client
|
client
|
||||||
.enqueue_packet(&JavaCTransfer::new(hostname, VarInt(port)))
|
.enqueue_packet(&JavaCTransfer::new(hostname, VarInt(port)))
|
||||||
|
|||||||
@ -5,7 +5,7 @@ use crate::{
|
|||||||
world::{
|
world::{
|
||||||
World,
|
World,
|
||||||
chunker::is_within_view_distance,
|
chunker::is_within_view_distance,
|
||||||
portal::{NetherPortal, PortalManager, PortalSearchResult, SourcePortalInfo},
|
portal::{NetherPortal, PortalProcessor, PortalType, SourcePortalInfo},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use arc_swap::ArcSwap;
|
use arc_swap::ArcSwap;
|
||||||
@ -775,7 +775,7 @@ pub struct Entity {
|
|||||||
|
|
||||||
pub portal_cooldown: AtomicU32,
|
pub portal_cooldown: AtomicU32,
|
||||||
|
|
||||||
pub portal_manager: Mutex<Option<Mutex<PortalManager>>>,
|
pub portal_manager: Mutex<Option<Mutex<PortalProcessor>>>,
|
||||||
/// Custom name for the entity
|
/// Custom name for the entity
|
||||||
pub custom_name: ArcSwap<Option<TextComponent>>,
|
pub custom_name: ArcSwap<Option<TextComponent>>,
|
||||||
/// Indicates whether the entity's custom name is visible
|
/// Indicates whether the entity's custom name is visible
|
||||||
@ -2121,100 +2121,46 @@ impl Entity {
|
|||||||
let mut manager_guard = self.portal_manager.lock().await;
|
let mut manager_guard = self.portal_manager.lock().await;
|
||||||
let mut should_remove = false;
|
let mut should_remove = false;
|
||||||
if let Some(pmanager_mutex) = manager_guard.as_ref() {
|
if let Some(pmanager_mutex) = manager_guard.as_ref() {
|
||||||
let mut portal_manager = pmanager_mutex.lock().await;
|
let mut portal_processor = pmanager_mutex.lock().await;
|
||||||
if portal_manager.tick() {
|
if portal_processor.process_portal_teleportation(
|
||||||
|
&self.world.load(),
|
||||||
|
caller.as_ref(),
|
||||||
|
true,
|
||||||
|
) {
|
||||||
self.portal_cooldown
|
self.portal_cooldown
|
||||||
.store(self.default_portal_cooldown(), Ordering::Relaxed);
|
.store(self.default_portal_cooldown(), Ordering::Relaxed);
|
||||||
let pos = self.pos.load();
|
|
||||||
let current_yaw = self.yaw.load();
|
|
||||||
let dimensions = self.entity_dimension.load();
|
|
||||||
let scale_factor_new = portal_manager.portal_world.dimension.coordinate_scale;
|
|
||||||
let scale_factor_current = self.world.load().dimension.coordinate_scale;
|
|
||||||
|
|
||||||
let scale_factor = scale_factor_current / scale_factor_new;
|
let transition = portal_processor
|
||||||
let target_pos =
|
.portal_type
|
||||||
BlockPos::floored(pos.x * scale_factor, pos.y, pos.z * scale_factor);
|
.get_portal_destination(
|
||||||
|
&self.world.load(),
|
||||||
let dest_world = portal_manager.portal_world.clone();
|
portal_processor.destination_world.clone(),
|
||||||
let source_portal = portal_manager.source_portal.clone();
|
caller,
|
||||||
let source_axis = source_portal.as_ref().map(|p| p.axis);
|
portal_processor.entry_position,
|
||||||
drop(portal_manager);
|
portal_processor.source_portal.clone(),
|
||||||
|
|
||||||
let is_end_portal = dest_world.dimension == Dimension::THE_END
|
|
||||||
|| self.world.load().dimension == Dimension::THE_END;
|
|
||||||
|
|
||||||
let (teleport_pos, new_yaw) = if is_end_portal {
|
|
||||||
if dest_world.dimension == Dimension::THE_END {
|
|
||||||
// Entering the End: spawn on the obsidian platform at (100, 50, 0)
|
|
||||||
(Vector3::new(100.5f64, 50.0f64, 0.5f64), None)
|
|
||||||
} else {
|
|
||||||
// Leaving the End through the exit portal: return to overworld spawn
|
|
||||||
let info = dest_world.level_info.load();
|
|
||||||
(
|
|
||||||
Vector3::new(
|
|
||||||
f64::from(info.spawn_x) + 0.5,
|
|
||||||
f64::from(info.spawn_y),
|
|
||||||
f64::from(info.spawn_z) + 0.5,
|
|
||||||
),
|
|
||||||
None,
|
|
||||||
)
|
)
|
||||||
}
|
|
||||||
} else if let Some(dest_result) =
|
|
||||||
NetherPortal::search_for_portal(&dest_world, target_pos).await
|
|
||||||
{
|
|
||||||
let base_pos = source_portal.as_ref().map_or_else(
|
|
||||||
|| dest_result.get_teleport_position(),
|
|
||||||
|source| {
|
|
||||||
let source_result = PortalSearchResult {
|
|
||||||
lower_corner: source.lower_corner,
|
|
||||||
axis: source.axis,
|
|
||||||
width: source.width,
|
|
||||||
height: source.height,
|
|
||||||
};
|
|
||||||
let relative_pos = source_result.entity_pos_in_portal(pos, &dimensions);
|
|
||||||
dest_result.calculate_exit_position(relative_pos, &dimensions)
|
|
||||||
},
|
|
||||||
);
|
|
||||||
let final_pos =
|
|
||||||
dest_result.find_open_position(&dest_world, base_pos, &dimensions);
|
|
||||||
let yaw = dest_result.calculate_teleport_yaw(current_yaw, source_axis);
|
|
||||||
(final_pos, Some(yaw))
|
|
||||||
} else if let Some((build_pos, axis, is_fallback)) =
|
|
||||||
NetherPortal::find_safe_location(
|
|
||||||
&dest_world,
|
|
||||||
target_pos,
|
|
||||||
pumpkin_data::block_properties::HorizontalAxis::X,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
NetherPortal::build_portal_frame(&dest_world, build_pos, axis, is_fallback)
|
|
||||||
.await;
|
.await;
|
||||||
let new_portal = PortalSearchResult {
|
|
||||||
lower_corner: build_pos,
|
drop(portal_processor);
|
||||||
axis,
|
|
||||||
width: 2,
|
if let Some(transition) = transition {
|
||||||
height: 3,
|
let dest_world = transition.new_world.clone();
|
||||||
};
|
let yaw = transition.yaw;
|
||||||
let center_pos = new_portal.get_teleport_position();
|
let pitch = transition.pitch;
|
||||||
let final_pos =
|
let teleport_pos = transition.position;
|
||||||
new_portal.find_open_position(&dest_world, center_pos, &dimensions);
|
|
||||||
let yaw = new_portal.calculate_teleport_yaw(current_yaw, source_axis);
|
|
||||||
(final_pos, Some(yaw))
|
|
||||||
} else {
|
|
||||||
(target_pos.0.to_f64(), None)
|
|
||||||
};
|
|
||||||
|
|
||||||
// Teleport the main entity
|
// Teleport the main entity
|
||||||
caller
|
caller
|
||||||
.clone()
|
.clone()
|
||||||
.teleport(teleport_pos, new_yaw, None, dest_world.clone())
|
.teleport(teleport_pos, yaw, pitch, dest_world.clone())
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
// Teleport all passengers recursively along with the vehicle
|
// Teleport all passengers recursively along with the vehicle
|
||||||
let yaw_delta = new_yaw.map(|y| y - current_yaw);
|
let yaw_delta = yaw.map(|y| y - self.yaw.load());
|
||||||
Self::teleport_passengers_recursive(self, teleport_pos, yaw_delta, &dest_world)
|
Self::teleport_passengers_recursive(self, teleport_pos, yaw_delta, &dest_world)
|
||||||
.await;
|
.await;
|
||||||
} else if portal_manager.ticks_in_portal == 0 {
|
}
|
||||||
|
} else if portal_processor.portal_time == 0 {
|
||||||
should_remove = true;
|
should_remove = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2262,7 +2208,12 @@ impl Entity {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn try_use_portal(&self, portal_delay: u32, portal_world: Arc<World>, pos: BlockPos) {
|
pub async fn try_use_portal(
|
||||||
|
&self,
|
||||||
|
_portal_delay: u32,
|
||||||
|
portal_world: Arc<World>,
|
||||||
|
pos: BlockPos,
|
||||||
|
) {
|
||||||
// Passengers don't teleport independently - they wait for their vehicle
|
// Passengers don't teleport independently - they wait for their vehicle
|
||||||
if self.has_vehicle().await {
|
if self.has_vehicle().await {
|
||||||
return;
|
return;
|
||||||
@ -2295,7 +2246,15 @@ impl Entity {
|
|||||||
let mut manager = self.portal_manager.lock().await;
|
let mut manager = self.portal_manager.lock().await;
|
||||||
let world = self.world.load();
|
let world = self.world.load();
|
||||||
if manager.is_none() {
|
if manager.is_none() {
|
||||||
let mut new_manager = PortalManager::new(portal_delay, portal_world, pos);
|
let portal_type = if portal_world.dimension == Dimension::THE_END
|
||||||
|
|| self.world.load().dimension == Dimension::THE_END
|
||||||
|
{
|
||||||
|
PortalType::End
|
||||||
|
} else {
|
||||||
|
PortalType::Nether
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut new_manager = PortalProcessor::new(portal_type, pos, portal_world);
|
||||||
|
|
||||||
if let Some(portal) = NetherPortal::get_on_axis(
|
if let Some(portal) = NetherPortal::get_on_axis(
|
||||||
&world,
|
&world,
|
||||||
@ -2326,8 +2285,8 @@ impl Entity {
|
|||||||
*manager = Some(Mutex::new(new_manager));
|
*manager = Some(Mutex::new(new_manager));
|
||||||
} else if let Some(manager) = manager.as_ref() {
|
} else if let Some(manager) = manager.as_ref() {
|
||||||
let mut manager = manager.lock().await;
|
let mut manager = manager.lock().await;
|
||||||
manager.pos = pos;
|
manager.entry_position = pos;
|
||||||
manager.in_portal = true;
|
manager.inside_portal_this_tick = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2712,7 +2671,7 @@ impl Entity {
|
|||||||
let world = self.world.load();
|
let world = self.world.load();
|
||||||
let chunk_pos = self.chunk_pos.load();
|
let chunk_pos = self.chunk_pos.load();
|
||||||
for player in world.players.load().iter() {
|
for player in world.players.load().iter() {
|
||||||
if let ClientPlatform::Bedrock(client) = &player.client {
|
if let ClientPlatform::Bedrock(client) = player.client.as_ref() {
|
||||||
let center = player.get_entity().chunk_pos.load();
|
let center = player.get_entity().chunk_pos.load();
|
||||||
let view_distance =
|
let view_distance =
|
||||||
crate::world::chunker::get_view_distance(player).get() as i32;
|
crate::world::chunker::get_view_distance(player).get() as i32;
|
||||||
@ -2755,7 +2714,7 @@ impl Entity {
|
|||||||
let world = self.world.load();
|
let world = self.world.load();
|
||||||
let chunk_pos = self.chunk_pos.load();
|
let chunk_pos = self.chunk_pos.load();
|
||||||
for player in world.players.load().iter() {
|
for player in world.players.load().iter() {
|
||||||
if let ClientPlatform::Java(client) = &player.client {
|
if let ClientPlatform::Java(client) = player.client.as_ref() {
|
||||||
// Apply Chebyshev distance check
|
// Apply Chebyshev distance check
|
||||||
let center = player.get_entity().chunk_pos.load();
|
let center = player.get_entity().chunk_pos.load();
|
||||||
let view_distance = crate::world::chunker::get_view_distance(player).get() as i32;
|
let view_distance = crate::world::chunker::get_view_distance(player).get() as i32;
|
||||||
|
|||||||
@ -411,7 +411,7 @@ pub struct Player {
|
|||||||
/// The player's game profile information, including their username and UUID.
|
/// The player's game profile information, including their username and UUID.
|
||||||
pub gameprofile: GameProfile,
|
pub gameprofile: GameProfile,
|
||||||
/// The client connection associated with the player.
|
/// The client connection associated with the player.
|
||||||
pub client: ClientPlatform,
|
pub client: Arc<ClientPlatform>,
|
||||||
/// The player's inventory.
|
/// The player's inventory.
|
||||||
pub inventory: Arc<PlayerInventory>,
|
pub inventory: Arc<PlayerInventory>,
|
||||||
/// The player's `EnderChest` inventory.
|
/// The player's `EnderChest` inventory.
|
||||||
@ -571,7 +571,7 @@ impl Player {
|
|||||||
|
|
||||||
#[expect(clippy::too_many_lines)]
|
#[expect(clippy::too_many_lines)]
|
||||||
pub async fn new(
|
pub async fn new(
|
||||||
client: ClientPlatform,
|
client: Arc<ClientPlatform>,
|
||||||
gameprofile: GameProfile,
|
gameprofile: GameProfile,
|
||||||
config: PlayerConfig,
|
config: PlayerConfig,
|
||||||
world: Arc<World>,
|
world: Arc<World>,
|
||||||
@ -1729,7 +1729,7 @@ impl Player {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn show_title(&self, text: &TextComponent, mode: &TitleMode) {
|
pub async fn show_title(&self, text: &TextComponent, mode: &TitleMode) {
|
||||||
match &self.client {
|
match self.client.as_ref() {
|
||||||
ClientPlatform::Java(client) => match mode {
|
ClientPlatform::Java(client) => match mode {
|
||||||
TitleMode::Title => client.enqueue_packet(&CTitleText::new(text)).await,
|
TitleMode::Title => client.enqueue_packet(&CTitleText::new(text)).await,
|
||||||
TitleMode::SubTitle => client.enqueue_packet(&CSubtitle::new(text)).await,
|
TitleMode::SubTitle => client.enqueue_packet(&CSubtitle::new(text)).await,
|
||||||
@ -1757,7 +1757,7 @@ impl Player {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send_title_animation(&self, fade_in: i32, stay: i32, fade_out: i32) {
|
pub async fn send_title_animation(&self, fade_in: i32, stay: i32, fade_out: i32) {
|
||||||
match &self.client {
|
match self.client.as_ref() {
|
||||||
ClientPlatform::Java(client) => {
|
ClientPlatform::Java(client) => {
|
||||||
client
|
client
|
||||||
.enqueue_packet(&CTitleAnimation::new(fade_in, stay, fade_out))
|
.enqueue_packet(&CTitleAnimation::new(fade_in, stay, fade_out))
|
||||||
@ -1915,11 +1915,10 @@ impl Player {
|
|||||||
*xp -= 1;
|
*xp -= 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (chunk_of_chunks, total_sent_chunks) = {
|
let (chunk_of_chunks, total_sent_chunks) = {
|
||||||
let mut chunk_manager = self.chunk_manager.lock().await;
|
let mut chunk_manager = self.chunk_manager.lock().await;
|
||||||
chunk_manager.pull_new_chunks();
|
chunk_manager.pull_new_chunks();
|
||||||
let chunks = if let ClientPlatform::Java(_) = self.client {
|
let chunks = if let ClientPlatform::Java(_) = self.client.as_ref() {
|
||||||
// Java clients can only send a limited amount of chunks per tick.
|
// Java clients can only send a limited amount of chunks per tick.
|
||||||
// If we have sent too many chunks without receiving an ack, we stop sending chunks.
|
// If we have sent too many chunks without receiving an ack, we stop sending chunks.
|
||||||
chunk_manager
|
chunk_manager
|
||||||
@ -1930,11 +1929,12 @@ impl Player {
|
|||||||
};
|
};
|
||||||
(chunks, chunk_manager.sent_chunks_count())
|
(chunks, chunk_manager.sent_chunks_count())
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(chunk_of_chunks) = chunk_of_chunks {
|
if let Some(chunk_of_chunks) = chunk_of_chunks {
|
||||||
self.client.send_chunks(&chunk_of_chunks).await;
|
let client = self.client.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
if let ClientPlatform::Bedrock(bedrock_client) = &self.client
|
client.send_chunks(&chunk_of_chunks).await;
|
||||||
|
});
|
||||||
|
if let ClientPlatform::Bedrock(bedrock_client) = self.client.as_ref()
|
||||||
&& !self.bedrock_spawned.load(Ordering::Relaxed)
|
&& !self.bedrock_spawned.load(Ordering::Relaxed)
|
||||||
&& total_sent_chunks > 4
|
&& total_sent_chunks > 4
|
||||||
{
|
{
|
||||||
@ -1944,7 +1944,6 @@ impl Player {
|
|||||||
self.bedrock_spawned.store(true, Ordering::Relaxed);
|
self.bedrock_spawned.store(true, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.tick_counter.fetch_add(1, Ordering::Relaxed);
|
self.tick_counter.fetch_add(1, Ordering::Relaxed);
|
||||||
self.living_entity
|
self.living_entity
|
||||||
.entity
|
.entity
|
||||||
@ -1978,7 +1977,6 @@ impl Player {
|
|||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.last_attacked_ticks.fetch_add(1, Ordering::Relaxed);
|
self.last_attacked_ticks.fetch_add(1, Ordering::Relaxed);
|
||||||
|
|
||||||
let caller: Arc<dyn EntityBase> = self.clone();
|
let caller: Arc<dyn EntityBase> = self.clone();
|
||||||
@ -1996,7 +1994,6 @@ impl Player {
|
|||||||
|
|
||||||
// Timeout/keep alive handling
|
// Timeout/keep alive handling
|
||||||
self.tick_client_load_timeout();
|
self.tick_client_load_timeout();
|
||||||
|
|
||||||
// Idle timeout handling
|
// Idle timeout handling
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
let idle_timeout_minutes = server.player_idle_timeout.load(Ordering::Relaxed);
|
let idle_timeout_minutes = server.player_idle_timeout.load(Ordering::Relaxed);
|
||||||
@ -2141,7 +2138,7 @@ impl Player {
|
|||||||
|
|
||||||
/// Updates the current abilities the player has.
|
/// Updates the current abilities the player has.
|
||||||
pub async fn send_abilities_update(&self) {
|
pub async fn send_abilities_update(&self) {
|
||||||
match &self.client {
|
match self.client.as_ref() {
|
||||||
ClientPlatform::Java(java) => {
|
ClientPlatform::Java(java) => {
|
||||||
let mut b = 0;
|
let mut b = 0;
|
||||||
let abilities = &self.abilities.lock().await;
|
let abilities = &self.abilities.lock().await;
|
||||||
@ -2244,7 +2241,7 @@ impl Player {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send_stats(&self) {
|
pub async fn send_stats(&self) {
|
||||||
if let ClientPlatform::Java(java) = &self.client {
|
if let ClientPlatform::Java(java) = self.client.as_ref() {
|
||||||
let stats_guard = self.stats.lock().await;
|
let stats_guard = self.stats.lock().await;
|
||||||
let packet_stats: Vec<Statistic> = stats_guard
|
let packet_stats: Vec<Statistic> = stats_guard
|
||||||
.stats
|
.stats
|
||||||
@ -2394,7 +2391,7 @@ impl Player {
|
|||||||
self.permission_lvl.store(lvl);
|
self.permission_lvl.store(lvl);
|
||||||
self.send_permission_lvl_update();
|
self.send_permission_lvl_update();
|
||||||
|
|
||||||
if let ClientPlatform::Bedrock(_) = &self.client {
|
if let ClientPlatform::Bedrock(_) = self.client.as_ref() {
|
||||||
client_suggestions::send_bedrock_commands_packet(self, server, command_dispatcher)
|
client_suggestions::send_bedrock_commands_packet(self, server, command_dispatcher)
|
||||||
.await;
|
.await;
|
||||||
} else {
|
} else {
|
||||||
@ -2405,7 +2402,7 @@ impl Player {
|
|||||||
/// Sends the world time to only this player.
|
/// Sends the world time to only this player.
|
||||||
pub async fn send_time(&self, world: &World) {
|
pub async fn send_time(&self, world: &World) {
|
||||||
let l_world = world.level_time.lock().await;
|
let l_world = world.level_time.lock().await;
|
||||||
match &self.client {
|
match self.client.as_ref() {
|
||||||
ClientPlatform::Java(java_client) => {
|
ClientPlatform::Java(java_client) => {
|
||||||
java_client
|
java_client
|
||||||
.enqueue_packet(&CUpdateTime::new(
|
.enqueue_packet(&CUpdateTime::new(
|
||||||
@ -2624,7 +2621,7 @@ impl Player {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
match &self.client {
|
match self.client.as_ref() {
|
||||||
ClientPlatform::Java(client) => {
|
ClientPlatform::Java(client) => {
|
||||||
client
|
client
|
||||||
.enqueue_packet(&CSetHealth::new(
|
.enqueue_packet(&CSetHealth::new(
|
||||||
@ -2898,7 +2895,7 @@ impl Player {
|
|||||||
}],
|
}],
|
||||||
));
|
));
|
||||||
|
|
||||||
match &self.client {
|
match self.client.as_ref() {
|
||||||
crate::net::ClientPlatform::Java(client) => {
|
crate::net::ClientPlatform::Java(client) => {
|
||||||
client
|
client
|
||||||
.enqueue_packet(&CGameEvent::new(
|
.enqueue_packet(&CGameEvent::new(
|
||||||
@ -3021,7 +3018,7 @@ impl Player {
|
|||||||
|
|
||||||
/// Sends a custom payload packet to this player (Java edition only).
|
/// Sends a custom payload packet to this player (Java edition only).
|
||||||
pub async fn send_custom_payload(&self, channel: &str, data: &[u8]) {
|
pub async fn send_custom_payload(&self, channel: &str, data: &[u8]) {
|
||||||
if let ClientPlatform::Java(java) = &self.client {
|
if let ClientPlatform::Java(java) = self.client.as_ref() {
|
||||||
java.enqueue_packet(&CCustomPayload::new(channel, data))
|
java.enqueue_packet(&CCustomPayload::new(channel, data))
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
@ -3114,7 +3111,7 @@ impl Player {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send_system_message_raw(&self, text: &TextComponent, overlay: bool) {
|
pub async fn send_system_message_raw(&self, text: &TextComponent, overlay: bool) {
|
||||||
match &self.client {
|
match self.client.as_ref() {
|
||||||
ClientPlatform::Java(client) => {
|
ClientPlatform::Java(client) => {
|
||||||
client
|
client
|
||||||
.enqueue_packet(&CSystemChatMessage::new(text, overlay))
|
.enqueue_packet(&CSystemChatMessage::new(text, overlay))
|
||||||
|
|||||||
@ -468,13 +468,13 @@ impl PumpkinServer {
|
|||||||
},
|
},
|
||||||
PacketHandlerResult::ReadyToPlay(profile,config) => {
|
PacketHandlerResult::ReadyToPlay(profile,config) => {
|
||||||
if let Some((player, world)) = server_clone
|
if let Some((player, world)) = server_clone
|
||||||
.add_player(ClientPlatform::Java(java_client), profile, Some(config))
|
.add_player(Arc::new(ClientPlatform::Java(java_client)), profile, Some(config))
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
world
|
world
|
||||||
.spawn_java_player(&server_clone.basic_config, &player, &server_clone)
|
.spawn_java_player(&server_clone.basic_config, &player, &server_clone)
|
||||||
.await;
|
.await;
|
||||||
if let ClientPlatform::Java(client) = &player.client {
|
if let ClientPlatform::Java(client) = player.client.as_ref() {
|
||||||
*client.player.lock().await = Some(player.clone());
|
*client.player.lock().await = Some(player.clone());
|
||||||
client.progress_player_packets(&player, &server_clone).await;
|
client.progress_player_packets(&player, &server_clone).await;
|
||||||
|
|
||||||
@ -558,7 +558,7 @@ impl PumpkinServer {
|
|||||||
}
|
}
|
||||||
PacketHandlerResult::ReadyToPlay(profile, config) => {
|
PacketHandlerResult::ReadyToPlay(profile, config) => {
|
||||||
if let Some((player, _world)) = server_clone
|
if let Some((player, _world)) = server_clone
|
||||||
.add_player(ClientPlatform::Bedrock(client_clone.clone()), profile, Some(config))
|
.add_player(Arc::new(ClientPlatform::Bedrock(client_clone.clone())), profile, Some(config))
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
*client_clone.player.lock().await = Some(player.clone());
|
*client_clone.player.lock().await = Some(player.clone());
|
||||||
|
|||||||
@ -53,6 +53,7 @@ use pumpkin_protocol::{
|
|||||||
request_network_settings::SRequestNetworkSettings,
|
request_network_settings::SRequestNetworkSettings,
|
||||||
resource_pack_response::SResourcePackResponse,
|
resource_pack_response::SResourcePackResponse,
|
||||||
set_local_player_as_initialized::SSetLocalPlayerAsInitialized,
|
set_local_player_as_initialized::SSetLocalPlayerAsInitialized,
|
||||||
|
set_player_inventory_options::SSetPlayerInventoryOptions,
|
||||||
text::SText,
|
text::SText,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -118,6 +119,7 @@ pub struct BedrockClient {
|
|||||||
pub be_clients: Arc<Mutex<HashMap<SocketAddr, Arc<Self>>>>,
|
pub be_clients: Arc<Mutex<HashMap<SocketAddr, Arc<Self>>>>,
|
||||||
|
|
||||||
tasks: TaskTracker,
|
tasks: TaskTracker,
|
||||||
|
rt_handle: tokio::runtime::Handle,
|
||||||
outgoing_packet_queue_send: Sender<OutgoingPacket>,
|
outgoing_packet_queue_send: Sender<OutgoingPacket>,
|
||||||
/// A queue of serialized packets to send to the network
|
/// A queue of serialized packets to send to the network
|
||||||
outgoing_packet_queue_recv: Mutex<Option<Receiver<OutgoingPacket>>>,
|
outgoing_packet_queue_recv: Mutex<Option<Receiver<OutgoingPacket>>>,
|
||||||
@ -166,6 +168,7 @@ impl BedrockClient {
|
|||||||
let (send, recv) = tokio::sync::mpsc::channel(4096);
|
let (send, recv) = tokio::sync::mpsc::channel(4096);
|
||||||
let (priority_send, priority_recv) = tokio::sync::mpsc::channel(4096);
|
let (priority_send, priority_recv) = tokio::sync::mpsc::channel(4096);
|
||||||
let (incoming_send, incoming_recv) = tokio::sync::mpsc::channel(4096);
|
let (incoming_send, incoming_recv) = tokio::sync::mpsc::channel(4096);
|
||||||
|
let rt_handle = tokio::runtime::Handle::current();
|
||||||
Self {
|
Self {
|
||||||
socket,
|
socket,
|
||||||
player: Mutex::new(None),
|
player: Mutex::new(None),
|
||||||
@ -176,6 +179,7 @@ impl BedrockClient {
|
|||||||
network_writer: Arc::new(RwLock::new(UDPNetworkEncoder::new())),
|
network_writer: Arc::new(RwLock::new(UDPNetworkEncoder::new())),
|
||||||
network_reader: Mutex::new(UDPNetworkDecoder::new()),
|
network_reader: Mutex::new(UDPNetworkDecoder::new()),
|
||||||
tasks: TaskTracker::new(),
|
tasks: TaskTracker::new(),
|
||||||
|
rt_handle,
|
||||||
outgoing_packet_queue_send: send,
|
outgoing_packet_queue_send: send,
|
||||||
outgoing_packet_queue_recv: Mutex::new(Some(recv)),
|
outgoing_packet_queue_recv: Mutex::new(Some(recv)),
|
||||||
outgoing_packet_priority_send: priority_send,
|
outgoing_packet_priority_send: priority_send,
|
||||||
@ -338,19 +342,62 @@ impl BedrockClient {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let mut valid_chunks = Vec::with_capacity(chunks.len());
|
||||||
for chunk in chunks {
|
for chunk in chunks {
|
||||||
let event = ChunkSend::new(player.world(), chunk.clone());
|
let event = ChunkSend::new(player.world(), chunk.clone());
|
||||||
let event = server.plugin_manager.fire(event).await;
|
let event = server.plugin_manager.fire(event).await;
|
||||||
if event.cancelled {
|
if !event.cancelled {
|
||||||
continue;
|
valid_chunks.push(chunk.clone());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.enqueue_packet_internal(&CLevelChunk {
|
if valid_chunks.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut serialize_tasks = Vec::with_capacity(valid_chunks.len());
|
||||||
|
for chunk in valid_chunks {
|
||||||
|
serialize_tasks.push(tokio::task::spawn_blocking(move || {
|
||||||
|
let mut packet_payload = Vec::new();
|
||||||
|
let packet = CLevelChunk {
|
||||||
dimension: 0,
|
dimension: 0,
|
||||||
cache_enabled: false,
|
cache_enabled: false,
|
||||||
chunk,
|
chunk: &chunk,
|
||||||
})
|
};
|
||||||
.await;
|
packet
|
||||||
|
.write_packet(&mut packet_payload)
|
||||||
|
.map(|()| packet_payload)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut encoded_payloads = Vec::with_capacity(serialize_tasks.len());
|
||||||
|
for task in serialize_tasks {
|
||||||
|
match task.await {
|
||||||
|
Ok(Ok(payload)) => encoded_payloads.push(payload),
|
||||||
|
Ok(Err(e)) => error!("Failed to serialize Bedrock chunk: {:?}", e),
|
||||||
|
Err(e) => error!("Join error in Bedrock chunk serialization: {:?}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut packets_to_enqueue = Vec::with_capacity(encoded_payloads.len());
|
||||||
|
{
|
||||||
|
let encoder = self.network_writer.read().await;
|
||||||
|
for payload in encoded_payloads {
|
||||||
|
let mut packet_buf = Vec::new();
|
||||||
|
match encoder.write_game_packet(
|
||||||
|
CLevelChunk::PACKET_ID as u16,
|
||||||
|
SubClient::Main,
|
||||||
|
SubClient::Main,
|
||||||
|
&payload,
|
||||||
|
&mut packet_buf,
|
||||||
|
) {
|
||||||
|
Ok(()) => packets_to_enqueue.push(packet_buf),
|
||||||
|
Err(err) => error!("Failed to write game packet wrapper: {err}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for packet_buf in packets_to_enqueue {
|
||||||
|
self.enqueue_packet_data(packet_buf.into()).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1040,6 +1087,10 @@ impl BedrockClient {
|
|||||||
&SSetLocalPlayerAsInitialized::read(reader)?,
|
&SSetLocalPlayerAsInitialized::read(reader)?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
SSetPlayerInventoryOptions::PACKET_ID => {
|
||||||
|
let _ = SSetPlayerInventoryOptions::read(reader)?;
|
||||||
|
// Ignore for now
|
||||||
|
}
|
||||||
SPlayerAction::PACKET_ID => {
|
SPlayerAction::PACKET_ID => {
|
||||||
self.handle_player_action(player, server, SPlayerAction::read(reader)?)
|
self.handle_player_action(player, server, SPlayerAction::read(reader)?)
|
||||||
.await;
|
.await;
|
||||||
@ -1210,20 +1261,9 @@ impl BedrockClient {
|
|||||||
{
|
{
|
||||||
if self.close_token.is_cancelled() {
|
if self.close_token.is_cancelled() {
|
||||||
None
|
None
|
||||||
} else if tokio::runtime::Handle::try_current().is_ok() {
|
|
||||||
Some(self.tasks.spawn(task))
|
|
||||||
} else {
|
} else {
|
||||||
warn!("No Tokio runtime in current thread; running task on dedicated runtime thread");
|
let _guard = self.rt_handle.enter();
|
||||||
std::thread::spawn(move || {
|
Some(self.tasks.spawn(task))
|
||||||
let rt = tokio::runtime::Builder::new_current_thread()
|
|
||||||
.enable_all()
|
|
||||||
.build()
|
|
||||||
.expect("Failed to build fallback runtime");
|
|
||||||
rt.block_on(async move {
|
|
||||||
let _ = task.await;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -213,7 +213,7 @@ impl Context {
|
|||||||
/// - `player`: The player for which the commands will be reloaded.
|
/// - `player`: The player for which the commands will be reloaded.
|
||||||
pub async fn reload_commands_for(&self, player: &Arc<Player>) {
|
pub async fn reload_commands_for(&self, player: &Arc<Player>) {
|
||||||
let command_dispatcher = self.server.command_dispatcher.read().await;
|
let command_dispatcher = self.server.command_dispatcher.read().await;
|
||||||
if let ClientPlatform::Bedrock(_) = &player.client {
|
if let ClientPlatform::Bedrock(_) = player.client.as_ref() {
|
||||||
client_suggestions::send_bedrock_commands_packet(
|
client_suggestions::send_bedrock_commands_packet(
|
||||||
player,
|
player,
|
||||||
&self.server,
|
&self.server,
|
||||||
|
|||||||
@ -27,7 +27,7 @@ impl ToFromWasmEvent for PacketReceivedEvent {
|
|||||||
.add_player(self.player.clone())
|
.add_player(self.player.clone())
|
||||||
.expect("failed to add player resource");
|
.expect("failed to add player resource");
|
||||||
|
|
||||||
let packet = match &self.player.client {
|
let packet = match self.player.client.as_ref() {
|
||||||
ClientPlatform::Java(client) => {
|
ClientPlatform::Java(client) => {
|
||||||
let version = client.version.load();
|
let version = client.version.load();
|
||||||
generated_packets::deserialize_java_serverbound_packet(
|
generated_packets::deserialize_java_serverbound_packet(
|
||||||
@ -75,7 +75,7 @@ impl ToFromWasmEvent for PacketSentEvent {
|
|||||||
.add_player(self.player.clone())
|
.add_player(self.player.clone())
|
||||||
.expect("failed to add player resource");
|
.expect("failed to add player resource");
|
||||||
|
|
||||||
let packet = match &self.player.client {
|
let packet = match self.player.client.as_ref() {
|
||||||
ClientPlatform::Java(_) => {
|
ClientPlatform::Java(_) => {
|
||||||
generated_packets::clientbound_java_any_to_wit(self.packet.as_ref())
|
generated_packets::clientbound_java_any_to_wit(self.packet.as_ref())
|
||||||
.map_or(ClientboundPacket::Unknown, ClientboundPacket::Java)
|
.map_or(ClientboundPacket::Unknown, ClientboundPacket::Java)
|
||||||
|
|||||||
@ -1084,7 +1084,7 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
|
|||||||
port: u16,
|
port: u16,
|
||||||
) -> wasmtime::Result<()> {
|
) -> wasmtime::Result<()> {
|
||||||
let player = player_from_resource(self, &player)?;
|
let player = player_from_resource(self, &player)?;
|
||||||
if let crate::net::ClientPlatform::Java(client) = &player.client {
|
if let crate::net::ClientPlatform::Java(client) = player.client.as_ref() {
|
||||||
client
|
client
|
||||||
.send_packet_now(&pumpkin_protocol::java::client::play::CTransfer::new(
|
.send_packet_now(&pumpkin_protocol::java::client::play::CTransfer::new(
|
||||||
&host,
|
&host,
|
||||||
@ -1425,7 +1425,7 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
|
|||||||
player: Resource<Player>,
|
player: Resource<Player>,
|
||||||
) -> wasmtime::Result<Option<Resource<pumpkin::plugin::player::JavaPlayer>>> {
|
) -> wasmtime::Result<Option<Resource<pumpkin::plugin::player::JavaPlayer>>> {
|
||||||
let player = player_from_resource(self, &player)?;
|
let player = player_from_resource(self, &player)?;
|
||||||
if let crate::net::ClientPlatform::Java(_) = player.client {
|
if let crate::net::ClientPlatform::Java(_) = player.client.as_ref() {
|
||||||
Ok(Some(self.add_java_player(player)?))
|
Ok(Some(self.add_java_player(player)?))
|
||||||
} else {
|
} else {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
@ -1437,7 +1437,7 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
|
|||||||
player: Resource<Player>,
|
player: Resource<Player>,
|
||||||
) -> wasmtime::Result<Option<Resource<pumpkin::plugin::player::BedrockPlayer>>> {
|
) -> wasmtime::Result<Option<Resource<pumpkin::plugin::player::BedrockPlayer>>> {
|
||||||
let player = player_from_resource(self, &player)?;
|
let player = player_from_resource(self, &player)?;
|
||||||
if let crate::net::ClientPlatform::Bedrock(_) = player.client {
|
if let crate::net::ClientPlatform::Bedrock(_) = player.client.as_ref() {
|
||||||
Ok(Some(self.add_bedrock_player(player)?))
|
Ok(Some(self.add_bedrock_player(player)?))
|
||||||
} else {
|
} else {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
@ -1607,7 +1607,7 @@ impl pumpkin::plugin::player::HostJavaPlayer for PluginHostState {
|
|||||||
.provider
|
.provider
|
||||||
.clone();
|
.clone();
|
||||||
|
|
||||||
if let crate::net::ClientPlatform::Java(_) = player.client {
|
if let crate::net::ClientPlatform::Java(_) = player.client.as_ref() {
|
||||||
player
|
player
|
||||||
.client
|
.client
|
||||||
.send_packet_now(&pumpkin_protocol::java::client::play::CCustomPayload::new(
|
.send_packet_now(&pumpkin_protocol::java::client::play::CCustomPayload::new(
|
||||||
@ -1757,7 +1757,7 @@ impl pumpkin::plugin::player::HostJavaPlayer for PluginHostState {
|
|||||||
external_title: dialog.external_title.as_ref().map(|t| text_component_from_resource(self, t)),
|
external_title: dialog.external_title.as_ref().map(|t| text_component_from_resource(self, t)),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let crate::net::ClientPlatform::Java(client) = &player.client {
|
if let crate::net::ClientPlatform::Java(client) = player.client.as_ref() {
|
||||||
match client.connection_state.load() {
|
match client.connection_state.load() {
|
||||||
pumpkin_protocol::ConnectionState::Config => {
|
pumpkin_protocol::ConnectionState::Config => {
|
||||||
client
|
client
|
||||||
@ -1797,7 +1797,7 @@ impl pumpkin::plugin::player::HostJavaPlayer for PluginHostState {
|
|||||||
.provider
|
.provider
|
||||||
.clone();
|
.clone();
|
||||||
|
|
||||||
if let crate::net::ClientPlatform::Java(client) = &player.client {
|
if let crate::net::ClientPlatform::Java(client) = player.client.as_ref() {
|
||||||
match client.connection_state.load() {
|
match client.connection_state.load() {
|
||||||
pumpkin_protocol::ConnectionState::Config => {
|
pumpkin_protocol::ConnectionState::Config => {
|
||||||
client
|
client
|
||||||
@ -1847,7 +1847,7 @@ impl pumpkin::plugin::player::HostBedrockPlayer for PluginHostState {
|
|||||||
.provider
|
.provider
|
||||||
.clone();
|
.clone();
|
||||||
|
|
||||||
if let crate::net::ClientPlatform::Bedrock(client) = &player.client {
|
if let crate::net::ClientPlatform::Bedrock(client) = player.client.as_ref() {
|
||||||
Ok(to_wasm_bedrock_version(client.version.load()))
|
Ok(to_wasm_bedrock_version(client.version.load()))
|
||||||
} else {
|
} else {
|
||||||
Ok(pumpkin::plugin::player::BedrockMinecraftVersion::Unknown)
|
Ok(pumpkin::plugin::player::BedrockMinecraftVersion::Unknown)
|
||||||
@ -2048,7 +2048,7 @@ impl pumpkin::plugin::player::HostBedrockPlayer for PluginHostState {
|
|||||||
tick: VarULong(0),
|
tick: VarULong(0),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let crate::net::ClientPlatform::Bedrock(client) = &player.client {
|
if let crate::net::ClientPlatform::Bedrock(client) = player.client.as_ref() {
|
||||||
client.send_game_packet(&packet).await;
|
client.send_game_packet(&packet).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2068,7 +2068,7 @@ impl pumpkin::plugin::player::HostBedrockPlayer for PluginHostState {
|
|||||||
.provider
|
.provider
|
||||||
.clone();
|
.clone();
|
||||||
|
|
||||||
if let crate::net::ClientPlatform::Bedrock(client) = &player.client {
|
if let crate::net::ClientPlatform::Bedrock(client) = player.client.as_ref() {
|
||||||
let data = client.client_data.load();
|
let data = client.client_data.load();
|
||||||
(**data).as_ref().map_or_else(
|
(**data).as_ref().map_or_else(
|
||||||
|| Err(wasmtime::Error::msg("client data not available")),
|
|| Err(wasmtime::Error::msg("client data not available")),
|
||||||
@ -2140,7 +2140,7 @@ impl pumpkin::plugin::player::HostBedrockPlayer for PluginHostState {
|
|||||||
.provider
|
.provider
|
||||||
.clone();
|
.clone();
|
||||||
|
|
||||||
if let crate::net::ClientPlatform::Bedrock(client) = &player.client {
|
if let crate::net::ClientPlatform::Bedrock(client) = player.client.as_ref() {
|
||||||
let form_id = client.next_form_id.fetch_add(1, Ordering::Relaxed);
|
let form_id = client.next_form_id.fetch_add(1, Ordering::Relaxed);
|
||||||
|
|
||||||
let locale_str = player.config.load().locale.clone();
|
let locale_str = player.config.load().locale.clone();
|
||||||
|
|||||||
@ -454,7 +454,7 @@ impl Server {
|
|||||||
/// You still have to spawn the `Player` in a `World` to let them join and make them visible.
|
/// You still have to spawn the `Player` in a `World` to let them join and make them visible.
|
||||||
pub async fn add_player(
|
pub async fn add_player(
|
||||||
&self,
|
&self,
|
||||||
client: ClientPlatform,
|
client: Arc<ClientPlatform>,
|
||||||
profile: GameProfile,
|
profile: GameProfile,
|
||||||
config: Option<PlayerConfig>,
|
config: Option<PlayerConfig>,
|
||||||
) -> Option<(Arc<Player>, Arc<World>)> {
|
) -> Option<(Arc<Player>, Arc<World>)> {
|
||||||
|
|||||||
@ -94,7 +94,7 @@ impl Bossbar {
|
|||||||
/// Extra methods for [`Player`] to send and manage the bossbar.
|
/// Extra methods for [`Player`] to send and manage the bossbar.
|
||||||
impl Player {
|
impl Player {
|
||||||
pub async fn send_bossbar(&self, bossbar: &Bossbar) {
|
pub async fn send_bossbar(&self, bossbar: &Bossbar) {
|
||||||
match &self.client {
|
match self.client.as_ref() {
|
||||||
ClientPlatform::Java(java) => {
|
ClientPlatform::Java(java) => {
|
||||||
let boss_action = BosseventAction::Add {
|
let boss_action = BosseventAction::Add {
|
||||||
title: bossbar.title.clone(),
|
title: bossbar.title.clone(),
|
||||||
@ -123,7 +123,7 @@ impl Player {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn remove_bossbar(&self, uuid: Uuid) {
|
pub async fn remove_bossbar(&self, uuid: Uuid) {
|
||||||
match &self.client {
|
match self.client.as_ref() {
|
||||||
ClientPlatform::Java(java) => {
|
ClientPlatform::Java(java) => {
|
||||||
let boss_action = BosseventAction::Remove;
|
let boss_action = BosseventAction::Remove;
|
||||||
|
|
||||||
@ -141,7 +141,7 @@ impl Player {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update_bossbar_health(&self, uuid: &Uuid, health: f32) {
|
pub async fn update_bossbar_health(&self, uuid: &Uuid, health: f32) {
|
||||||
match &self.client {
|
match self.client.as_ref() {
|
||||||
ClientPlatform::Java(java) => {
|
ClientPlatform::Java(java) => {
|
||||||
let boss_action = BosseventAction::UpdateHealth(health);
|
let boss_action = BosseventAction::UpdateHealth(health);
|
||||||
|
|
||||||
@ -159,7 +159,7 @@ impl Player {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update_bossbar_title(&self, uuid: &Uuid, title: TextComponent) {
|
pub async fn update_bossbar_title(&self, uuid: &Uuid, title: TextComponent) {
|
||||||
match &self.client {
|
match self.client.as_ref() {
|
||||||
ClientPlatform::Java(java) => {
|
ClientPlatform::Java(java) => {
|
||||||
let boss_action = BosseventAction::UpdateTile(title);
|
let boss_action = BosseventAction::UpdateTile(title);
|
||||||
|
|
||||||
@ -183,7 +183,7 @@ impl Player {
|
|||||||
dividers: BossbarDivisions,
|
dividers: BossbarDivisions,
|
||||||
_flags: BossbarFlags,
|
_flags: BossbarFlags,
|
||||||
) {
|
) {
|
||||||
match &self.client {
|
match self.client.as_ref() {
|
||||||
ClientPlatform::Java(java) => {
|
ClientPlatform::Java(java) => {
|
||||||
let boss_action = BosseventAction::UpdateStyle {
|
let boss_action = BosseventAction::UpdateStyle {
|
||||||
color: (color as u8).into(),
|
color: (color as u8).into(),
|
||||||
@ -207,7 +207,7 @@ impl Player {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update_bossbar_flags(&self, uuid: &Uuid, flags: BossbarFlags) {
|
pub async fn update_bossbar_flags(&self, uuid: &Uuid, flags: BossbarFlags) {
|
||||||
match &self.client {
|
match self.client.as_ref() {
|
||||||
ClientPlatform::Java(java) => {
|
ClientPlatform::Java(java) => {
|
||||||
let boss_action = BosseventAction::UpdateFlags(flags.bits());
|
let boss_action = BosseventAction::UpdateFlags(flags.bits());
|
||||||
|
|
||||||
|
|||||||
@ -49,7 +49,7 @@ pub async fn update_position(player: &Arc<Player>) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
match &player.client {
|
match player.client.as_ref() {
|
||||||
ClientPlatform::Java(java_client) => {
|
ClientPlatform::Java(java_client) => {
|
||||||
java_client
|
java_client
|
||||||
.send_packet_now(&CCenterChunk {
|
.send_packet_now(&CCenterChunk {
|
||||||
@ -93,7 +93,7 @@ pub async fn update_position(player: &Arc<Player>) {
|
|||||||
|
|
||||||
player.watched_section.store(new_cylindrical);
|
player.watched_section.store(new_cylindrical);
|
||||||
|
|
||||||
if let ClientPlatform::Java(_) = &player.client {
|
if let ClientPlatform::Java(_) = player.client.as_ref() {
|
||||||
for chunk in &unloading_chunks {
|
for chunk in &unloading_chunks {
|
||||||
player
|
player
|
||||||
.client
|
.client
|
||||||
|
|||||||
@ -143,7 +143,6 @@ use rand::{RngExt, rng};
|
|||||||
use scoreboard::Scoreboard;
|
use scoreboard::Scoreboard;
|
||||||
use time::LevelTime;
|
use time::LevelTime;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use tokio::task::JoinSet;
|
|
||||||
|
|
||||||
pub mod border;
|
pub mod border;
|
||||||
pub mod bossbar;
|
pub mod bossbar;
|
||||||
@ -507,7 +506,7 @@ impl World {
|
|||||||
let mut recipients_by_version: BTreeMap<JavaMinecraftVersion, Vec<&'a JavaClient>> =
|
let mut recipients_by_version: BTreeMap<JavaMinecraftVersion, Vec<&'a JavaClient>> =
|
||||||
BTreeMap::new();
|
BTreeMap::new();
|
||||||
for player in players {
|
for player in players {
|
||||||
if let ClientPlatform::Java(java_client) = &player.client {
|
if let ClientPlatform::Java(java_client) = player.client.as_ref() {
|
||||||
recipients_by_version
|
recipients_by_version
|
||||||
.entry(java_client.version.load())
|
.entry(java_client.version.load())
|
||||||
.or_default()
|
.or_default()
|
||||||
@ -556,7 +555,7 @@ impl World {
|
|||||||
pub fn broadcast_packet_all_sync<P: ClientPacket>(&self, packet: &P) {
|
pub fn broadcast_packet_all_sync<P: ClientPacket>(&self, packet: &P) {
|
||||||
let players = self.players.load();
|
let players = self.players.load();
|
||||||
for player in players.iter() {
|
for player in players.iter() {
|
||||||
match &player.client {
|
match player.client.as_ref() {
|
||||||
ClientPlatform::Java(java) => {
|
ClientPlatform::Java(java) => {
|
||||||
if let Ok(data) =
|
if let Ok(data) =
|
||||||
JavaClient::serialize_packet_for_version(packet, java.version.load())
|
JavaClient::serialize_packet_for_version(packet, java.version.load())
|
||||||
@ -624,7 +623,7 @@ impl World {
|
|||||||
let mut be_recipients = Vec::new();
|
let mut be_recipients = Vec::new();
|
||||||
|
|
||||||
for player in players.iter() {
|
for player in players.iter() {
|
||||||
if let ClientPlatform::Bedrock(be_client) = &player.client {
|
if let ClientPlatform::Bedrock(be_client) = player.client.as_ref() {
|
||||||
be_recipients.push(be_client.clone());
|
be_recipients.push(be_client.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -700,7 +699,7 @@ impl World {
|
|||||||
if except.contains(&p.gameprofile.id) {
|
if except.contains(&p.gameprofile.id) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
match &p.client {
|
match p.client.as_ref() {
|
||||||
ClientPlatform::Java(_) => java_recipients.push(p),
|
ClientPlatform::Java(_) => java_recipients.push(p),
|
||||||
ClientPlatform::Bedrock(be_client) => be_client.try_enqueue_packet(be_packet),
|
ClientPlatform::Bedrock(be_client) => be_client.try_enqueue_packet(be_packet),
|
||||||
}
|
}
|
||||||
@ -725,7 +724,7 @@ impl World {
|
|||||||
if except.contains(&p.gameprofile.id) {
|
if except.contains(&p.gameprofile.id) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
match &p.client {
|
match p.client.as_ref() {
|
||||||
ClientPlatform::Java(_) => java_recipients.push(p),
|
ClientPlatform::Java(_) => java_recipients.push(p),
|
||||||
ClientPlatform::Bedrock(be_client) => bedrock_recipients.push(be_client.clone()),
|
ClientPlatform::Bedrock(be_client) => bedrock_recipients.push(be_client.clone()),
|
||||||
}
|
}
|
||||||
@ -897,79 +896,91 @@ impl World {
|
|||||||
pub async fn tick(self: &Arc<Self>, server: Arc<Server>) {
|
pub async fn tick(self: &Arc<Self>, server: Arc<Server>) {
|
||||||
let start = tokio::time::Instant::now();
|
let start = tokio::time::Instant::now();
|
||||||
|
|
||||||
// IMPORTANT: send flush_block_updates first to prevent issues with CAcknowledgeBlockChange
|
|
||||||
self.flush_block_updates().await;
|
self.flush_block_updates().await;
|
||||||
self.flush_synced_block_events().await;
|
self.flush_synced_block_events().await;
|
||||||
self.update_active_chunks();
|
self.update_active_chunks();
|
||||||
self.tick_environment().await;
|
self.tick_environment().await;
|
||||||
|
|
||||||
let chunk_start = tokio::time::Instant::now();
|
let world_for_chunks = self.clone();
|
||||||
self.tick_chunks().await;
|
let chunk_future = async move {
|
||||||
let chunk_elapsed = chunk_start.elapsed();
|
let t = tokio::time::Instant::now();
|
||||||
|
world_for_chunks.tick_chunks().await;
|
||||||
|
t.elapsed()
|
||||||
|
};
|
||||||
|
|
||||||
let player_start = tokio::time::Instant::now();
|
let players = self.players.load();
|
||||||
let players = self.players.load().clone();
|
|
||||||
let player_count = players.len();
|
let player_count = players.len();
|
||||||
|
let players_cache = Arc::new(
|
||||||
|
players
|
||||||
|
.iter()
|
||||||
|
.map(|player| {
|
||||||
|
let entity = player.get_entity();
|
||||||
|
let pos = entity.pos.load();
|
||||||
|
let bb = entity.bounding_box.load().expand(1.0, 0.5, 1.0);
|
||||||
|
(player.clone(), pos, bb)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
);
|
||||||
|
|
||||||
let mut player_tasks = tokio::task::JoinSet::new();
|
let server_for_players = server.clone();
|
||||||
|
let player_future = async move {
|
||||||
|
let t = tokio::time::Instant::now();
|
||||||
|
let mut tasks = tokio::task::JoinSet::new();
|
||||||
for player in players.iter() {
|
for player in players.iter() {
|
||||||
let player_clone = player.clone();
|
let p_clone = player.clone();
|
||||||
let server_clone = server.clone();
|
let s_clone = server_for_players.clone();
|
||||||
player_tasks.spawn(async move {
|
tasks.spawn(async move {
|
||||||
player_clone.tick(&server_clone).await;
|
p_clone.tick(&s_clone).await;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
while let Some(res) = player_tasks.join_next().await {
|
while let Some(res) = tasks.join_next().await {
|
||||||
if let Err(e) = res {
|
if let Err(e) = res {
|
||||||
error!("Player tick panicked: {:?}", e);
|
error!("Player tick panicked: {:?}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let player_elapsed = player_start.elapsed();
|
t.elapsed()
|
||||||
|
};
|
||||||
|
|
||||||
let entity_start = tokio::time::Instant::now();
|
let entities_to_tick = self.entities.load();
|
||||||
let entities_to_tick = self.entities.load().clone();
|
|
||||||
let entity_count = entities_to_tick.len();
|
let entity_count = entities_to_tick.len();
|
||||||
|
let server_for_entities = server.clone();
|
||||||
|
|
||||||
let mut entity_tasks = tokio::task::JoinSet::new();
|
let entity_future = async move {
|
||||||
|
let t = tokio::time::Instant::now();
|
||||||
|
let mut tasks = tokio::task::JoinSet::new();
|
||||||
for entity in entities_to_tick.iter() {
|
for entity in entities_to_tick.iter() {
|
||||||
let entity_clone = entity.clone();
|
let e_clone = entity.clone();
|
||||||
let server_clone = server.clone();
|
let s_clone = server_for_entities.clone();
|
||||||
let players_clone = players.clone();
|
let p_cache = players_cache.clone();
|
||||||
entity_tasks.spawn(async move {
|
|
||||||
entity_clone.get_entity().age.fetch_add(1, Relaxed);
|
|
||||||
entity_clone.tick(&entity_clone, &server_clone).await;
|
|
||||||
|
|
||||||
let entity_inner = entity_clone.get_entity();
|
tasks.spawn(async move {
|
||||||
|
e_clone.get_entity().age.fetch_add(1, Relaxed);
|
||||||
|
e_clone.tick(&e_clone, &s_clone).await;
|
||||||
|
|
||||||
|
let entity_inner = e_clone.get_entity();
|
||||||
|
let entity_pos = entity_inner.pos.load();
|
||||||
let entity_bb = entity_inner.bounding_box.load();
|
let entity_bb = entity_inner.bounding_box.load();
|
||||||
|
|
||||||
for player in players_clone.iter() {
|
for (player, player_pos, player_bb) in p_cache.iter() {
|
||||||
let player_pos = player.get_entity().pos.load();
|
|
||||||
let entity_pos = entity_inner.pos.load();
|
|
||||||
|
|
||||||
if (player_pos.x - entity_pos.x).abs() < 5.0
|
if (player_pos.x - entity_pos.x).abs() < 5.0
|
||||||
&& (player_pos.y - entity_pos.y).abs() < 5.0
|
&& (player_pos.y - entity_pos.y).abs() < 5.0
|
||||||
&& (player_pos.z - entity_pos.z).abs() < 5.0
|
&& (player_pos.z - entity_pos.z).abs() < 5.0
|
||||||
&& player
|
&& player_bb.intersects(&entity_bb)
|
||||||
.get_entity()
|
|
||||||
.bounding_box
|
|
||||||
.load()
|
|
||||||
.expand(1.0, 0.5, 1.0)
|
|
||||||
.intersects(&entity_bb)
|
|
||||||
{
|
{
|
||||||
entity_clone.on_player_collision(player).await;
|
e_clone.on_player_collision(player).await;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
while let Some(res) = entity_tasks.join_next().await {
|
while let Some(res) = tasks.join_next().await {
|
||||||
if let Err(e) = res {
|
if let Err(e) = res {
|
||||||
error!("Entity tick panicked: {:?}", e);
|
error!("Entity tick panicked: {:?}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let entity_elapsed = entity_start.elapsed();
|
t.elapsed()
|
||||||
|
};
|
||||||
|
|
||||||
let block_entity_start = tokio::time::Instant::now();
|
|
||||||
let active_chunks = self.active_chunks.load();
|
let active_chunks = self.active_chunks.load();
|
||||||
let block_entities: Vec<Arc<dyn BlockEntity>> = self
|
let block_entities: Vec<Arc<dyn BlockEntity>> = self
|
||||||
.block_entities
|
.block_entities
|
||||||
@ -979,23 +990,34 @@ impl World {
|
|||||||
.collect();
|
.collect();
|
||||||
let block_entity_count = block_entities.len();
|
let block_entity_count = block_entities.len();
|
||||||
|
|
||||||
let mut block_entity_tasks = tokio::task::JoinSet::new();
|
let world_for_be = self.clone();
|
||||||
for block_entity in block_entities {
|
let block_entity_future = async move {
|
||||||
let world_clone = self.clone();
|
let t = tokio::time::Instant::now();
|
||||||
block_entity_tasks.spawn(async move {
|
let mut tasks = tokio::task::JoinSet::new();
|
||||||
block_entity.tick(&world_clone).await;
|
for be in block_entities {
|
||||||
|
let be_clone = be.clone();
|
||||||
|
let w_clone = world_for_be.clone();
|
||||||
|
tasks.spawn(async move {
|
||||||
|
be_clone.tick(&w_clone).await;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
while let Some(res) = block_entity_tasks.join_next().await {
|
while let Some(res) = tasks.join_next().await {
|
||||||
if let Err(e) = res {
|
if let Err(e) = res {
|
||||||
error!("Block entity tick panicked: {:?}", e);
|
error!("Block entity panicked: {:?}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let block_entity_elapsed = block_entity_start.elapsed();
|
t.elapsed()
|
||||||
|
};
|
||||||
|
|
||||||
|
let (chunk_elapsed, player_elapsed, entity_elapsed, block_entity_elapsed) = tokio::join!(
|
||||||
|
chunk_future,
|
||||||
|
player_future,
|
||||||
|
entity_future,
|
||||||
|
block_entity_future
|
||||||
|
);
|
||||||
|
|
||||||
self.level.chunk_loading.lock().unwrap().send_change();
|
self.level.chunk_loading.lock().unwrap().send_change();
|
||||||
|
|
||||||
// Tick the End dragon fight (only on THE_END worlds).
|
|
||||||
if let Some(ref fight_mutex) = self.dragon_fight {
|
if let Some(ref fight_mutex) = self.dragon_fight {
|
||||||
dragon_fight::DragonFight::tick(fight_mutex, self).await;
|
dragon_fight::DragonFight::tick(fight_mutex, self).await;
|
||||||
}
|
}
|
||||||
@ -1069,7 +1091,7 @@ impl World {
|
|||||||
});
|
});
|
||||||
|
|
||||||
for p in recipients {
|
for p in recipients {
|
||||||
match &p.client {
|
match p.client.as_ref() {
|
||||||
ClientPlatform::Java(_) => java_recipients.push(p),
|
ClientPlatform::Java(_) => java_recipients.push(p),
|
||||||
ClientPlatform::Bedrock(be_client) => {
|
ClientPlatform::Bedrock(be_client) => {
|
||||||
for (block_pos, block_state_id) in &updates {
|
for (block_pos, block_state_id) in &updates {
|
||||||
@ -1161,65 +1183,84 @@ impl World {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[expect(clippy::too_many_lines)]
|
||||||
pub async fn tick_chunks(self: &Arc<Self>) {
|
pub async fn tick_chunks(self: &Arc<Self>) {
|
||||||
let active_chunks = self.active_chunks.load();
|
let active_chunks = self.active_chunks.load();
|
||||||
let tick_data = self.level.get_tick_data(&active_chunks);
|
let tick_data = self.level.get_tick_data(&active_chunks);
|
||||||
|
|
||||||
|
// ONE JoinSet for all chunk operations
|
||||||
|
let mut chunk_tasks = tokio::task::JoinSet::new();
|
||||||
|
|
||||||
|
// 1. Spawn Block Ticks
|
||||||
for scheduled_tick in tick_data.block_ticks {
|
for scheduled_tick in tick_data.block_ticks {
|
||||||
let block = self.get_block(&scheduled_tick.position);
|
let world = self.clone();
|
||||||
if let Some(pumpkin_block) = self.block_registry.get_pumpkin_block(block.id) {
|
let pos = scheduled_tick.position; // Clone for the move closure
|
||||||
|
chunk_tasks.spawn(async move {
|
||||||
|
let block = world.get_block(&pos);
|
||||||
|
if let Some(pumpkin_block) = world.block_registry.get_pumpkin_block(block.id) {
|
||||||
pumpkin_block
|
pumpkin_block
|
||||||
.on_scheduled_tick(OnScheduledTickArgs {
|
.on_scheduled_tick(OnScheduledTickArgs {
|
||||||
world: self,
|
world: &world,
|
||||||
block,
|
block,
|
||||||
position: &scheduled_tick.position,
|
position: &pos,
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
for scheduled_tick in tick_data.fluid_ticks {
|
|
||||||
let fluid = self.get_fluid(&scheduled_tick.position);
|
|
||||||
if let Some(pumpkin_fluid) = self.block_registry.get_pumpkin_fluid(fluid.id) {
|
|
||||||
pumpkin_fluid
|
|
||||||
.on_scheduled_tick(self, fluid, &scheduled_tick.position)
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for scheduled_tick in tick_data.random_ticks {
|
// 2. Spawn Fluid Ticks
|
||||||
let (block, fluid) = match (scheduled_tick.tick_block, scheduled_tick.tick_fluid) {
|
for scheduled_tick in tick_data.fluid_ticks {
|
||||||
(true, true) => {
|
let world = self.clone();
|
||||||
let (block, fluid) = self.get_block_and_fluid(&scheduled_tick.position);
|
let pos = scheduled_tick.position;
|
||||||
(Some(block), Some(fluid))
|
chunk_tasks.spawn(async move {
|
||||||
|
let fluid = world.get_fluid(&pos);
|
||||||
|
if let Some(pumpkin_fluid) = world.block_registry.get_pumpkin_fluid(fluid.id) {
|
||||||
|
pumpkin_fluid.on_scheduled_tick(&world, fluid, &pos).await;
|
||||||
}
|
}
|
||||||
(true, false) => (Some(self.get_block(&scheduled_tick.position)), None),
|
});
|
||||||
(false, true) => (None, Some(self.get_fluid(&scheduled_tick.position))),
|
}
|
||||||
|
|
||||||
|
// 3. Spawn Random Ticks
|
||||||
|
for scheduled_tick in tick_data.random_ticks {
|
||||||
|
let world = self.clone();
|
||||||
|
let pos = scheduled_tick.position;
|
||||||
|
let tick_block = scheduled_tick.tick_block;
|
||||||
|
let tick_fluid = scheduled_tick.tick_fluid;
|
||||||
|
|
||||||
|
chunk_tasks.spawn(async move {
|
||||||
|
let (block, fluid) = match (tick_block, tick_fluid) {
|
||||||
|
(true, true) => {
|
||||||
|
let (b, f) = world.get_block_and_fluid(&pos);
|
||||||
|
(Some(b), Some(f))
|
||||||
|
}
|
||||||
|
(true, false) => (Some(world.get_block(&pos)), None),
|
||||||
|
(false, true) => (None, Some(world.get_fluid(&pos))),
|
||||||
(false, false) => (None, None),
|
(false, false) => (None, None),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(block) = block
|
if let Some(block) = block
|
||||||
&& let Some(pumpkin_block) = self.block_registry.get_pumpkin_block(block.id)
|
&& let Some(pumpkin_block) = world.block_registry.get_pumpkin_block(block.id)
|
||||||
{
|
{
|
||||||
pumpkin_block
|
pumpkin_block
|
||||||
.random_tick(RandomTickArgs {
|
.random_tick(RandomTickArgs {
|
||||||
world: self,
|
world: &world,
|
||||||
block,
|
block,
|
||||||
position: &scheduled_tick.position,
|
position: &pos,
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(fluid) = fluid
|
if let Some(fluid) = fluid
|
||||||
&& let Some(pumpkin_fluid) = self.block_registry.get_pumpkin_fluid(fluid.id)
|
&& let Some(pumpkin_fluid) = world.block_registry.get_pumpkin_fluid(fluid.id)
|
||||||
{
|
{
|
||||||
pumpkin_fluid
|
pumpkin_fluid.random_tick(fluid, &world, &pos).await;
|
||||||
.random_tick(fluid, self, &scheduled_tick.position)
|
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 4. Calculate Spawn List (Sequential setup)
|
||||||
let spawn_state = self.spawn_state.load();
|
let spawn_state = self.spawn_state.load();
|
||||||
|
|
||||||
// TODO gamerule this.spawnEnemies || this.spawnFriendlies
|
|
||||||
let (spawn_mobs, spawn_monsters, peaceful) = {
|
let (spawn_mobs, spawn_monsters, peaceful) = {
|
||||||
let lock = self.level_info.load();
|
let lock = self.level_info.load();
|
||||||
(
|
(
|
||||||
@ -1231,18 +1272,16 @@ impl World {
|
|||||||
let spawn_passives = self.level_time.lock().await.time_of_day % 400 == 0;
|
let spawn_passives = self.level_time.lock().await.time_of_day % 400 == 0;
|
||||||
let spawn_enemies = !peaceful && spawn_monsters && spawn_mobs;
|
let spawn_enemies = !peaceful && spawn_monsters && spawn_mobs;
|
||||||
let spawn_passives = spawn_passives && spawn_mobs;
|
let spawn_passives = spawn_passives && spawn_mobs;
|
||||||
let spawn_list: Vec<&'static MobCategory> =
|
|
||||||
natural_spawner::get_filtered_spawning_categories(
|
let spawn_list = Arc::new(natural_spawner::get_filtered_spawning_categories(
|
||||||
&spawn_state,
|
&spawn_state,
|
||||||
spawn_mobs,
|
spawn_mobs,
|
||||||
spawn_enemies,
|
spawn_enemies,
|
||||||
spawn_passives,
|
spawn_passives,
|
||||||
);
|
));
|
||||||
|
|
||||||
if spawn_list.is_empty() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// 5. Spawn Chunk Spawners into the SAME JoinSet
|
||||||
|
if !spawn_list.is_empty() {
|
||||||
let mut spawning_chunks = Vec::new();
|
let mut spawning_chunks = Vec::new();
|
||||||
for pos in active_chunks.iter() {
|
for pos in active_chunks.iter() {
|
||||||
if let Some(chunk) = self.level.read_chunk_sync(pos, std::clone::Clone::clone) {
|
if let Some(chunk) = self.level.read_chunk_sync(pos, std::clone::Clone::clone) {
|
||||||
@ -1250,24 +1289,26 @@ impl World {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// log::debug!("spawning list size {}", spawn_list.len());
|
|
||||||
spawning_chunks.shuffle(&mut rng());
|
spawning_chunks.shuffle(&mut rng());
|
||||||
|
|
||||||
let mut spawn_tasks = JoinSet::new();
|
|
||||||
let spawn_list = Arc::new(spawn_list);
|
|
||||||
|
|
||||||
for (pos, chunk) in spawning_chunks {
|
for (pos, chunk) in spawning_chunks {
|
||||||
let world = self.clone();
|
let world = self.clone();
|
||||||
let spawn_list = spawn_list.clone();
|
let s_list = spawn_list.clone();
|
||||||
let spawn_state = spawn_state.clone();
|
let s_state = spawn_state.clone();
|
||||||
let chunk = chunk.clone();
|
|
||||||
spawn_tasks.spawn(async move {
|
chunk_tasks.spawn(async move {
|
||||||
world
|
world
|
||||||
.tick_spawning_chunk(pos, &chunk, &spawn_list, &spawn_state)
|
.tick_spawning_chunk(pos, &chunk, &s_list, &s_state)
|
||||||
.await;
|
.await;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
while spawn_tasks.join_next().await.is_some() {}
|
}
|
||||||
|
|
||||||
|
while let Some(res) = chunk_tasks.join_next().await {
|
||||||
|
if let Err(e) = res {
|
||||||
|
error!("Chunk task panicked: {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_fluid_collisions(self: &Arc<Self>, bounding_box: BoundingBox) -> Vec<&Fluid> {
|
pub fn get_fluid_collisions(self: &Arc<Self>, bounding_box: BoundingBox) -> Vec<&Fluid> {
|
||||||
@ -2894,7 +2935,7 @@ impl World {
|
|||||||
player.send_active_effects().await;
|
player.send_active_effects().await;
|
||||||
self.send_player_equipment(player).await;
|
self.send_player_equipment(player).await;
|
||||||
|
|
||||||
if let crate::net::ClientPlatform::Java(java_client) = &player.client
|
if let crate::net::ClientPlatform::Java(java_client) = player.client.as_ref()
|
||||||
&& server.advanced_config.recipe.send_recipes
|
&& server.advanced_config.recipe.send_recipes
|
||||||
{
|
{
|
||||||
java_client
|
java_client
|
||||||
@ -2956,7 +2997,7 @@ impl World {
|
|||||||
yaw: f32,
|
yaw: f32,
|
||||||
pitch: f32,
|
pitch: f32,
|
||||||
) {
|
) {
|
||||||
if let ClientPlatform::Java(client) = &player.client {
|
if let ClientPlatform::Java(client) = player.client.as_ref() {
|
||||||
self.worldborder.lock().await.init_client(client).await;
|
self.worldborder.lock().await.init_client(client).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3003,7 +3044,7 @@ impl World {
|
|||||||
};
|
};
|
||||||
for player in self.players.load().iter() {
|
for player in self.players.load().iter() {
|
||||||
let mut sound_id = Sound::EntityGenericExplode as u16;
|
let mut sound_id = Sound::EntityGenericExplode as u16;
|
||||||
if let ClientPlatform::Java(java_client) = &player.client {
|
if let ClientPlatform::Java(java_client) = player.client.as_ref() {
|
||||||
sound_id = remap_sound_id_for_version(sound_id, java_client.version.load());
|
sound_id = remap_sound_id_for_version(sound_id, java_client.version.load());
|
||||||
}
|
}
|
||||||
let sound = IdOr::<SoundEvent>::Id(sound_id);
|
let sound = IdOr::<SoundEvent>::Id(sound_id);
|
||||||
@ -3228,7 +3269,7 @@ impl World {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
// Ensure at least the center chunk is sent synchronously before teleport.
|
// Ensure at least the center chunk is sent synchronously before teleport.
|
||||||
if let crate::net::ClientPlatform::Java(java_client) = &player.client {
|
if let crate::net::ClientPlatform::Java(java_client) = player.client.as_ref() {
|
||||||
let center_chunk = player.get_entity().chunk_pos.load();
|
let center_chunk = player.get_entity().chunk_pos.load();
|
||||||
let chunk = target_world
|
let chunk = target_world
|
||||||
.level
|
.level
|
||||||
@ -5051,7 +5092,7 @@ impl World {
|
|||||||
});
|
});
|
||||||
|
|
||||||
for p in recipients {
|
for p in recipients {
|
||||||
match &p.client {
|
match p.client.as_ref() {
|
||||||
ClientPlatform::Java(_) => java_recipients.push(p),
|
ClientPlatform::Java(_) => java_recipients.push(p),
|
||||||
ClientPlatform::Bedrock(be_client) => be_client.try_enqueue_packet(be_packet),
|
ClientPlatform::Bedrock(be_client) => be_client.try_enqueue_packet(be_packet),
|
||||||
}
|
}
|
||||||
@ -5107,7 +5148,7 @@ impl World {
|
|||||||
let mut bedrock_recipients = Vec::new();
|
let mut bedrock_recipients = Vec::new();
|
||||||
|
|
||||||
for p in recipients {
|
for p in recipients {
|
||||||
match &p.client {
|
match p.client.as_ref() {
|
||||||
ClientPlatform::Java(_) => java_recipients.push(p),
|
ClientPlatform::Java(_) => java_recipients.push(p),
|
||||||
ClientPlatform::Bedrock(be_client) => bedrock_recipients.push(be_client.clone()),
|
ClientPlatform::Bedrock(be_client) => bedrock_recipients.push(be_client.clone()),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,11 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use pumpkin_data::Block;
|
||||||
use pumpkin_data::block_properties::HorizontalAxis;
|
use pumpkin_data::block_properties::HorizontalAxis;
|
||||||
|
use pumpkin_data::dimension::Dimension;
|
||||||
use pumpkin_util::math::position::BlockPos;
|
use pumpkin_util::math::position::BlockPos;
|
||||||
|
use pumpkin_util::math::vector3::Vector3;
|
||||||
|
use pumpkin_world::world::BlockFlags;
|
||||||
|
|
||||||
use super::World;
|
use super::World;
|
||||||
|
|
||||||
@ -31,23 +35,215 @@ impl From<&PortalSearchResult> for SourcePortalInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PortalManager {
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
pub portal_delay: u32,
|
pub enum PortalType {
|
||||||
pub portal_world: Arc<World>,
|
Nether,
|
||||||
pub pos: BlockPos,
|
End,
|
||||||
pub ticks_in_portal: u32,
|
}
|
||||||
pub in_portal: bool,
|
|
||||||
|
impl PortalType {
|
||||||
|
pub fn get_portal_transition_time(
|
||||||
|
&self,
|
||||||
|
current_world: &World,
|
||||||
|
entity: &dyn crate::entity::EntityBase,
|
||||||
|
) -> u32 {
|
||||||
|
match self {
|
||||||
|
Self::End => 0,
|
||||||
|
Self::Nether => {
|
||||||
|
let entity_type = entity.get_entity().entity_type;
|
||||||
|
let level_info = current_world.level_info.load();
|
||||||
|
match entity_type.id {
|
||||||
|
id if id == pumpkin_data::entity::EntityType::PLAYER.id => (current_world
|
||||||
|
.get_player_by_id(entity.get_entity().entity_id))
|
||||||
|
.map_or(80, |player| match player.gamemode.load() {
|
||||||
|
pumpkin_util::GameMode::Creative => {
|
||||||
|
level_info.game_rules.players_nether_portal_creative_delay as u32
|
||||||
|
}
|
||||||
|
_ => level_info.game_rules.players_nether_portal_default_delay as u32,
|
||||||
|
}),
|
||||||
|
_ => 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[expect(clippy::too_many_lines)]
|
||||||
|
pub async fn get_portal_destination(
|
||||||
|
&self,
|
||||||
|
current_level: &World,
|
||||||
|
dest_world: Arc<World>,
|
||||||
|
caller: &Arc<dyn crate::entity::EntityBase>,
|
||||||
|
_portal_entry_pos: BlockPos,
|
||||||
|
source_portal: Option<SourcePortalInfo>,
|
||||||
|
) -> Option<TeleportTransition> {
|
||||||
|
match self {
|
||||||
|
Self::End => {
|
||||||
|
let is_end_portal = dest_world.dimension == Dimension::THE_END
|
||||||
|
|| current_level.dimension == Dimension::THE_END;
|
||||||
|
|
||||||
|
if is_end_portal {
|
||||||
|
if dest_world.dimension == Dimension::THE_END {
|
||||||
|
// Entering the End: spawn on the obsidian platform at (100, 49, 0) for players, or (100, 50, 0) for other entities
|
||||||
|
let is_player = caller
|
||||||
|
.get_living_entity()
|
||||||
|
.is_some_and(crate::entity::living::LivingEntity::is_player);
|
||||||
|
let y = if is_player { 49.0 } else { 50.0 };
|
||||||
|
|
||||||
|
// Ensure chunks covering the platform are loaded/generated
|
||||||
|
dest_world
|
||||||
|
.get_block_state_async(&BlockPos::new(98, 49, -2))
|
||||||
|
.await;
|
||||||
|
dest_world
|
||||||
|
.get_block_state_async(&BlockPos::new(102, 49, 2))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// Generate/regenerate the obsidian platform (5x5 obsidian at Y=48, and 5x5x3 air above it)
|
||||||
|
let platform_pos = BlockPos::new(100, 49, 0);
|
||||||
|
for dx in -2..=2 {
|
||||||
|
for dz in -2..=2 {
|
||||||
|
for dy in -1..3 {
|
||||||
|
let block = if dy == -1 {
|
||||||
|
Block::OBSIDIAN
|
||||||
|
} else {
|
||||||
|
Block::AIR
|
||||||
|
};
|
||||||
|
let target_pos = BlockPos::new(
|
||||||
|
platform_pos.0.x + dx,
|
||||||
|
platform_pos.0.y + dy,
|
||||||
|
platform_pos.0.z + dz,
|
||||||
|
);
|
||||||
|
dest_world
|
||||||
|
.set_block_state(
|
||||||
|
&target_pos,
|
||||||
|
block.default_state.id,
|
||||||
|
BlockFlags::NOTIFY_ALL,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(TeleportTransition {
|
||||||
|
new_world: dest_world,
|
||||||
|
position: Vector3::new(100.5f64, y, 0.5f64),
|
||||||
|
yaw: Some(90.0f32),
|
||||||
|
pitch: None,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// Leaving the End through the exit portal: return to overworld spawn
|
||||||
|
let info = dest_world.level_info.load();
|
||||||
|
Some(TeleportTransition {
|
||||||
|
new_world: dest_world,
|
||||||
|
position: Vector3::new(
|
||||||
|
f64::from(info.spawn_x) + 0.5,
|
||||||
|
f64::from(info.spawn_y),
|
||||||
|
f64::from(info.spawn_z) + 0.5,
|
||||||
|
),
|
||||||
|
yaw: None,
|
||||||
|
pitch: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::Nether => {
|
||||||
|
let pos = caller.get_entity().pos.load();
|
||||||
|
let current_yaw = caller.get_entity().yaw.load();
|
||||||
|
let dimensions = caller.get_entity().entity_dimension.load();
|
||||||
|
let scale_factor_new = dest_world.dimension.coordinate_scale;
|
||||||
|
let scale_factor_current = current_level.dimension.coordinate_scale;
|
||||||
|
|
||||||
|
let scale_factor = scale_factor_current / scale_factor_new;
|
||||||
|
let target_pos =
|
||||||
|
BlockPos::floored(pos.x * scale_factor, pos.y, pos.z * scale_factor);
|
||||||
|
|
||||||
|
let source_axis = source_portal.as_ref().map(|p| p.axis);
|
||||||
|
|
||||||
|
let (final_pos, yaw) = if let Some(dest_result) =
|
||||||
|
NetherPortal::search_for_portal(&dest_world, target_pos).await
|
||||||
|
{
|
||||||
|
let base_pos = source_portal.as_ref().map_or_else(
|
||||||
|
|| dest_result.get_teleport_position(),
|
||||||
|
|source| {
|
||||||
|
let source_result = PortalSearchResult {
|
||||||
|
lower_corner: source.lower_corner,
|
||||||
|
axis: source.axis,
|
||||||
|
width: source.width,
|
||||||
|
height: source.height,
|
||||||
|
};
|
||||||
|
let relative_pos = source_result.entity_pos_in_portal(pos, &dimensions);
|
||||||
|
dest_result.calculate_exit_position(relative_pos, &dimensions)
|
||||||
|
},
|
||||||
|
);
|
||||||
|
let final_pos =
|
||||||
|
dest_result.find_open_position(&dest_world, base_pos, &dimensions);
|
||||||
|
let yaw = dest_result.calculate_teleport_yaw(current_yaw, source_axis);
|
||||||
|
(final_pos, Some(yaw))
|
||||||
|
} else if let Some((build_pos, axis, is_fallback)) =
|
||||||
|
NetherPortal::find_safe_location(
|
||||||
|
&dest_world,
|
||||||
|
target_pos,
|
||||||
|
pumpkin_data::block_properties::HorizontalAxis::X,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
NetherPortal::build_portal_frame(&dest_world, build_pos, axis, is_fallback)
|
||||||
|
.await;
|
||||||
|
let new_portal = PortalSearchResult {
|
||||||
|
lower_corner: build_pos,
|
||||||
|
axis,
|
||||||
|
width: 2,
|
||||||
|
height: 3,
|
||||||
|
};
|
||||||
|
let center_pos = new_portal.get_teleport_position();
|
||||||
|
let final_pos =
|
||||||
|
new_portal.find_open_position(&dest_world, center_pos, &dimensions);
|
||||||
|
let yaw = new_portal.calculate_teleport_yaw(current_yaw, source_axis);
|
||||||
|
(final_pos, Some(yaw))
|
||||||
|
} else {
|
||||||
|
(target_pos.0.to_f64(), None)
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(TeleportTransition {
|
||||||
|
new_world: dest_world,
|
||||||
|
position: final_pos,
|
||||||
|
yaw,
|
||||||
|
pitch: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TeleportTransition {
|
||||||
|
pub new_world: Arc<World>,
|
||||||
|
pub position: Vector3<f64>,
|
||||||
|
pub yaw: Option<f32>,
|
||||||
|
pub pitch: Option<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PortalProcessor {
|
||||||
|
pub portal_type: PortalType,
|
||||||
|
pub entry_position: BlockPos,
|
||||||
|
pub portal_time: u32,
|
||||||
|
pub inside_portal_this_tick: bool,
|
||||||
|
pub destination_world: Arc<World>,
|
||||||
pub source_portal: Option<SourcePortalInfo>,
|
pub source_portal: Option<SourcePortalInfo>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PortalManager {
|
impl PortalProcessor {
|
||||||
pub const fn new(portal_delay: u32, portal_world: Arc<World>, pos: BlockPos) -> Self {
|
pub const fn new(
|
||||||
|
portal_type: PortalType,
|
||||||
|
entry_position: BlockPos,
|
||||||
|
destination_world: Arc<World>,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
portal_delay,
|
portal_type,
|
||||||
portal_world,
|
entry_position,
|
||||||
pos,
|
portal_time: 0,
|
||||||
ticks_in_portal: 0,
|
inside_portal_this_tick: true,
|
||||||
in_portal: true,
|
destination_world,
|
||||||
source_portal: None,
|
source_portal: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -56,18 +252,35 @@ impl PortalManager {
|
|||||||
self.source_portal = Some(info);
|
self.source_portal = Some(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn tick(&mut self) -> bool {
|
pub fn process_portal_teleportation(
|
||||||
if self.in_portal {
|
&mut self,
|
||||||
self.in_portal = false;
|
current_world: &World,
|
||||||
self.ticks_in_portal += 1;
|
entity: &dyn crate::entity::EntityBase,
|
||||||
self.ticks_in_portal >= self.portal_delay
|
allowed_to_teleport: bool,
|
||||||
|
) -> bool {
|
||||||
|
if self.inside_portal_this_tick {
|
||||||
|
self.inside_portal_this_tick = false;
|
||||||
|
if allowed_to_teleport {
|
||||||
|
self.portal_time += 1;
|
||||||
|
let transition_time = self
|
||||||
|
.portal_type
|
||||||
|
.get_portal_transition_time(current_world, entity);
|
||||||
|
self.portal_time >= transition_time
|
||||||
} else {
|
} else {
|
||||||
if self.ticks_in_portal < 4 {
|
false
|
||||||
self.ticks_in_portal = 0;
|
|
||||||
} else {
|
|
||||||
self.ticks_in_portal -= 4;
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
self.decay_tick();
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const fn decay_tick(&mut self) {
|
||||||
|
self.portal_time = self.portal_time.saturating_sub(4);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub const fn has_expired(&self) -> bool {
|
||||||
|
self.portal_time == 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user