mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
fix: end spawning
This commit is contained in:
@@ -190,16 +190,20 @@ impl PacketRead for SInventoryTransaction {
|
||||
fn read<R: Read>(buf: &mut R) -> Result<Self, Error> {
|
||||
let legacy_request_id = VarInt::read(buf)?;
|
||||
|
||||
let has_legacy_slots = bool::read(buf)?;
|
||||
let mut legacy_set_item_slots = Vec::new();
|
||||
if legacy_request_id.0 != 0 {
|
||||
if has_legacy_slots {
|
||||
let len = VarUInt::read(buf)?.0;
|
||||
for _ in 0..len {
|
||||
legacy_set_item_slots.push(LegacySetItemSlot::read(buf)?);
|
||||
}
|
||||
}
|
||||
|
||||
let _has_transaction_type = bool::read(buf)?;
|
||||
let transaction_type = VarUInt::read(buf)?;
|
||||
|
||||
let _has_tr_data = bool::read(buf)?;
|
||||
|
||||
let has_value = bool::read(buf)?;
|
||||
let mut actions = Vec::new();
|
||||
if has_value {
|
||||
|
||||
@@ -20,6 +20,7 @@ pub mod request_chunk_radius;
|
||||
pub mod request_network_settings;
|
||||
pub mod resource_pack_response;
|
||||
pub mod set_local_player_as_initialized;
|
||||
pub mod set_player_inventory_options;
|
||||
pub mod text;
|
||||
|
||||
pub use actor_event::*;
|
||||
@@ -44,4 +45,5 @@ pub use request_chunk_radius::*;
|
||||
pub use request_network_settings::*;
|
||||
pub use resource_pack_response::*;
|
||||
pub use set_local_player_as_initialized::*;
|
||||
pub use set_player_inventory_options::*;
|
||||
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 rustc_hash::FxHashMap;
|
||||
use tokio::sync::Mutex;
|
||||
use tracing::debug;
|
||||
use tracing::{debug, trace};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
@@ -291,6 +291,9 @@ impl ChunkEntityData {
|
||||
}
|
||||
let mut map = FxHashMap::default();
|
||||
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") {
|
||||
if uuid.len() != 4 {
|
||||
debug!(
|
||||
@@ -309,7 +312,7 @@ impl ChunkEntityData {
|
||||
| (uuid[3] as u128),
|
||||
)
|
||||
} else {
|
||||
debug!(
|
||||
trace!(
|
||||
"Entity in chunk {},{} is missing UUID: {:?}",
|
||||
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, ()> {
|
||||
Box::pin(async move {
|
||||
match &args.player.client {
|
||||
match args.player.client.as_ref() {
|
||||
crate::net::ClientPlatform::Java(java) => {
|
||||
java.send_sign_packet(*args.position, true).await;
|
||||
}
|
||||
@@ -454,7 +454,7 @@ impl BlockBehaviour for SignBlock {
|
||||
|
||||
let is_facing_front_text =
|
||||
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) => {
|
||||
java.send_sign_packet(*args.position, is_facing_front_text)
|
||||
.await;
|
||||
|
||||
@@ -365,7 +365,7 @@ pub async fn send_bedrock_commands_packet(
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ impl CommandExecutor for TargetSelfExecutor {
|
||||
let name = &player.gameprofile.name;
|
||||
info!("[{name}: Transferring {name} to {hostname}:{port}]");
|
||||
|
||||
match &player.client {
|
||||
match player.client.as_ref() {
|
||||
ClientPlatform::Java(client) => {
|
||||
client
|
||||
.enqueue_packet(&JavaCTransfer::new(hostname, VarInt(port)))
|
||||
@@ -121,7 +121,7 @@ impl CommandExecutor for TargetPlayerExecutor {
|
||||
}
|
||||
|
||||
for p in players {
|
||||
match &p.client {
|
||||
match p.client.as_ref() {
|
||||
ClientPlatform::Java(client) => {
|
||||
client
|
||||
.enqueue_packet(&JavaCTransfer::new(hostname, VarInt(port)))
|
||||
|
||||
@@ -5,7 +5,7 @@ use crate::{
|
||||
world::{
|
||||
World,
|
||||
chunker::is_within_view_distance,
|
||||
portal::{NetherPortal, PortalManager, PortalSearchResult, SourcePortalInfo},
|
||||
portal::{NetherPortal, PortalProcessor, PortalType, SourcePortalInfo},
|
||||
},
|
||||
};
|
||||
use arc_swap::ArcSwap;
|
||||
@@ -775,7 +775,7 @@ pub struct Entity {
|
||||
|
||||
pub portal_cooldown: AtomicU32,
|
||||
|
||||
pub portal_manager: Mutex<Option<Mutex<PortalManager>>>,
|
||||
pub portal_manager: Mutex<Option<Mutex<PortalProcessor>>>,
|
||||
/// Custom name for the entity
|
||||
pub custom_name: ArcSwap<Option<TextComponent>>,
|
||||
/// 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 should_remove = false;
|
||||
if let Some(pmanager_mutex) = manager_guard.as_ref() {
|
||||
let mut portal_manager = pmanager_mutex.lock().await;
|
||||
if portal_manager.tick() {
|
||||
let mut portal_processor = pmanager_mutex.lock().await;
|
||||
if portal_processor.process_portal_teleportation(
|
||||
&self.world.load(),
|
||||
caller.as_ref(),
|
||||
true,
|
||||
) {
|
||||
self.portal_cooldown
|
||||
.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 target_pos =
|
||||
BlockPos::floored(pos.x * scale_factor, pos.y, pos.z * scale_factor);
|
||||
|
||||
let dest_world = portal_manager.portal_world.clone();
|
||||
let source_portal = portal_manager.source_portal.clone();
|
||||
let source_axis = source_portal.as_ref().map(|p| p.axis);
|
||||
drop(portal_manager);
|
||||
|
||||
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,
|
||||
let transition = portal_processor
|
||||
.portal_type
|
||||
.get_portal_destination(
|
||||
&self.world.load(),
|
||||
portal_processor.destination_world.clone(),
|
||||
caller,
|
||||
portal_processor.entry_position,
|
||||
portal_processor.source_portal.clone(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
NetherPortal::build_portal_frame(&dest_world, build_pos, axis, is_fallback)
|
||||
.await;
|
||||
|
||||
drop(portal_processor);
|
||||
|
||||
if let Some(transition) = transition {
|
||||
let dest_world = transition.new_world.clone();
|
||||
let yaw = transition.yaw;
|
||||
let pitch = transition.pitch;
|
||||
let teleport_pos = transition.position;
|
||||
|
||||
// Teleport the main entity
|
||||
caller
|
||||
.clone()
|
||||
.teleport(teleport_pos, yaw, pitch, dest_world.clone())
|
||||
.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)
|
||||
};
|
||||
|
||||
// Teleport the main entity
|
||||
caller
|
||||
.clone()
|
||||
.teleport(teleport_pos, new_yaw, None, dest_world.clone())
|
||||
.await;
|
||||
|
||||
// Teleport all passengers recursively along with the vehicle
|
||||
let yaw_delta = new_yaw.map(|y| y - current_yaw);
|
||||
Self::teleport_passengers_recursive(self, teleport_pos, yaw_delta, &dest_world)
|
||||
.await;
|
||||
} else if portal_manager.ticks_in_portal == 0 {
|
||||
// Teleport all passengers recursively along with the vehicle
|
||||
let yaw_delta = yaw.map(|y| y - self.yaw.load());
|
||||
Self::teleport_passengers_recursive(self, teleport_pos, yaw_delta, &dest_world)
|
||||
.await;
|
||||
}
|
||||
} else if portal_processor.portal_time == 0 {
|
||||
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
|
||||
if self.has_vehicle().await {
|
||||
return;
|
||||
@@ -2295,7 +2246,15 @@ impl Entity {
|
||||
let mut manager = self.portal_manager.lock().await;
|
||||
let world = self.world.load();
|
||||
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(
|
||||
&world,
|
||||
@@ -2326,8 +2285,8 @@ impl Entity {
|
||||
*manager = Some(Mutex::new(new_manager));
|
||||
} else if let Some(manager) = manager.as_ref() {
|
||||
let mut manager = manager.lock().await;
|
||||
manager.pos = pos;
|
||||
manager.in_portal = true;
|
||||
manager.entry_position = pos;
|
||||
manager.inside_portal_this_tick = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2712,7 +2671,7 @@ impl Entity {
|
||||
let world = self.world.load();
|
||||
let chunk_pos = self.chunk_pos.load();
|
||||
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 view_distance =
|
||||
crate::world::chunker::get_view_distance(player).get() as i32;
|
||||
@@ -2755,7 +2714,7 @@ impl Entity {
|
||||
let world = self.world.load();
|
||||
let chunk_pos = self.chunk_pos.load();
|
||||
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
|
||||
let center = player.get_entity().chunk_pos.load();
|
||||
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.
|
||||
pub gameprofile: GameProfile,
|
||||
/// The client connection associated with the player.
|
||||
pub client: ClientPlatform,
|
||||
pub client: Arc<ClientPlatform>,
|
||||
/// The player's inventory.
|
||||
pub inventory: Arc<PlayerInventory>,
|
||||
/// The player's `EnderChest` inventory.
|
||||
@@ -571,7 +571,7 @@ impl Player {
|
||||
|
||||
#[expect(clippy::too_many_lines)]
|
||||
pub async fn new(
|
||||
client: ClientPlatform,
|
||||
client: Arc<ClientPlatform>,
|
||||
gameprofile: GameProfile,
|
||||
config: PlayerConfig,
|
||||
world: Arc<World>,
|
||||
@@ -1729,7 +1729,7 @@ impl Player {
|
||||
}
|
||||
|
||||
pub async fn show_title(&self, text: &TextComponent, mode: &TitleMode) {
|
||||
match &self.client {
|
||||
match self.client.as_ref() {
|
||||
ClientPlatform::Java(client) => match mode {
|
||||
TitleMode::Title => client.enqueue_packet(&CTitleText::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) {
|
||||
match &self.client {
|
||||
match self.client.as_ref() {
|
||||
ClientPlatform::Java(client) => {
|
||||
client
|
||||
.enqueue_packet(&CTitleAnimation::new(fade_in, stay, fade_out))
|
||||
@@ -1915,11 +1915,10 @@ impl Player {
|
||||
*xp -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
let (chunk_of_chunks, total_sent_chunks) = {
|
||||
let mut chunk_manager = self.chunk_manager.lock().await;
|
||||
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.
|
||||
// If we have sent too many chunks without receiving an ack, we stop sending chunks.
|
||||
chunk_manager
|
||||
@@ -1930,11 +1929,12 @@ impl Player {
|
||||
};
|
||||
(chunks, chunk_manager.sent_chunks_count())
|
||||
};
|
||||
|
||||
if let Some(chunk_of_chunks) = chunk_of_chunks {
|
||||
self.client.send_chunks(&chunk_of_chunks).await;
|
||||
|
||||
if let ClientPlatform::Bedrock(bedrock_client) = &self.client
|
||||
let client = self.client.clone();
|
||||
tokio::spawn(async move {
|
||||
client.send_chunks(&chunk_of_chunks).await;
|
||||
});
|
||||
if let ClientPlatform::Bedrock(bedrock_client) = self.client.as_ref()
|
||||
&& !self.bedrock_spawned.load(Ordering::Relaxed)
|
||||
&& total_sent_chunks > 4
|
||||
{
|
||||
@@ -1944,7 +1944,6 @@ impl Player {
|
||||
self.bedrock_spawned.store(true, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
self.tick_counter.fetch_add(1, Ordering::Relaxed);
|
||||
self.living_entity
|
||||
.entity
|
||||
@@ -1978,7 +1977,6 @@ impl Player {
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
self.last_attacked_ticks.fetch_add(1, Ordering::Relaxed);
|
||||
|
||||
let caller: Arc<dyn EntityBase> = self.clone();
|
||||
@@ -1996,7 +1994,6 @@ impl Player {
|
||||
|
||||
// Timeout/keep alive handling
|
||||
self.tick_client_load_timeout();
|
||||
|
||||
// Idle timeout handling
|
||||
let now = Instant::now();
|
||||
let idle_timeout_minutes = server.player_idle_timeout.load(Ordering::Relaxed);
|
||||
@@ -2141,7 +2138,7 @@ impl Player {
|
||||
|
||||
/// Updates the current abilities the player has.
|
||||
pub async fn send_abilities_update(&self) {
|
||||
match &self.client {
|
||||
match self.client.as_ref() {
|
||||
ClientPlatform::Java(java) => {
|
||||
let mut b = 0;
|
||||
let abilities = &self.abilities.lock().await;
|
||||
@@ -2244,7 +2241,7 @@ impl Player {
|
||||
}
|
||||
|
||||
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 packet_stats: Vec<Statistic> = stats_guard
|
||||
.stats
|
||||
@@ -2394,7 +2391,7 @@ impl Player {
|
||||
self.permission_lvl.store(lvl);
|
||||
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)
|
||||
.await;
|
||||
} else {
|
||||
@@ -2405,7 +2402,7 @@ impl Player {
|
||||
/// Sends the world time to only this player.
|
||||
pub async fn send_time(&self, world: &World) {
|
||||
let l_world = world.level_time.lock().await;
|
||||
match &self.client {
|
||||
match self.client.as_ref() {
|
||||
ClientPlatform::Java(java_client) => {
|
||||
java_client
|
||||
.enqueue_packet(&CUpdateTime::new(
|
||||
@@ -2624,7 +2621,7 @@ impl Player {
|
||||
return;
|
||||
}
|
||||
|
||||
match &self.client {
|
||||
match self.client.as_ref() {
|
||||
ClientPlatform::Java(client) => {
|
||||
client
|
||||
.enqueue_packet(&CSetHealth::new(
|
||||
@@ -2898,7 +2895,7 @@ impl Player {
|
||||
}],
|
||||
));
|
||||
|
||||
match &self.client {
|
||||
match self.client.as_ref() {
|
||||
crate::net::ClientPlatform::Java(client) => {
|
||||
client
|
||||
.enqueue_packet(&CGameEvent::new(
|
||||
@@ -3021,7 +3018,7 @@ impl Player {
|
||||
|
||||
/// Sends a custom payload packet to this player (Java edition only).
|
||||
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))
|
||||
.await;
|
||||
}
|
||||
@@ -3114,7 +3111,7 @@ impl Player {
|
||||
}
|
||||
|
||||
pub async fn send_system_message_raw(&self, text: &TextComponent, overlay: bool) {
|
||||
match &self.client {
|
||||
match self.client.as_ref() {
|
||||
ClientPlatform::Java(client) => {
|
||||
client
|
||||
.enqueue_packet(&CSystemChatMessage::new(text, overlay))
|
||||
|
||||
@@ -468,13 +468,13 @@ impl PumpkinServer {
|
||||
},
|
||||
PacketHandlerResult::ReadyToPlay(profile,config) => {
|
||||
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
|
||||
{
|
||||
world
|
||||
.spawn_java_player(&server_clone.basic_config, &player, &server_clone)
|
||||
.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.progress_player_packets(&player, &server_clone).await;
|
||||
|
||||
@@ -558,7 +558,7 @@ impl PumpkinServer {
|
||||
}
|
||||
PacketHandlerResult::ReadyToPlay(profile, config) => {
|
||||
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
|
||||
{
|
||||
*client_clone.player.lock().await = Some(player.clone());
|
||||
|
||||
@@ -53,6 +53,7 @@ use pumpkin_protocol::{
|
||||
request_network_settings::SRequestNetworkSettings,
|
||||
resource_pack_response::SResourcePackResponse,
|
||||
set_local_player_as_initialized::SSetLocalPlayerAsInitialized,
|
||||
set_player_inventory_options::SSetPlayerInventoryOptions,
|
||||
text::SText,
|
||||
},
|
||||
},
|
||||
@@ -118,6 +119,7 @@ pub struct BedrockClient {
|
||||
pub be_clients: Arc<Mutex<HashMap<SocketAddr, Arc<Self>>>>,
|
||||
|
||||
tasks: TaskTracker,
|
||||
rt_handle: tokio::runtime::Handle,
|
||||
outgoing_packet_queue_send: Sender<OutgoingPacket>,
|
||||
/// A queue of serialized packets to send to the network
|
||||
outgoing_packet_queue_recv: Mutex<Option<Receiver<OutgoingPacket>>>,
|
||||
@@ -166,6 +168,7 @@ impl BedrockClient {
|
||||
let (send, 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 rt_handle = tokio::runtime::Handle::current();
|
||||
Self {
|
||||
socket,
|
||||
player: Mutex::new(None),
|
||||
@@ -176,6 +179,7 @@ impl BedrockClient {
|
||||
network_writer: Arc::new(RwLock::new(UDPNetworkEncoder::new())),
|
||||
network_reader: Mutex::new(UDPNetworkDecoder::new()),
|
||||
tasks: TaskTracker::new(),
|
||||
rt_handle,
|
||||
outgoing_packet_queue_send: send,
|
||||
outgoing_packet_queue_recv: Mutex::new(Some(recv)),
|
||||
outgoing_packet_priority_send: priority_send,
|
||||
@@ -338,19 +342,62 @@ impl BedrockClient {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut valid_chunks = Vec::with_capacity(chunks.len());
|
||||
for chunk in chunks {
|
||||
let event = ChunkSend::new(player.world(), chunk.clone());
|
||||
let event = server.plugin_manager.fire(event).await;
|
||||
if event.cancelled {
|
||||
continue;
|
||||
if !event.cancelled {
|
||||
valid_chunks.push(chunk.clone());
|
||||
}
|
||||
}
|
||||
|
||||
self.enqueue_packet_internal(&CLevelChunk {
|
||||
dimension: 0,
|
||||
cache_enabled: false,
|
||||
chunk,
|
||||
})
|
||||
.await;
|
||||
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,
|
||||
cache_enabled: false,
|
||||
chunk: &chunk,
|
||||
};
|
||||
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)?,
|
||||
);
|
||||
}
|
||||
SSetPlayerInventoryOptions::PACKET_ID => {
|
||||
let _ = SSetPlayerInventoryOptions::read(reader)?;
|
||||
// Ignore for now
|
||||
}
|
||||
SPlayerAction::PACKET_ID => {
|
||||
self.handle_player_action(player, server, SPlayerAction::read(reader)?)
|
||||
.await;
|
||||
@@ -1210,20 +1261,9 @@ impl BedrockClient {
|
||||
{
|
||||
if self.close_token.is_cancelled() {
|
||||
None
|
||||
} else if tokio::runtime::Handle::try_current().is_ok() {
|
||||
Some(self.tasks.spawn(task))
|
||||
} else {
|
||||
warn!("No Tokio runtime in current thread; running task on dedicated runtime thread");
|
||||
std::thread::spawn(move || {
|
||||
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
|
||||
let _guard = self.rt_handle.enter();
|
||||
Some(self.tasks.spawn(task))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,7 +213,7 @@ impl Context {
|
||||
/// - `player`: The player for which the commands will be reloaded.
|
||||
pub async fn reload_commands_for(&self, player: &Arc<Player>) {
|
||||
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(
|
||||
player,
|
||||
&self.server,
|
||||
|
||||
@@ -27,7 +27,7 @@ impl ToFromWasmEvent for PacketReceivedEvent {
|
||||
.add_player(self.player.clone())
|
||||
.expect("failed to add player resource");
|
||||
|
||||
let packet = match &self.player.client {
|
||||
let packet = match self.player.client.as_ref() {
|
||||
ClientPlatform::Java(client) => {
|
||||
let version = client.version.load();
|
||||
generated_packets::deserialize_java_serverbound_packet(
|
||||
@@ -75,7 +75,7 @@ impl ToFromWasmEvent for PacketSentEvent {
|
||||
.add_player(self.player.clone())
|
||||
.expect("failed to add player resource");
|
||||
|
||||
let packet = match &self.player.client {
|
||||
let packet = match self.player.client.as_ref() {
|
||||
ClientPlatform::Java(_) => {
|
||||
generated_packets::clientbound_java_any_to_wit(self.packet.as_ref())
|
||||
.map_or(ClientboundPacket::Unknown, ClientboundPacket::Java)
|
||||
|
||||
@@ -1084,7 +1084,7 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
|
||||
port: u16,
|
||||
) -> wasmtime::Result<()> {
|
||||
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
|
||||
.send_packet_now(&pumpkin_protocol::java::client::play::CTransfer::new(
|
||||
&host,
|
||||
@@ -1425,7 +1425,7 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
|
||||
player: Resource<Player>,
|
||||
) -> wasmtime::Result<Option<Resource<pumpkin::plugin::player::JavaPlayer>>> {
|
||||
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)?))
|
||||
} else {
|
||||
Ok(None)
|
||||
@@ -1437,7 +1437,7 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
|
||||
player: Resource<Player>,
|
||||
) -> wasmtime::Result<Option<Resource<pumpkin::plugin::player::BedrockPlayer>>> {
|
||||
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)?))
|
||||
} else {
|
||||
Ok(None)
|
||||
@@ -1607,7 +1607,7 @@ impl pumpkin::plugin::player::HostJavaPlayer for PluginHostState {
|
||||
.provider
|
||||
.clone();
|
||||
|
||||
if let crate::net::ClientPlatform::Java(_) = player.client {
|
||||
if let crate::net::ClientPlatform::Java(_) = player.client.as_ref() {
|
||||
player
|
||||
.client
|
||||
.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)),
|
||||
};
|
||||
|
||||
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() {
|
||||
pumpkin_protocol::ConnectionState::Config => {
|
||||
client
|
||||
@@ -1797,7 +1797,7 @@ impl pumpkin::plugin::player::HostJavaPlayer for PluginHostState {
|
||||
.provider
|
||||
.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() {
|
||||
pumpkin_protocol::ConnectionState::Config => {
|
||||
client
|
||||
@@ -1847,7 +1847,7 @@ impl pumpkin::plugin::player::HostBedrockPlayer for PluginHostState {
|
||||
.provider
|
||||
.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()))
|
||||
} else {
|
||||
Ok(pumpkin::plugin::player::BedrockMinecraftVersion::Unknown)
|
||||
@@ -2048,7 +2048,7 @@ impl pumpkin::plugin::player::HostBedrockPlayer for PluginHostState {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -2068,7 +2068,7 @@ impl pumpkin::plugin::player::HostBedrockPlayer for PluginHostState {
|
||||
.provider
|
||||
.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();
|
||||
(**data).as_ref().map_or_else(
|
||||
|| Err(wasmtime::Error::msg("client data not available")),
|
||||
@@ -2140,7 +2140,7 @@ impl pumpkin::plugin::player::HostBedrockPlayer for PluginHostState {
|
||||
.provider
|
||||
.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 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.
|
||||
pub async fn add_player(
|
||||
&self,
|
||||
client: ClientPlatform,
|
||||
client: Arc<ClientPlatform>,
|
||||
profile: GameProfile,
|
||||
config: Option<PlayerConfig>,
|
||||
) -> Option<(Arc<Player>, Arc<World>)> {
|
||||
|
||||
@@ -94,7 +94,7 @@ impl Bossbar {
|
||||
/// Extra methods for [`Player`] to send and manage the bossbar.
|
||||
impl Player {
|
||||
pub async fn send_bossbar(&self, bossbar: &Bossbar) {
|
||||
match &self.client {
|
||||
match self.client.as_ref() {
|
||||
ClientPlatform::Java(java) => {
|
||||
let boss_action = BosseventAction::Add {
|
||||
title: bossbar.title.clone(),
|
||||
@@ -123,7 +123,7 @@ impl Player {
|
||||
}
|
||||
|
||||
pub async fn remove_bossbar(&self, uuid: Uuid) {
|
||||
match &self.client {
|
||||
match self.client.as_ref() {
|
||||
ClientPlatform::Java(java) => {
|
||||
let boss_action = BosseventAction::Remove;
|
||||
|
||||
@@ -141,7 +141,7 @@ impl Player {
|
||||
}
|
||||
|
||||
pub async fn update_bossbar_health(&self, uuid: &Uuid, health: f32) {
|
||||
match &self.client {
|
||||
match self.client.as_ref() {
|
||||
ClientPlatform::Java(java) => {
|
||||
let boss_action = BosseventAction::UpdateHealth(health);
|
||||
|
||||
@@ -159,7 +159,7 @@ impl Player {
|
||||
}
|
||||
|
||||
pub async fn update_bossbar_title(&self, uuid: &Uuid, title: TextComponent) {
|
||||
match &self.client {
|
||||
match self.client.as_ref() {
|
||||
ClientPlatform::Java(java) => {
|
||||
let boss_action = BosseventAction::UpdateTile(title);
|
||||
|
||||
@@ -183,7 +183,7 @@ impl Player {
|
||||
dividers: BossbarDivisions,
|
||||
_flags: BossbarFlags,
|
||||
) {
|
||||
match &self.client {
|
||||
match self.client.as_ref() {
|
||||
ClientPlatform::Java(java) => {
|
||||
let boss_action = BosseventAction::UpdateStyle {
|
||||
color: (color as u8).into(),
|
||||
@@ -207,7 +207,7 @@ impl Player {
|
||||
}
|
||||
|
||||
pub async fn update_bossbar_flags(&self, uuid: &Uuid, flags: BossbarFlags) {
|
||||
match &self.client {
|
||||
match self.client.as_ref() {
|
||||
ClientPlatform::Java(java) => {
|
||||
let boss_action = BosseventAction::UpdateFlags(flags.bits());
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ pub async fn update_position(player: &Arc<Player>) {
|
||||
return;
|
||||
}
|
||||
|
||||
match &player.client {
|
||||
match player.client.as_ref() {
|
||||
ClientPlatform::Java(java_client) => {
|
||||
java_client
|
||||
.send_packet_now(&CCenterChunk {
|
||||
@@ -93,7 +93,7 @@ pub async fn update_position(player: &Arc<Player>) {
|
||||
|
||||
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 {
|
||||
player
|
||||
.client
|
||||
|
||||
@@ -143,7 +143,6 @@ use rand::{RngExt, rng};
|
||||
use scoreboard::Scoreboard;
|
||||
use time::LevelTime;
|
||||
use tokio::sync::Mutex;
|
||||
use tokio::task::JoinSet;
|
||||
|
||||
pub mod border;
|
||||
pub mod bossbar;
|
||||
@@ -507,7 +506,7 @@ impl World {
|
||||
let mut recipients_by_version: BTreeMap<JavaMinecraftVersion, Vec<&'a JavaClient>> =
|
||||
BTreeMap::new();
|
||||
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
|
||||
.entry(java_client.version.load())
|
||||
.or_default()
|
||||
@@ -556,7 +555,7 @@ impl World {
|
||||
pub fn broadcast_packet_all_sync<P: ClientPacket>(&self, packet: &P) {
|
||||
let players = self.players.load();
|
||||
for player in players.iter() {
|
||||
match &player.client {
|
||||
match player.client.as_ref() {
|
||||
ClientPlatform::Java(java) => {
|
||||
if let Ok(data) =
|
||||
JavaClient::serialize_packet_for_version(packet, java.version.load())
|
||||
@@ -624,7 +623,7 @@ impl World {
|
||||
let mut be_recipients = Vec::new();
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
@@ -700,7 +699,7 @@ impl World {
|
||||
if except.contains(&p.gameprofile.id) {
|
||||
continue;
|
||||
}
|
||||
match &p.client {
|
||||
match p.client.as_ref() {
|
||||
ClientPlatform::Java(_) => java_recipients.push(p),
|
||||
ClientPlatform::Bedrock(be_client) => be_client.try_enqueue_packet(be_packet),
|
||||
}
|
||||
@@ -725,7 +724,7 @@ impl World {
|
||||
if except.contains(&p.gameprofile.id) {
|
||||
continue;
|
||||
}
|
||||
match &p.client {
|
||||
match p.client.as_ref() {
|
||||
ClientPlatform::Java(_) => java_recipients.push(p),
|
||||
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>) {
|
||||
let start = tokio::time::Instant::now();
|
||||
|
||||
// IMPORTANT: send flush_block_updates first to prevent issues with CAcknowledgeBlockChange
|
||||
self.flush_block_updates().await;
|
||||
self.flush_synced_block_events().await;
|
||||
self.update_active_chunks();
|
||||
self.tick_environment().await;
|
||||
|
||||
let chunk_start = tokio::time::Instant::now();
|
||||
self.tick_chunks().await;
|
||||
let chunk_elapsed = chunk_start.elapsed();
|
||||
let world_for_chunks = self.clone();
|
||||
let chunk_future = async move {
|
||||
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().clone();
|
||||
let players = self.players.load();
|
||||
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();
|
||||
for player in players.iter() {
|
||||
let player_clone = player.clone();
|
||||
let server_clone = server.clone();
|
||||
player_tasks.spawn(async move {
|
||||
player_clone.tick(&server_clone).await;
|
||||
});
|
||||
}
|
||||
while let Some(res) = player_tasks.join_next().await {
|
||||
if let Err(e) = res {
|
||||
error!("Player tick panicked: {:?}", e);
|
||||
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() {
|
||||
let p_clone = player.clone();
|
||||
let s_clone = server_for_players.clone();
|
||||
tasks.spawn(async move {
|
||||
p_clone.tick(&s_clone).await;
|
||||
});
|
||||
}
|
||||
}
|
||||
let player_elapsed = player_start.elapsed();
|
||||
|
||||
let entity_start = tokio::time::Instant::now();
|
||||
let entities_to_tick = self.entities.load().clone();
|
||||
let entity_count = entities_to_tick.len();
|
||||
|
||||
let mut entity_tasks = tokio::task::JoinSet::new();
|
||||
for entity in entities_to_tick.iter() {
|
||||
let entity_clone = entity.clone();
|
||||
let server_clone = server.clone();
|
||||
let players_clone = players.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();
|
||||
let entity_bb = entity_inner.bounding_box.load();
|
||||
|
||||
for player in players_clone.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
|
||||
&& (player_pos.y - entity_pos.y).abs() < 5.0
|
||||
&& (player_pos.z - entity_pos.z).abs() < 5.0
|
||||
&& player
|
||||
.get_entity()
|
||||
.bounding_box
|
||||
.load()
|
||||
.expand(1.0, 0.5, 1.0)
|
||||
.intersects(&entity_bb)
|
||||
{
|
||||
entity_clone.on_player_collision(player).await;
|
||||
break;
|
||||
}
|
||||
while let Some(res) = tasks.join_next().await {
|
||||
if let Err(e) = res {
|
||||
error!("Player tick panicked: {:?}", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
while let Some(res) = entity_tasks.join_next().await {
|
||||
if let Err(e) = res {
|
||||
error!("Entity tick panicked: {:?}", e);
|
||||
}
|
||||
}
|
||||
let entity_elapsed = entity_start.elapsed();
|
||||
t.elapsed()
|
||||
};
|
||||
|
||||
let entities_to_tick = self.entities.load();
|
||||
let entity_count = entities_to_tick.len();
|
||||
let server_for_entities = server.clone();
|
||||
|
||||
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() {
|
||||
let e_clone = entity.clone();
|
||||
let s_clone = server_for_entities.clone();
|
||||
let p_cache = players_cache.clone();
|
||||
|
||||
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();
|
||||
|
||||
for (player, player_pos, player_bb) in p_cache.iter() {
|
||||
if (player_pos.x - entity_pos.x).abs() < 5.0
|
||||
&& (player_pos.y - entity_pos.y).abs() < 5.0
|
||||
&& (player_pos.z - entity_pos.z).abs() < 5.0
|
||||
&& player_bb.intersects(&entity_bb)
|
||||
{
|
||||
e_clone.on_player_collision(player).await;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
while let Some(res) = tasks.join_next().await {
|
||||
if let Err(e) = res {
|
||||
error!("Entity tick panicked: {:?}", e);
|
||||
}
|
||||
}
|
||||
t.elapsed()
|
||||
};
|
||||
|
||||
let block_entity_start = tokio::time::Instant::now();
|
||||
let active_chunks = self.active_chunks.load();
|
||||
let block_entities: Vec<Arc<dyn BlockEntity>> = self
|
||||
.block_entities
|
||||
@@ -979,23 +990,34 @@ impl World {
|
||||
.collect();
|
||||
let block_entity_count = block_entities.len();
|
||||
|
||||
let mut block_entity_tasks = tokio::task::JoinSet::new();
|
||||
for block_entity in block_entities {
|
||||
let world_clone = self.clone();
|
||||
block_entity_tasks.spawn(async move {
|
||||
block_entity.tick(&world_clone).await;
|
||||
});
|
||||
}
|
||||
while let Some(res) = block_entity_tasks.join_next().await {
|
||||
if let Err(e) = res {
|
||||
error!("Block entity tick panicked: {:?}", e);
|
||||
let world_for_be = self.clone();
|
||||
let block_entity_future = async move {
|
||||
let t = tokio::time::Instant::now();
|
||||
let mut tasks = tokio::task::JoinSet::new();
|
||||
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;
|
||||
});
|
||||
}
|
||||
}
|
||||
let block_entity_elapsed = block_entity_start.elapsed();
|
||||
while let Some(res) = tasks.join_next().await {
|
||||
if let Err(e) = res {
|
||||
error!("Block entity panicked: {:?}", e);
|
||||
}
|
||||
}
|
||||
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();
|
||||
|
||||
// Tick the End dragon fight (only on THE_END worlds).
|
||||
if let Some(ref fight_mutex) = self.dragon_fight {
|
||||
dragon_fight::DragonFight::tick(fight_mutex, self).await;
|
||||
}
|
||||
@@ -1069,7 +1091,7 @@ impl World {
|
||||
});
|
||||
|
||||
for p in recipients {
|
||||
match &p.client {
|
||||
match p.client.as_ref() {
|
||||
ClientPlatform::Java(_) => java_recipients.push(p),
|
||||
ClientPlatform::Bedrock(be_client) => {
|
||||
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>) {
|
||||
let active_chunks = self.active_chunks.load();
|
||||
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 {
|
||||
let block = self.get_block(&scheduled_tick.position);
|
||||
if let Some(pumpkin_block) = self.block_registry.get_pumpkin_block(block.id) {
|
||||
pumpkin_block
|
||||
.on_scheduled_tick(OnScheduledTickArgs {
|
||||
world: self,
|
||||
block,
|
||||
position: &scheduled_tick.position,
|
||||
})
|
||||
.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 {
|
||||
let (block, fluid) = match (scheduled_tick.tick_block, scheduled_tick.tick_fluid) {
|
||||
(true, true) => {
|
||||
let (block, fluid) = self.get_block_and_fluid(&scheduled_tick.position);
|
||||
(Some(block), Some(fluid))
|
||||
let world = self.clone();
|
||||
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
|
||||
.on_scheduled_tick(OnScheduledTickArgs {
|
||||
world: &world,
|
||||
block,
|
||||
position: &pos,
|
||||
})
|
||||
.await;
|
||||
}
|
||||
(true, false) => (Some(self.get_block(&scheduled_tick.position)), None),
|
||||
(false, true) => (None, Some(self.get_fluid(&scheduled_tick.position))),
|
||||
(false, false) => (None, None),
|
||||
};
|
||||
|
||||
if let Some(block) = block
|
||||
&& let Some(pumpkin_block) = self.block_registry.get_pumpkin_block(block.id)
|
||||
{
|
||||
pumpkin_block
|
||||
.random_tick(RandomTickArgs {
|
||||
world: self,
|
||||
block,
|
||||
position: &scheduled_tick.position,
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
if let Some(fluid) = fluid
|
||||
&& let Some(pumpkin_fluid) = self.block_registry.get_pumpkin_fluid(fluid.id)
|
||||
{
|
||||
pumpkin_fluid
|
||||
.random_tick(fluid, self, &scheduled_tick.position)
|
||||
.await;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let spawn_state = self.spawn_state.load();
|
||||
// 2. Spawn Fluid Ticks
|
||||
for scheduled_tick in tick_data.fluid_ticks {
|
||||
let world = self.clone();
|
||||
let pos = scheduled_tick.position;
|
||||
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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// TODO gamerule this.spawnEnemies || this.spawnFriendlies
|
||||
// 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),
|
||||
};
|
||||
|
||||
if let Some(block) = block
|
||||
&& let Some(pumpkin_block) = world.block_registry.get_pumpkin_block(block.id)
|
||||
{
|
||||
pumpkin_block
|
||||
.random_tick(RandomTickArgs {
|
||||
world: &world,
|
||||
block,
|
||||
position: &pos,
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
if let Some(fluid) = fluid
|
||||
&& let Some(pumpkin_fluid) = world.block_registry.get_pumpkin_fluid(fluid.id)
|
||||
{
|
||||
pumpkin_fluid.random_tick(fluid, &world, &pos).await;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 4. Calculate Spawn List (Sequential setup)
|
||||
let spawn_state = self.spawn_state.load();
|
||||
let (spawn_mobs, spawn_monsters, peaceful) = {
|
||||
let lock = self.level_info.load();
|
||||
(
|
||||
@@ -1231,43 +1272,43 @@ impl World {
|
||||
let spawn_passives = self.level_time.lock().await.time_of_day % 400 == 0;
|
||||
let spawn_enemies = !peaceful && spawn_monsters && spawn_mobs;
|
||||
let spawn_passives = spawn_passives && spawn_mobs;
|
||||
let spawn_list: Vec<&'static MobCategory> =
|
||||
natural_spawner::get_filtered_spawning_categories(
|
||||
&spawn_state,
|
||||
spawn_mobs,
|
||||
spawn_enemies,
|
||||
spawn_passives,
|
||||
);
|
||||
|
||||
if spawn_list.is_empty() {
|
||||
return;
|
||||
}
|
||||
let spawn_list = Arc::new(natural_spawner::get_filtered_spawning_categories(
|
||||
&spawn_state,
|
||||
spawn_mobs,
|
||||
spawn_enemies,
|
||||
spawn_passives,
|
||||
));
|
||||
|
||||
let mut spawning_chunks = Vec::new();
|
||||
for pos in active_chunks.iter() {
|
||||
if let Some(chunk) = self.level.read_chunk_sync(pos, std::clone::Clone::clone) {
|
||||
spawning_chunks.push((*pos, chunk));
|
||||
// 5. Spawn Chunk Spawners into the SAME JoinSet
|
||||
if !spawn_list.is_empty() {
|
||||
let mut spawning_chunks = Vec::new();
|
||||
for pos in active_chunks.iter() {
|
||||
if let Some(chunk) = self.level.read_chunk_sync(pos, std::clone::Clone::clone) {
|
||||
spawning_chunks.push((*pos, chunk));
|
||||
}
|
||||
}
|
||||
|
||||
spawning_chunks.shuffle(&mut rng());
|
||||
|
||||
for (pos, chunk) in spawning_chunks {
|
||||
let world = self.clone();
|
||||
let s_list = spawn_list.clone();
|
||||
let s_state = spawn_state.clone();
|
||||
|
||||
chunk_tasks.spawn(async move {
|
||||
world
|
||||
.tick_spawning_chunk(pos, &chunk, &s_list, &s_state)
|
||||
.await;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// log::debug!("spawning list size {}", spawn_list.len());
|
||||
spawning_chunks.shuffle(&mut rng());
|
||||
|
||||
let mut spawn_tasks = JoinSet::new();
|
||||
let spawn_list = Arc::new(spawn_list);
|
||||
|
||||
for (pos, chunk) in spawning_chunks {
|
||||
let world = self.clone();
|
||||
let spawn_list = spawn_list.clone();
|
||||
let spawn_state = spawn_state.clone();
|
||||
let chunk = chunk.clone();
|
||||
spawn_tasks.spawn(async move {
|
||||
world
|
||||
.tick_spawning_chunk(pos, &chunk, &spawn_list, &spawn_state)
|
||||
.await;
|
||||
});
|
||||
while let Some(res) = chunk_tasks.join_next().await {
|
||||
if let Err(e) = res {
|
||||
error!("Chunk task panicked: {:?}", e);
|
||||
}
|
||||
}
|
||||
while spawn_tasks.join_next().await.is_some() {}
|
||||
}
|
||||
|
||||
pub fn get_fluid_collisions(self: &Arc<Self>, bounding_box: BoundingBox) -> Vec<&Fluid> {
|
||||
@@ -2894,7 +2935,7 @@ impl World {
|
||||
player.send_active_effects().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
|
||||
{
|
||||
java_client
|
||||
@@ -2956,7 +2997,7 @@ impl World {
|
||||
yaw: 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;
|
||||
}
|
||||
|
||||
@@ -3003,7 +3044,7 @@ impl World {
|
||||
};
|
||||
for player in self.players.load().iter() {
|
||||
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());
|
||||
}
|
||||
let sound = IdOr::<SoundEvent>::Id(sound_id);
|
||||
@@ -3228,7 +3269,7 @@ impl World {
|
||||
.await;
|
||||
|
||||
// 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 chunk = target_world
|
||||
.level
|
||||
@@ -5051,7 +5092,7 @@ impl World {
|
||||
});
|
||||
|
||||
for p in recipients {
|
||||
match &p.client {
|
||||
match p.client.as_ref() {
|
||||
ClientPlatform::Java(_) => java_recipients.push(p),
|
||||
ClientPlatform::Bedrock(be_client) => be_client.try_enqueue_packet(be_packet),
|
||||
}
|
||||
@@ -5107,7 +5148,7 @@ impl World {
|
||||
let mut bedrock_recipients = Vec::new();
|
||||
|
||||
for p in recipients {
|
||||
match &p.client {
|
||||
match p.client.as_ref() {
|
||||
ClientPlatform::Java(_) => java_recipients.push(p),
|
||||
ClientPlatform::Bedrock(be_client) => bedrock_recipients.push(be_client.clone()),
|
||||
}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use pumpkin_data::Block;
|
||||
use pumpkin_data::block_properties::HorizontalAxis;
|
||||
use pumpkin_data::dimension::Dimension;
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
use pumpkin_util::math::vector3::Vector3;
|
||||
use pumpkin_world::world::BlockFlags;
|
||||
|
||||
use super::World;
|
||||
|
||||
@@ -31,23 +35,215 @@ impl From<&PortalSearchResult> for SourcePortalInfo {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PortalManager {
|
||||
pub portal_delay: u32,
|
||||
pub portal_world: Arc<World>,
|
||||
pub pos: BlockPos,
|
||||
pub ticks_in_portal: u32,
|
||||
pub in_portal: bool,
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub enum PortalType {
|
||||
Nether,
|
||||
End,
|
||||
}
|
||||
|
||||
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>,
|
||||
}
|
||||
|
||||
impl PortalManager {
|
||||
pub const fn new(portal_delay: u32, portal_world: Arc<World>, pos: BlockPos) -> Self {
|
||||
impl PortalProcessor {
|
||||
pub const fn new(
|
||||
portal_type: PortalType,
|
||||
entry_position: BlockPos,
|
||||
destination_world: Arc<World>,
|
||||
) -> Self {
|
||||
Self {
|
||||
portal_delay,
|
||||
portal_world,
|
||||
pos,
|
||||
ticks_in_portal: 0,
|
||||
in_portal: true,
|
||||
portal_type,
|
||||
entry_position,
|
||||
portal_time: 0,
|
||||
inside_portal_this_tick: true,
|
||||
destination_world,
|
||||
source_portal: None,
|
||||
}
|
||||
}
|
||||
@@ -56,18 +252,35 @@ impl PortalManager {
|
||||
self.source_portal = Some(info);
|
||||
}
|
||||
|
||||
pub const fn tick(&mut self) -> bool {
|
||||
if self.in_portal {
|
||||
self.in_portal = false;
|
||||
self.ticks_in_portal += 1;
|
||||
self.ticks_in_portal >= self.portal_delay
|
||||
} else {
|
||||
if self.ticks_in_portal < 4 {
|
||||
self.ticks_in_portal = 0;
|
||||
pub fn process_portal_teleportation(
|
||||
&mut self,
|
||||
current_world: &World,
|
||||
entity: &dyn crate::entity::EntityBase,
|
||||
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 {
|
||||
self.ticks_in_portal -= 4;
|
||||
false
|
||||
}
|
||||
} else {
|
||||
self.decay_tick();
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user