fix: reset player velocity state on client tick end
This commit is contained in:
parent
5bbbf4da84
commit
26baa1effd
File diff suppressed because it is too large
Load Diff
@ -2,92 +2,12 @@ use super::{ChunkLevel, ChunkPos, HashMapType, LevelChannel};
|
|||||||
use crate::chunk_system::chunk_state::StagedChunkEnum; // Fixed path
|
use crate::chunk_system::chunk_state::StagedChunkEnum; // Fixed path
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use std::cmp::min;
|
use std::cmp::min;
|
||||||
use std::collections::VecDeque;
|
|
||||||
use std::collections::hash_map::Entry;
|
use std::collections::hash_map::Entry;
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
use std::mem::swap;
|
use std::mem::swap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
struct LevelCache(i32, i32, usize, [(i8, i8); 256 * 256]);
|
|
||||||
|
|
||||||
#[expect(clippy::large_stack_arrays)]
|
|
||||||
impl LevelCache {
|
|
||||||
const fn new() -> Self {
|
|
||||||
Self(0, 0, 0, [(0, 0); 256 * 256])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl LevelCache {
|
|
||||||
fn clean(&mut self, pos: ChunkPos, level: i8) {
|
|
||||||
let dst = ChunkLoading::MAX_LEVEL - level + 1;
|
|
||||||
self.0 = pos.x - dst as i32;
|
|
||||||
self.1 = pos.y - dst as i32;
|
|
||||||
self.2 = (dst as usize) << 1 | 1;
|
|
||||||
self.3[..self.2 * self.2].fill((-127, -127));
|
|
||||||
}
|
|
||||||
fn get(&mut self, map: &ChunkLevel, pos: ChunkPos) -> i8 {
|
|
||||||
let dx = (pos.x - self.0) as usize;
|
|
||||||
let dy = (pos.y - self.1) as usize;
|
|
||||||
debug_assert!(pos.x >= self.0 && pos.y >= self.1);
|
|
||||||
let value = &mut self.3[dx * self.2 + dy];
|
|
||||||
if value.0 == -127 {
|
|
||||||
value.0 = *map.get(&pos).unwrap_or(&ChunkLoading::MAX_LEVEL);
|
|
||||||
value.1 = value.0;
|
|
||||||
}
|
|
||||||
value.1
|
|
||||||
}
|
|
||||||
fn set(&mut self, map: &ChunkLevel, pos: ChunkPos, level: i8) {
|
|
||||||
let dx = (pos.x - self.0) as usize;
|
|
||||||
let dy = (pos.y - self.1) as usize;
|
|
||||||
debug_assert!(pos.x >= self.0 && pos.y >= self.1);
|
|
||||||
let value = &mut self.3[dx * self.2 + dy];
|
|
||||||
if value.0 == -127 {
|
|
||||||
value.0 = *map.get(&pos).unwrap_or(&ChunkLoading::MAX_LEVEL);
|
|
||||||
}
|
|
||||||
value.1 = level;
|
|
||||||
}
|
|
||||||
fn write(
|
|
||||||
&self,
|
|
||||||
map: &mut ChunkLevel,
|
|
||||||
change: &mut HashMapType<ChunkPos, (StagedChunkEnum, StagedChunkEnum)>,
|
|
||||||
) {
|
|
||||||
for i in 0..self.2 {
|
|
||||||
for j in 0..self.2 {
|
|
||||||
let value = self.3[i * self.2 + j];
|
|
||||||
if value.0 != value.1 {
|
|
||||||
let pos = ChunkPos::new(i as i32 + self.0, j as i32 + self.1);
|
|
||||||
if value.1 == ChunkLoading::MAX_LEVEL {
|
|
||||||
map.remove(&pos);
|
|
||||||
} else {
|
|
||||||
map.insert(pos, value.1);
|
|
||||||
}
|
|
||||||
let value = (
|
|
||||||
StagedChunkEnum::level_to_stage(value.0),
|
|
||||||
StagedChunkEnum::level_to_stage(value.1),
|
|
||||||
);
|
|
||||||
if value.0 == value.1 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
match change.entry(pos) {
|
|
||||||
Entry::Occupied(mut entry) => {
|
|
||||||
let i = entry.get_mut();
|
|
||||||
debug_assert_eq!(i.1, value.0);
|
|
||||||
if i.0 == value.1 {
|
|
||||||
entry.remove();
|
|
||||||
} else {
|
|
||||||
i.1 = value.1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Entry::Vacant(entry) => {
|
|
||||||
entry.insert(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ChunkLoading {
|
pub struct ChunkLoading {
|
||||||
pub is_priority_dirty: bool,
|
pub is_priority_dirty: bool,
|
||||||
pub pos_level: ChunkLevel,
|
pub pos_level: ChunkLevel,
|
||||||
@ -95,9 +15,6 @@ pub struct ChunkLoading {
|
|||||||
pub ticket: HashMapType<ChunkPos, Vec<i8>>, // TODO lifetime & id
|
pub ticket: HashMapType<ChunkPos, Vec<i8>>, // TODO lifetime & id
|
||||||
pub high_priority: Vec<ChunkPos>,
|
pub high_priority: Vec<ChunkPos>,
|
||||||
pub sender: Arc<LevelChannel>,
|
pub sender: Arc<LevelChannel>,
|
||||||
pub increase_update: VecDeque<(ChunkPos, i8)>,
|
|
||||||
pub decrease_update: VecDeque<(ChunkPos, i8)>,
|
|
||||||
cache: LevelCache,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ChunkLoading {
|
impl ChunkLoading {
|
||||||
@ -110,13 +27,13 @@ impl ChunkLoading {
|
|||||||
let Some(&level) = levels.iter().min() else {
|
let Some(&level) = levels.iter().min() else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let range = Self::MAX_LEVEL - level - 1;
|
let range = (Self::MAX_LEVEL - level - 1).max(0) as u8;
|
||||||
for dx in -range..=range {
|
for r in 0..=range {
|
||||||
for dy in -range..=range {
|
let ring_level = level + r as i8;
|
||||||
|
for &(dx, dy) in pumpkin_data::chunk_view_lut::get_chebyshev_ring(r) {
|
||||||
let new_pos = ticket_pos.add_raw(dx as i32, dy as i32);
|
let new_pos = ticket_pos.add_raw(dx as i32, dy as i32);
|
||||||
let level_from_source = level + dx.abs().max(dy.abs());
|
|
||||||
let i = temp.entry(new_pos).or_insert(Self::MAX_LEVEL);
|
let i = temp.entry(new_pos).or_insert(Self::MAX_LEVEL);
|
||||||
*i = min(*i, level_from_source);
|
*i = min(*i, ring_level);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -205,9 +122,36 @@ impl ChunkLoading {
|
|||||||
ticket: HashMapType::default(),
|
ticket: HashMapType::default(),
|
||||||
high_priority: Vec::new(),
|
high_priority: Vec::new(),
|
||||||
sender,
|
sender,
|
||||||
increase_update: VecDeque::default(),
|
}
|
||||||
decrease_update: VecDeque::default(),
|
}
|
||||||
cache: LevelCache::new(),
|
|
||||||
|
fn record_change(
|
||||||
|
change: &mut HashMapType<ChunkPos, (StagedChunkEnum, StagedChunkEnum)>,
|
||||||
|
pos: ChunkPos,
|
||||||
|
old_level: i8,
|
||||||
|
new_level: i8,
|
||||||
|
) {
|
||||||
|
if old_level == new_level {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let old_stage = StagedChunkEnum::level_to_stage(old_level);
|
||||||
|
let new_stage = StagedChunkEnum::level_to_stage(new_level);
|
||||||
|
if old_stage == new_stage {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
match change.entry(pos) {
|
||||||
|
Entry::Occupied(mut entry) => {
|
||||||
|
let i = entry.get_mut();
|
||||||
|
debug_assert_eq!(i.1, old_stage);
|
||||||
|
if i.0 == new_stage {
|
||||||
|
entry.remove();
|
||||||
|
} else {
|
||||||
|
i.1 = new_stage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Entry::Vacant(entry) => {
|
||||||
|
entry.insert((old_stage, new_stage));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,88 +174,13 @@ impl ChunkLoading {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_increase_update(&mut self) {
|
|
||||||
while let Some((pos, level)) = self.increase_update.pop_front() {
|
|
||||||
debug_assert!(level < Self::MAX_LEVEL);
|
|
||||||
if level > self.cache.get(&self.pos_level, pos) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
debug_assert_eq!(level, self.cache.get(&self.pos_level, pos));
|
|
||||||
let spread_level = level + 1;
|
|
||||||
if spread_level >= Self::MAX_LEVEL {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for dx in -1..2 {
|
|
||||||
for dy in -1..2 {
|
|
||||||
let new_pos = pos.add_raw(dx, dy);
|
|
||||||
if new_pos != pos {
|
|
||||||
self.check_then_push(new_pos, spread_level);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_then_push(&mut self, pos: ChunkPos, level: i8) {
|
|
||||||
debug_assert!(level < Self::MAX_LEVEL);
|
|
||||||
let old = self.cache.get(&self.pos_level, pos);
|
|
||||||
if old <= level {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
self.cache.set(&self.pos_level, pos, level);
|
|
||||||
self.increase_update.push_back((pos, level));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_decrease_update(&mut self, pos: ChunkPos, range: i32) {
|
|
||||||
while let Some((pos, level)) = self.decrease_update.pop_front() {
|
|
||||||
debug_assert!(level < Self::MAX_LEVEL);
|
|
||||||
let spread_level = level + 1;
|
|
||||||
for dx in -1..2 {
|
|
||||||
for dy in -1..2 {
|
|
||||||
let new_pos = pos.add_raw(dx, dy);
|
|
||||||
if new_pos == pos {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let new_pos_level = self.cache.get(&self.pos_level, new_pos);
|
|
||||||
if new_pos_level == Self::MAX_LEVEL {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
debug_assert!(new_pos_level <= spread_level);
|
|
||||||
if new_pos_level == spread_level {
|
|
||||||
self.cache.set(&self.pos_level, new_pos, Self::MAX_LEVEL);
|
|
||||||
if spread_level < Self::MAX_LEVEL {
|
|
||||||
self.decrease_update.push_back((new_pos, spread_level));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.increase_update.push_back((new_pos, new_pos_level));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (ticket_pos, levels) in &self.ticket {
|
|
||||||
if (ticket_pos.x - pos.x).abs() <= range && (ticket_pos.y - pos.y).abs() <= range {
|
|
||||||
let Some(&level) = levels.iter().min() else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
debug_assert!(level < Self::MAX_LEVEL);
|
|
||||||
let old = self.cache.get(&self.pos_level, *ticket_pos);
|
|
||||||
if old <= level {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
self.cache.set(&self.pos_level, *ticket_pos, level);
|
|
||||||
self.increase_update.push_back((*ticket_pos, level));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.run_increase_update();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_force_ticket(&mut self, pos: ChunkPos) {
|
pub fn add_force_ticket(&mut self, pos: ChunkPos) {
|
||||||
// debug!("add force ticket at {pos:?}");
|
// debug!("add force ticket at {pos:?}");
|
||||||
self.high_priority.push(pos);
|
self.high_priority.push(pos);
|
||||||
self.is_priority_dirty = true;
|
self.is_priority_dirty = true;
|
||||||
self.add_ticket(pos, Self::FULL_CHUNK_LEVEL);
|
self.add_ticket(pos, Self::FULL_CHUNK_LEVEL);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_force_ticket(&mut self, pos: ChunkPos) {
|
pub fn remove_force_ticket(&mut self, pos: ChunkPos) {
|
||||||
// debug!("remove force ticket at {pos:?}");
|
// debug!("remove force ticket at {pos:?}");
|
||||||
if let Some((index, _)) = self.high_priority.iter().find_position(|x| **x == pos) {
|
if let Some((index, _)) = self.high_priority.iter().find_position(|x| **x == pos) {
|
||||||
@ -320,6 +189,7 @@ impl ChunkLoading {
|
|||||||
self.is_priority_dirty = true;
|
self.is_priority_dirty = true;
|
||||||
self.remove_ticket(pos, Self::FULL_CHUNK_LEVEL);
|
self.remove_ticket(pos, Self::FULL_CHUNK_LEVEL);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_ticket(&mut self, pos: ChunkPos, level: i8) {
|
pub fn add_ticket(&mut self, pos: ChunkPos, level: i8) {
|
||||||
// debug!("add ticket at {pos:?} level {level}");
|
// debug!("add ticket at {pos:?} level {level}");
|
||||||
debug_assert!(level < Self::MAX_LEVEL);
|
debug_assert!(level < Self::MAX_LEVEL);
|
||||||
@ -336,15 +206,30 @@ impl ChunkLoading {
|
|||||||
if old <= level {
|
if old <= level {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.cache.clean(pos, level);
|
|
||||||
self.cache.set(&self.pos_level, pos, level);
|
|
||||||
|
|
||||||
debug_assert!(self.increase_update.is_empty());
|
let max_range = (Self::MAX_LEVEL - level - 1).max(0) as u8;
|
||||||
self.increase_update.push_back((pos, level));
|
for r in 0..=max_range {
|
||||||
self.run_increase_update();
|
let ring_level = level + r as i8;
|
||||||
self.cache.write(&mut self.pos_level, &mut self.change);
|
for &(dx, dy) in pumpkin_data::chunk_view_lut::get_chebyshev_ring(r) {
|
||||||
|
let p = pos.add_raw(dx as i32, dy as i32);
|
||||||
|
match self.pos_level.entry(p) {
|
||||||
|
Entry::Occupied(mut entry) => {
|
||||||
|
let cur = *entry.get();
|
||||||
|
if cur > ring_level {
|
||||||
|
*entry.get_mut() = ring_level;
|
||||||
|
Self::record_change(&mut self.change, p, cur, ring_level);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Entry::Vacant(entry) => {
|
||||||
|
entry.insert(ring_level);
|
||||||
|
Self::record_change(&mut self.change, p, Self::MAX_LEVEL, ring_level);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
debug_assert!(self.debug_check_error());
|
debug_assert!(self.debug_check_error());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_ticket(&mut self, pos: ChunkPos, level: i8) {
|
pub fn remove_ticket(&mut self, pos: ChunkPos, level: i8) {
|
||||||
// debug!("remove ticket at {pos:?} level {level}");
|
// debug!("remove ticket at {pos:?} level {level}");
|
||||||
debug_assert!(level < Self::MAX_LEVEL);
|
debug_assert!(level < Self::MAX_LEVEL);
|
||||||
@ -357,23 +242,55 @@ impl ChunkLoading {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
vec.remove(index);
|
vec.remove(index);
|
||||||
match self.pos_level.entry(pos) {
|
let source = *vec.iter().min().unwrap_or(&Self::MAX_LEVEL);
|
||||||
Entry::Occupied(entry) => {
|
if vec.is_empty() {
|
||||||
let old_level = *entry.get();
|
self.ticket.remove(&pos);
|
||||||
let source = *vec.iter().min().unwrap_or(&Self::MAX_LEVEL);
|
}
|
||||||
if vec.is_empty() {
|
|
||||||
self.ticket.remove(&pos);
|
let old_level = *self.pos_level.get(&pos).unwrap_or(&Self::MAX_LEVEL);
|
||||||
}
|
if level != old_level || source == level {
|
||||||
if level == old_level && source != level {
|
debug_assert!(self.debug_check_error());
|
||||||
self.cache.clean(pos, old_level);
|
return;
|
||||||
self.cache.set(&self.pos_level, pos, Self::MAX_LEVEL);
|
}
|
||||||
debug_assert!(self.decrease_update.is_empty());
|
|
||||||
self.decrease_update.push_back((pos, level));
|
let range = (Self::MAX_LEVEL - old_level - 1).max(0) as u8;
|
||||||
self.run_decrease_update(pos, (Self::MAX_LEVEL - level - 1) as i32);
|
|
||||||
self.cache.write(&mut self.pos_level, &mut self.change);
|
let nearby_tickets: Vec<(ChunkPos, i8)> = self
|
||||||
|
.ticket
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(&t_pos, levels)| {
|
||||||
|
let &min_lvl = levels.iter().min()?;
|
||||||
|
let t_range = (Self::MAX_LEVEL - min_lvl - 1).max(0) as i32;
|
||||||
|
((t_pos.x - pos.x).abs() <= range as i32 + t_range
|
||||||
|
&& (t_pos.y - pos.y).abs() <= range as i32 + t_range)
|
||||||
|
.then_some((t_pos, min_lvl))
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
for r in 0..=range {
|
||||||
|
let removed_contrib = old_level + r as i8;
|
||||||
|
for &(dx, dy) in pumpkin_data::chunk_view_lut::get_chebyshev_ring(r) {
|
||||||
|
let p = pos.add_raw(dx as i32, dy as i32);
|
||||||
|
let cur = *self.pos_level.get(&p).unwrap_or(&Self::MAX_LEVEL);
|
||||||
|
if cur == removed_contrib {
|
||||||
|
let mut new_level = Self::MAX_LEVEL;
|
||||||
|
for &(t_pos, t_level) in &nearby_tickets {
|
||||||
|
let dist = (p.x - t_pos.x).abs().max((p.y - t_pos.y).abs()) as i8;
|
||||||
|
let lvl = t_level + dist;
|
||||||
|
if lvl < new_level {
|
||||||
|
new_level = lvl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if new_level != cur {
|
||||||
|
if new_level == Self::MAX_LEVEL {
|
||||||
|
self.pos_level.remove(&p);
|
||||||
|
} else {
|
||||||
|
self.pos_level.insert(p, new_level);
|
||||||
|
}
|
||||||
|
Self::record_change(&mut self.change, p, cur, new_level);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Entry::Vacant(_) => panic!(),
|
|
||||||
}
|
}
|
||||||
debug_assert!(self.debug_check_error());
|
debug_assert!(self.debug_check_error());
|
||||||
}
|
}
|
||||||
|
|||||||
@ -618,48 +618,41 @@ impl GenerationSchedule {
|
|||||||
let stage = StagedChunkEnum::from(i);
|
let stage = StagedChunkEnum::from(i);
|
||||||
let dependency = stage.get_direct_dependencies();
|
let dependency = stage.get_direct_dependencies();
|
||||||
let radius = stage.get_direct_radius();
|
let radius = stage.get_direct_radius();
|
||||||
for dx in -radius..=radius {
|
self.unload_chunks.remove(&pos);
|
||||||
for dz in -radius..=radius {
|
let req_stage = dependency[0];
|
||||||
let new_pos = pos.add_raw(dx, dz);
|
let newly_created = Self::ensure_dependency_chain(
|
||||||
let req_stage = dependency[dx.abs().max(dz.abs()) as usize];
|
&mut self.graph,
|
||||||
self.unload_chunks.remove(&new_pos);
|
&mut self.queue,
|
||||||
if new_pos == pos {
|
&self.last_level,
|
||||||
let newly_created = Self::ensure_dependency_chain(
|
&self.last_high_priority,
|
||||||
&mut self.graph,
|
task,
|
||||||
&mut self.queue,
|
pos,
|
||||||
&self.last_level,
|
&mut holder,
|
||||||
&self.last_high_priority,
|
req_stage,
|
||||||
task,
|
);
|
||||||
new_pos,
|
for (stage_i, &created) in newly_created.iter().enumerate() {
|
||||||
&mut holder,
|
if created && stage_i > 1 {
|
||||||
req_stage,
|
let stage = StagedChunkEnum::from(stage_i as u8);
|
||||||
);
|
let dependency = stage.get_direct_dependencies();
|
||||||
for (stage_i, &created) in newly_created.iter().enumerate() {
|
let radius = stage.get_direct_radius();
|
||||||
if created && stage_i > 1 {
|
let cur_task = holder.tasks[stage_i];
|
||||||
let stage = StagedChunkEnum::from(stage_i as u8);
|
for r in 1..=(radius as u8) {
|
||||||
let dependency = stage.get_direct_dependencies();
|
let neighbor_req = dependency[r as usize];
|
||||||
let radius = stage.get_direct_radius();
|
for &(ndx, ndz) in
|
||||||
let cur_task = holder.tasks[stage_i];
|
pumpkin_data::chunk_view_lut::get_chebyshev_ring(r)
|
||||||
for ndx in -radius..=radius {
|
{
|
||||||
for ndz in -radius..=radius {
|
let neighbor_pos = pos.add_raw(ndx as i32, ndz as i32);
|
||||||
if ndx == 0 && ndz == 0 {
|
worklist.push_back((neighbor_pos, neighbor_req, cur_task));
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let neighbor_pos = new_pos.add_raw(ndx, ndz);
|
|
||||||
let neighbor_req = dependency
|
|
||||||
[ndx.abs().max(ndz.abs()) as usize];
|
|
||||||
worklist.push_back((
|
|
||||||
neighbor_pos,
|
|
||||||
neighbor_req,
|
|
||||||
cur_task,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for r in 1..=(radius as u8) {
|
||||||
|
let req_stage = dependency[r as usize];
|
||||||
|
for &(dx, dz) in pumpkin_data::chunk_view_lut::get_chebyshev_ring(r) {
|
||||||
|
let new_pos = pos.add_raw(dx as i32, dz as i32);
|
||||||
|
self.unload_chunks.remove(&new_pos);
|
||||||
worklist.push_back((new_pos, req_stage, task));
|
worklist.push_back((new_pos, req_stage, task));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -693,13 +686,10 @@ impl GenerationSchedule {
|
|||||||
let dependency = stage.get_direct_dependencies();
|
let dependency = stage.get_direct_dependencies();
|
||||||
let radius = stage.get_direct_radius();
|
let radius = stage.get_direct_radius();
|
||||||
let cur_task = holder.tasks[stage_i];
|
let cur_task = holder.tasks[stage_i];
|
||||||
for dx in -radius..=radius {
|
for r in 1..=(radius as u8) {
|
||||||
for dz in -radius..=radius {
|
let neighbor_req = dependency[r as usize];
|
||||||
if dx == 0 && dz == 0 {
|
for &(dx, dz) in pumpkin_data::chunk_view_lut::get_chebyshev_ring(r) {
|
||||||
continue;
|
let neighbor_pos = pos.add_raw(dx as i32, dz as i32);
|
||||||
}
|
|
||||||
let neighbor_pos = pos.add_raw(dx, dz);
|
|
||||||
let neighbor_req = dependency[dx.abs().max(dz.abs()) as usize];
|
|
||||||
worklist.push_back((neighbor_pos, neighbor_req, cur_task));
|
worklist.push_back((neighbor_pos, neighbor_req, cur_task));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -738,13 +728,11 @@ impl GenerationSchedule {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let dependencies = stage.get_direct_dependencies();
|
let dependencies = stage.get_direct_dependencies();
|
||||||
for dx in -radius..=radius {
|
for r in 1..=(radius as u8) {
|
||||||
for dz in -radius..=radius {
|
let req = dependencies[r as usize];
|
||||||
if dx == 0 && dz == 0 {
|
for &(dx, dz) in pumpkin_data::chunk_view_lut::get_chebyshev_ring(r) {
|
||||||
continue;
|
let neighbor = pos.add_raw(dx as i32, dz as i32);
|
||||||
}
|
worklist.push((neighbor, req));
|
||||||
let neighbor = pos.add_raw(dx, dz);
|
|
||||||
worklist.push((neighbor, dependencies[dx.abs().max(dz.abs()) as usize]));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -36,30 +36,6 @@ impl Cylindrical {
|
|||||||
(loading, unloading)
|
(loading, unloading)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
#[inline]
|
|
||||||
pub const fn left(&self) -> i32 {
|
|
||||||
self.center.x - self.view_distance.get() as i32 - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
#[inline]
|
|
||||||
pub const fn bottom(&self) -> i32 {
|
|
||||||
self.center.y - self.view_distance.get() as i32 - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
#[inline]
|
|
||||||
pub const fn right(&self) -> i32 {
|
|
||||||
self.center.x + self.view_distance.get() as i32 + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
#[inline]
|
|
||||||
pub const fn top(&self) -> i32 {
|
|
||||||
self.center.y + self.view_distance.get() as i32 + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
#[inline]
|
#[inline]
|
||||||
pub const fn is_within_distance(&self, x: i32, z: i32) -> bool {
|
pub const fn is_within_distance(&self, x: i32, z: i32) -> bool {
|
||||||
@ -111,17 +87,18 @@ mod test {
|
|||||||
|
|
||||||
for view_distance in 1..=32 {
|
for view_distance in 1..=32 {
|
||||||
cylinder.view_distance = NonZero::new(view_distance).unwrap();
|
cylinder.view_distance = NonZero::new(view_distance).unwrap();
|
||||||
|
let bound = view_distance as i32 + 1;
|
||||||
|
|
||||||
for chunk in cylinder.all_chunks_within() {
|
for chunk in cylinder.all_chunks_within() {
|
||||||
assert!(chunk.x >= cylinder.left() && chunk.x <= cylinder.right());
|
assert!(chunk.x >= -bound && chunk.x <= bound);
|
||||||
assert!(chunk.y >= cylinder.bottom() && chunk.y <= cylinder.top());
|
assert!(chunk.y >= -bound && chunk.y <= bound);
|
||||||
}
|
}
|
||||||
|
|
||||||
for x in (cylinder.left() - 2)..=(cylinder.right() + 2) {
|
for x in (-bound - 2)..=(bound + 2) {
|
||||||
for z in (cylinder.bottom() - 2)..=(cylinder.top() + 2) {
|
for z in (-bound - 2)..=(bound + 2) {
|
||||||
if cylinder.is_within_distance(x, z) {
|
if cylinder.is_within_distance(x, z) {
|
||||||
assert!(x >= cylinder.left() && x <= cylinder.right());
|
assert!(x >= -bound && x <= bound);
|
||||||
assert!(z >= cylinder.bottom() && z <= cylinder.top());
|
assert!(z >= -bound && z <= bound);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -419,7 +419,7 @@ fn move_piston(
|
|||||||
world.set_block_state(
|
world.set_block_state(
|
||||||
&target_pos,
|
&target_pos,
|
||||||
state,
|
state,
|
||||||
BlockFlags::NOTIFY_ALL | BlockFlags::MOVED,
|
BlockFlags::NOTIFY_LISTENERS | BlockFlags::MOVED,
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(moved_state) = moved_block_states.get(moved_blocks.len() - 1 - index) {
|
if let Some(moved_state) = moved_block_states.get(moved_blocks.len() - 1 - index) {
|
||||||
@ -449,7 +449,7 @@ fn move_piston(
|
|||||||
world.set_block_state(
|
world.set_block_state(
|
||||||
&extended_pos,
|
&extended_pos,
|
||||||
props.to_state_id(&Block::MOVING_PISTON),
|
props.to_state_id(&Block::MOVING_PISTON),
|
||||||
BlockFlags::NOTIFY_ALL | BlockFlags::MOVED,
|
BlockFlags::NOTIFY_LISTENERS | BlockFlags::MOVED,
|
||||||
);
|
);
|
||||||
let mut props = PistonHeadLikeProperties::default(&Block::PISTON_HEAD);
|
let mut props = PistonHeadLikeProperties::default(&Block::PISTON_HEAD);
|
||||||
props.facing = dir.to_facing();
|
props.facing = dir.to_facing();
|
||||||
@ -482,7 +482,9 @@ fn move_piston(
|
|||||||
state.id,
|
state.id,
|
||||||
BlockFlags::NOTIFY_LISTENERS,
|
BlockFlags::NOTIFY_LISTENERS,
|
||||||
);
|
);
|
||||||
world.update_neighbors(pos, None);
|
world
|
||||||
|
.block_registry
|
||||||
|
.update_neighbors(world, pos, BlockFlags::NOTIFY_LISTENERS);
|
||||||
world.block_registry.prepare(
|
world.block_registry.prepare(
|
||||||
world,
|
world,
|
||||||
pos,
|
pos,
|
||||||
@ -508,15 +510,22 @@ fn move_piston(
|
|||||||
block_state.id,
|
block_state.id,
|
||||||
BlockFlags::NOTIFY_LISTENERS,
|
BlockFlags::NOTIFY_LISTENERS,
|
||||||
);
|
);
|
||||||
world.update_neighbors(&broken_block_pos, None);
|
world.update_neighbors_at(
|
||||||
|
&broken_block_pos,
|
||||||
|
Block::from_state_id(block_state.id),
|
||||||
|
None,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for &moved_block_pos in moved_blocks.iter().rev() {
|
for (i, &moved_block_pos) in moved_blocks.iter().rev().enumerate() {
|
||||||
world.update_neighbors(&moved_block_pos, None);
|
if let Some(old_state) = moved_block_states.get(moved_blocks.len() - 1 - i) {
|
||||||
|
let old_block = Block::from_state_id(old_state.id);
|
||||||
|
world.update_neighbors_at(&moved_block_pos, old_block, None);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if extend {
|
if extend {
|
||||||
world.update_neighbors(&extended_pos, None);
|
world.update_neighbors_at(&extended_pos, &Block::PISTON_HEAD, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
true
|
true
|
||||||
|
|||||||
@ -241,13 +241,35 @@ pub fn get_power_on_side(
|
|||||||
) -> u8 {
|
) -> u8 {
|
||||||
let side_pos = pos.offset(side.to_block_direction().to_offset());
|
let side_pos = pos.offset(side.to_block_direction().to_offset());
|
||||||
let (side_block, side_state) = world.get_block_and_state(&side_pos);
|
let (side_block, side_state) = world.get_block_and_state(&side_pos);
|
||||||
if !only_gate || is_diode(side_block) {
|
let side_direction = side.to_block_direction();
|
||||||
world.block_registry.get_weak_redstone_power(
|
|
||||||
|
if only_gate {
|
||||||
|
if is_diode(side_block) {
|
||||||
|
world.block_registry.get_strong_redstone_power(
|
||||||
|
side_block,
|
||||||
|
world,
|
||||||
|
&side_pos,
|
||||||
|
side_state,
|
||||||
|
side_direction,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
} else if side_block == &Block::REDSTONE_BLOCK {
|
||||||
|
15
|
||||||
|
} else if side_block == &Block::REDSTONE_WIRE {
|
||||||
|
let props = RedstoneWireLikeProperties::from_state_id(side_state.id);
|
||||||
|
props.power
|
||||||
|
} else if world
|
||||||
|
.block_registry
|
||||||
|
.emits_redstone_power(side_block, side_state, side_direction)
|
||||||
|
{
|
||||||
|
world.block_registry.get_strong_redstone_power(
|
||||||
side_block,
|
side_block,
|
||||||
world,
|
world,
|
||||||
&side_pos,
|
&side_pos,
|
||||||
side_state,
|
side_state,
|
||||||
side.to_block_direction(),
|
side_direction,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
|
|||||||
@ -1,23 +1,106 @@
|
|||||||
use pumpkin_data::BlockStateId;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use pumpkin_data::block_properties::RailShapeStraight;
|
||||||
|
use pumpkin_data::entity::EntityType;
|
||||||
|
use pumpkin_data::{Block, BlockDirection, BlockStateId};
|
||||||
use pumpkin_macros::pumpkin_block;
|
use pumpkin_macros::pumpkin_block;
|
||||||
|
use pumpkin_util::math::boundingbox::BoundingBox;
|
||||||
|
use pumpkin_util::math::position::BlockPos;
|
||||||
|
use pumpkin_world::tick::TickPriority;
|
||||||
use pumpkin_world::world::BlockFlags;
|
use pumpkin_world::world::BlockFlags;
|
||||||
|
|
||||||
use crate::block::BlockBehaviour;
|
use crate::block::BlockBehaviour;
|
||||||
use crate::block::CanPlaceAtArgs;
|
use crate::block::CanPlaceAtArgs;
|
||||||
|
use crate::block::EmitsRedstonePowerArgs;
|
||||||
|
use crate::block::GetComparatorOutputArgs;
|
||||||
|
use crate::block::GetRedstonePowerArgs;
|
||||||
|
use crate::block::OnEntityCollisionArgs;
|
||||||
use crate::block::OnNeighborUpdateArgs;
|
use crate::block::OnNeighborUpdateArgs;
|
||||||
use crate::block::OnPlaceArgs;
|
use crate::block::OnPlaceArgs;
|
||||||
|
use crate::block::OnScheduledTickArgs;
|
||||||
|
use crate::block::OnStateReplacedArgs;
|
||||||
use crate::block::PlacedArgs;
|
use crate::block::PlacedArgs;
|
||||||
use crate::entity::EntityBase;
|
use crate::entity::EntityBase;
|
||||||
|
use crate::world::World;
|
||||||
|
|
||||||
use super::RailProperties;
|
use super::RailProperties;
|
||||||
use super::common::{
|
use super::common::{
|
||||||
can_place_rail_at, compute_placed_rail_shape, rail_placement_is_valid,
|
can_place_rail_at, compute_placed_rail_shape, rail_placement_is_valid,
|
||||||
update_flanking_rails_shape,
|
update_flanking_rails_shape,
|
||||||
};
|
};
|
||||||
|
use pumpkin_data::block_properties::DetectorRailProperties;
|
||||||
|
|
||||||
#[pumpkin_block("minecraft:detector_rail")]
|
#[pumpkin_block("minecraft:detector_rail")]
|
||||||
pub struct DetectorRailBlock;
|
pub struct DetectorRailBlock;
|
||||||
|
|
||||||
|
fn is_minecart(entity_type: &EntityType) -> bool {
|
||||||
|
entity_type == &EntityType::MINECART
|
||||||
|
|| entity_type == &EntityType::CHEST_MINECART
|
||||||
|
|| entity_type == &EntityType::COMMAND_BLOCK_MINECART
|
||||||
|
|| entity_type == &EntityType::FURNACE_MINECART
|
||||||
|
|| entity_type == &EntityType::HOPPER_MINECART
|
||||||
|
|| entity_type == &EntityType::SPAWNER_MINECART
|
||||||
|
|| entity_type == &EntityType::TNT_MINECART
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_power_to_connected(world: &Arc<World>, pos: &BlockPos, shape: RailShapeStraight) {
|
||||||
|
let (c1, c2) = match shape {
|
||||||
|
RailShapeStraight::NorthSouth => (pos.north(), pos.south()),
|
||||||
|
RailShapeStraight::EastWest => (pos.east(), pos.west()),
|
||||||
|
RailShapeStraight::AscendingEast => (pos.west(), pos.east().up()),
|
||||||
|
RailShapeStraight::AscendingWest => (pos.east(), pos.west().up()),
|
||||||
|
RailShapeStraight::AscendingNorth => (pos.south(), pos.north().up()),
|
||||||
|
RailShapeStraight::AscendingSouth => (pos.north(), pos.south().up()),
|
||||||
|
};
|
||||||
|
world.update_neighbors(&c1, None);
|
||||||
|
world.update_neighbors(&c2, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DetectorRailBlock {
|
||||||
|
pub fn check_pressed(
|
||||||
|
&self,
|
||||||
|
world: &Arc<World>,
|
||||||
|
pos: &BlockPos,
|
||||||
|
block: &Block,
|
||||||
|
state_id: BlockStateId,
|
||||||
|
) {
|
||||||
|
if !can_place_rail_at(world.as_ref(), pos) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search bounding box matching vanilla AABB (0.2 to 0.8 on X and Z, 0.0 to 0.8 on Y)
|
||||||
|
let search_box = BoundingBox::new_array([0.2, 0.0, 0.2], [0.8, 0.8, 0.8]).at_pos(*pos);
|
||||||
|
|
||||||
|
let entities = world.get_entities_at_box(&search_box);
|
||||||
|
let has_minecart = entities
|
||||||
|
.iter()
|
||||||
|
.any(|e| is_minecart(e.get_entity().entity_type));
|
||||||
|
|
||||||
|
let mut props = DetectorRailProperties::from_state_id(state_id);
|
||||||
|
let was_pressed = props.powered;
|
||||||
|
|
||||||
|
if has_minecart && !was_pressed {
|
||||||
|
props.powered = true;
|
||||||
|
let new_state_id = props.to_state_id(block);
|
||||||
|
world.set_block_state(pos, new_state_id, BlockFlags::NOTIFY_ALL);
|
||||||
|
update_power_to_connected(world, pos, props.shape);
|
||||||
|
world.update_neighbors(pos, None);
|
||||||
|
world.update_neighbors(&pos.down(), None);
|
||||||
|
} else if !has_minecart && was_pressed {
|
||||||
|
props.powered = false;
|
||||||
|
let new_state_id = props.to_state_id(block);
|
||||||
|
world.set_block_state(pos, new_state_id, BlockFlags::NOTIFY_ALL);
|
||||||
|
update_power_to_connected(world, pos, props.shape);
|
||||||
|
world.update_neighbors(pos, None);
|
||||||
|
world.update_neighbors(&pos.down(), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
if has_minecart {
|
||||||
|
world.schedule_block_tick(block, *pos, 20, TickPriority::Normal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl BlockBehaviour for DetectorRailBlock {
|
impl BlockBehaviour for DetectorRailBlock {
|
||||||
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
|
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
|
||||||
let mut rail_props = RailProperties::default(args.block);
|
let mut rail_props = RailProperties::default(args.block);
|
||||||
@ -35,6 +118,7 @@ impl BlockBehaviour for DetectorRailBlock {
|
|||||||
|
|
||||||
fn placed(&self, args: PlacedArgs<'_>) {
|
fn placed(&self, args: PlacedArgs<'_>) {
|
||||||
update_flanking_rails_shape(args.world, args.block, args.state_id, args.position);
|
update_flanking_rails_shape(args.world, args.block, args.state_id, args.position);
|
||||||
|
self.check_pressed(args.world, args.position, args.block, args.state_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_neighbor_update(&self, args: OnNeighborUpdateArgs<'_>) {
|
fn on_neighbor_update(&self, args: OnNeighborUpdateArgs<'_>) {
|
||||||
@ -44,7 +128,55 @@ impl BlockBehaviour for DetectorRailBlock {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn on_entity_collision(&self, args: OnEntityCollisionArgs<'_>) {
|
||||||
|
let props = DetectorRailProperties::from_state_id(args.state.id);
|
||||||
|
if !props.powered {
|
||||||
|
self.check_pressed(args.world, args.position, args.block, args.state.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
|
||||||
|
let state_id = args.world.get_block_state_id(args.position);
|
||||||
|
let props = DetectorRailProperties::from_state_id(state_id);
|
||||||
|
if props.powered {
|
||||||
|
self.check_pressed(args.world, args.position, args.block, state_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_state_replaced(&self, args: OnStateReplacedArgs<'_>) {
|
||||||
|
if !args.moved {
|
||||||
|
let props = DetectorRailProperties::from_state_id(args.old_state_id);
|
||||||
|
if props.powered {
|
||||||
|
args.world.update_neighbors(args.position, None);
|
||||||
|
args.world.update_neighbors(&args.position.down(), None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
|
fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
|
||||||
can_place_rail_at(args.block_accessor, args.position)
|
can_place_rail_at(args.block_accessor, args.position)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn emits_redstone_power(&self, _args: EmitsRedstonePowerArgs<'_>) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_weak_redstone_power(&self, args: GetRedstonePowerArgs<'_>) -> u8 {
|
||||||
|
let props = DetectorRailProperties::from_state_id(args.state.id);
|
||||||
|
if props.powered { 15 } else { 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_strong_redstone_power(&self, args: GetRedstonePowerArgs<'_>) -> u8 {
|
||||||
|
let props = DetectorRailProperties::from_state_id(args.state.id);
|
||||||
|
if props.powered && args.direction == BlockDirection::Up {
|
||||||
|
15
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_comparator_output(&self, args: GetComparatorOutputArgs<'_>) -> Option<u8> {
|
||||||
|
let props = DetectorRailProperties::from_state_id(args.state.id);
|
||||||
|
props.powered.then_some(0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1748,6 +1748,20 @@ impl LivingEntity {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Vanilla parity: the fall_damage gamerule only affects players.
|
||||||
|
if caller.get_player().is_some()
|
||||||
|
&& !self
|
||||||
|
.entity
|
||||||
|
.world
|
||||||
|
.load()
|
||||||
|
.level_info
|
||||||
|
.load()
|
||||||
|
.game_rules
|
||||||
|
.fall_damage
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if fall_distance >= 2.0
|
if fall_distance >= 2.0
|
||||||
&& let Some(player) = caller.get_player()
|
&& let Some(player) = caller.get_player()
|
||||||
{
|
{
|
||||||
|
|||||||
@ -4,7 +4,6 @@ use crate::{
|
|||||||
server::Server,
|
server::Server,
|
||||||
world::{
|
world::{
|
||||||
World,
|
World,
|
||||||
chunker::is_within_view_distance,
|
|
||||||
portal::{NetherPortal, PortalProcessor, PortalType, SourcePortalInfo},
|
portal::{NetherPortal, PortalProcessor, PortalType, SourcePortalInfo},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -2625,7 +2624,9 @@ impl Entity {
|
|||||||
&& self.age.load(Ordering::Relaxed) % Self::FREEZE_DAMAGE_INTERVAL == 0
|
&& self.age.load(Ordering::Relaxed) % Self::FREEZE_DAMAGE_INTERVAL == 0
|
||||||
{
|
{
|
||||||
let world = self.world.load_full();
|
let world = self.world.load_full();
|
||||||
if let Some(entity) = world.get_entity_by_id(self.entity_id) {
|
if world.level_info.load().game_rules.freeze_damage
|
||||||
|
&& let Some(entity) = world.get_entity_by_id(self.entity_id)
|
||||||
|
{
|
||||||
entity.damage(entity.as_ref(), 1.0, DamageType::FREEZE);
|
entity.damage(entity.as_ref(), 1.0, DamageType::FREEZE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3048,10 +3049,10 @@ impl Entity {
|
|||||||
} else {
|
} else {
|
||||||
let chunk_pos = self.chunk_pos.load();
|
let chunk_pos = self.chunk_pos.load();
|
||||||
for player in players.iter() {
|
for player in players.iter() {
|
||||||
let center = player.get_entity().chunk_pos.load();
|
if player
|
||||||
let view_distance = crate::world::chunker::get_view_distance(player).get() as i32;
|
.watched_section
|
||||||
|
.load()
|
||||||
if is_within_view_distance(chunk_pos, center, view_distance)
|
.is_within_distance(chunk_pos.x, chunk_pos.y)
|
||||||
&& let ClientPlatform::Bedrock(client) = player.client.as_ref()
|
&& let ClientPlatform::Bedrock(client) = player.client.as_ref()
|
||||||
{
|
{
|
||||||
bedrock_recipients.push(client);
|
bedrock_recipients.push(client);
|
||||||
@ -3097,10 +3098,10 @@ impl Entity {
|
|||||||
} else {
|
} else {
|
||||||
let chunk_pos = self.chunk_pos.load();
|
let chunk_pos = self.chunk_pos.load();
|
||||||
for player in players.iter() {
|
for player in players.iter() {
|
||||||
let center = player.get_entity().chunk_pos.load();
|
if player
|
||||||
let view_distance = crate::world::chunker::get_view_distance(player).get() as i32;
|
.watched_section
|
||||||
|
.load()
|
||||||
if is_within_view_distance(chunk_pos, center, view_distance)
|
.is_within_distance(chunk_pos.x, chunk_pos.y)
|
||||||
&& let ClientPlatform::Java(_) = player.client.as_ref()
|
&& let ClientPlatform::Java(_) = player.client.as_ref()
|
||||||
{
|
{
|
||||||
java_recipients.push(player);
|
java_recipients.push(player);
|
||||||
|
|||||||
@ -2567,11 +2567,9 @@ impl Player {
|
|||||||
if let Ok(listener) = self.chunk_listener.try_lock()
|
if let Ok(listener) = self.chunk_listener.try_lock()
|
||||||
&& let Ok(mut sender) = self.chunk_sender.try_lock()
|
&& let Ok(mut sender) = self.chunk_sender.try_lock()
|
||||||
{
|
{
|
||||||
let center = self.get_entity().chunk_pos.load();
|
let watched = self.watched_section.load();
|
||||||
let view_dist =
|
|
||||||
std::num::NonZeroI32::from(self.watched_section.load().view_distance).get();
|
|
||||||
while let Ok((pos, _)) = listener.try_recv() {
|
while let Ok((pos, _)) = listener.try_recv() {
|
||||||
if (pos.x - center.x).abs().max((pos.y - center.y).abs()) <= view_dist {
|
if watched.is_within_distance(pos.x, pos.y) {
|
||||||
sender.enqueue_chunk(pos);
|
sender.enqueue_chunk(pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2585,8 +2583,9 @@ impl Player {
|
|||||||
ClientPlatform::Bedrock(_) => JavaMinecraftVersion::V_1_20_2,
|
ClientPlatform::Bedrock(_) => JavaMinecraftVersion::V_1_20_2,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let view_distance = self.watched_section.load().view_distance;
|
||||||
let prepared_batch = self.chunk_sender.try_lock().ok().and_then(|mut sender| {
|
let prepared_batch = self.chunk_sender.try_lock().ok().and_then(|mut sender| {
|
||||||
sender.prepare_batch(&world.level, player_chunk, epoch, version)
|
sender.prepare_batch(&world.level, player_chunk, view_distance, epoch, version)
|
||||||
});
|
});
|
||||||
|
|
||||||
let total_sent_chunks = prepared_batch.map_or_else(
|
let total_sent_chunks = prepared_batch.map_or_else(
|
||||||
|
|||||||
@ -184,6 +184,22 @@ impl EntityBase for MinecartEntity {
|
|||||||
if is_activator_rail && let MinecartKind::Hopper(minecart) = &self.kind {
|
if is_activator_rail && let MinecartKind::Hopper(minecart) = &self.kind {
|
||||||
minecart.set_enabled(!powered);
|
minecart.set_enabled(!powered);
|
||||||
}
|
}
|
||||||
|
} else if block.id == Block::DETECTOR_RAIL.id
|
||||||
|
&& let Some(server) = world.server.upgrade()
|
||||||
|
{
|
||||||
|
world.block_registry.on_entity_collision(
|
||||||
|
block,
|
||||||
|
&world,
|
||||||
|
self,
|
||||||
|
&block_pos,
|
||||||
|
world.get_block_state(&block_pos),
|
||||||
|
&server,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_powered_rail || is_activator_rail {
|
||||||
|
let props = PoweredRailLikeProperties::from_state_id(state_id);
|
||||||
|
let powered = props.powered;
|
||||||
|
|
||||||
if powered {
|
if powered {
|
||||||
if is_powered_rail {
|
if is_powered_rail {
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
use rustc_hash::{FxHashMap, FxHashSet};
|
use rustc_hash::{FxHashMap, FxHashSet};
|
||||||
|
use std::num::NonZero;
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
|
|
||||||
use crate::net::java::chunk_data::{CChunkData, ChunkLightExt};
|
use crate::net::java::chunk_data::{CChunkData, ChunkLightExt};
|
||||||
@ -13,6 +14,7 @@ use pumpkin_protocol::{ClientPacket, MultiVersionJavaPacket};
|
|||||||
use pumpkin_util::math::vector2::Vector2;
|
use pumpkin_util::math::vector2::Vector2;
|
||||||
use pumpkin_util::version::JavaMinecraftVersion;
|
use pumpkin_util::version::JavaMinecraftVersion;
|
||||||
use pumpkin_world::chunk::ChunkData;
|
use pumpkin_world::chunk::ChunkData;
|
||||||
|
use pumpkin_world::cylindrical_chunk_iterator::Cylindrical;
|
||||||
use pumpkin_world::level::{Level, SyncChunk};
|
use pumpkin_world::level::{Level, SyncChunk};
|
||||||
|
|
||||||
use crate::net::ClientPlatform;
|
use crate::net::ClientPlatform;
|
||||||
@ -127,27 +129,68 @@ impl ChunkSender {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn collect_sorted_candidates(&self, level: &Level, center: Vector2<i32>) -> Vec<PreparedChunk> {
|
fn collect_sorted_candidates(
|
||||||
|
&self,
|
||||||
|
level: &Level,
|
||||||
|
center: Vector2<i32>,
|
||||||
|
view_distance: NonZero<u8>,
|
||||||
|
) -> Vec<PreparedChunk> {
|
||||||
let quota_limit = self.send_quota.floor() as usize;
|
let quota_limit = self.send_quota.floor() as usize;
|
||||||
let mut sorted: Vec<Vector2<i32>> = self.pending_chunks.iter().copied().collect();
|
|
||||||
|
|
||||||
sorted.sort_by_key(|pos| {
|
|
||||||
let dx = (pos.x - center.x).unsigned_abs() as u64;
|
|
||||||
let dz = (pos.y - center.y).unsigned_abs() as u64;
|
|
||||||
dx * dx + dz * dz
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut ready = Vec::with_capacity(quota_limit);
|
let mut ready = Vec::with_capacity(quota_limit);
|
||||||
for pos in sorted {
|
|
||||||
if ready.len() >= quota_limit {
|
// If pending_chunks is small, sorting it directly avoids scanning offsets.
|
||||||
break;
|
if self.pending_chunks.len() <= 16 {
|
||||||
|
let mut sorted: Vec<Vector2<i32>> = self.pending_chunks.iter().copied().collect();
|
||||||
|
sorted.sort_unstable_by_key(|pos| {
|
||||||
|
let dx = (pos.x - center.x).unsigned_abs() as u64;
|
||||||
|
let dz = (pos.y - center.y).unsigned_abs() as u64;
|
||||||
|
dx * dx + dz * dz
|
||||||
|
});
|
||||||
|
|
||||||
|
for pos in sorted {
|
||||||
|
if ready.len() >= quota_limit {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(chunk) = level.loaded_chunks.get(&pos) {
|
||||||
|
ready.push(PreparedChunk {
|
||||||
|
position: pos,
|
||||||
|
chunk: chunk.value().clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Re-use precompiled cylindrical chunk view LUT which is already sorted center-outward.
|
||||||
|
let offsets = Cylindrical::get_offsets(view_distance.get());
|
||||||
|
for &(dx, dy) in offsets {
|
||||||
|
if ready.len() >= quota_limit {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let pos = Vector2::new(center.x + i32::from(dx), center.y + i32::from(dy));
|
||||||
|
if self.pending_chunks.contains(&pos)
|
||||||
|
&& let Some(chunk) = level.loaded_chunks.get(&pos)
|
||||||
|
{
|
||||||
|
ready.push(PreparedChunk {
|
||||||
|
position: pos,
|
||||||
|
chunk: chunk.value().clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(chunk) = level.loaded_chunks.get(&pos) {
|
// Fallback for any pending chunks outside the precomputed table
|
||||||
ready.push(PreparedChunk {
|
if ready.is_empty() {
|
||||||
position: pos,
|
for &pos in &self.pending_chunks {
|
||||||
chunk: chunk.value().clone(),
|
if ready.len() >= quota_limit {
|
||||||
});
|
break;
|
||||||
|
}
|
||||||
|
if let Some(chunk) = level.loaded_chunks.get(&pos) {
|
||||||
|
ready.push(PreparedChunk {
|
||||||
|
position: pos,
|
||||||
|
chunk: chunk.value().clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,6 +201,7 @@ impl ChunkSender {
|
|||||||
&mut self,
|
&mut self,
|
||||||
level: &Level,
|
level: &Level,
|
||||||
player_chunk: Vector2<i32>,
|
player_chunk: Vector2<i32>,
|
||||||
|
view_distance: NonZero<u8>,
|
||||||
epoch: u32,
|
epoch: u32,
|
||||||
version: JavaMinecraftVersion,
|
version: JavaMinecraftVersion,
|
||||||
) -> Option<PreparedBatch> {
|
) -> Option<PreparedBatch> {
|
||||||
@ -173,7 +217,7 @@ impl ChunkSender {
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let candidates = self.collect_sorted_candidates(level, player_chunk);
|
let candidates = self.collect_sorted_candidates(level, player_chunk, view_distance);
|
||||||
if candidates.is_empty() {
|
if candidates.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -116,6 +116,10 @@ pub struct JavaClient {
|
|||||||
///
|
///
|
||||||
/// Whether we are waiting for a response after sending a keep alive packet.
|
/// Whether we are waiting for a response after sending a keep alive packet.
|
||||||
pub wait_for_keep_alive: AtomicBool,
|
pub wait_for_keep_alive: AtomicBool,
|
||||||
|
/// Set to `true` when any movement packet is received this tick.
|
||||||
|
/// On `SClientTickEnd` (≥1.21.4), if still `false`, the player's known
|
||||||
|
/// movement is zeroed (they stood still). Matches vanilla's `receivedMovementThisTick`.
|
||||||
|
pub received_movement_this_tick: AtomicBool,
|
||||||
/// The keep alive packet payload we send. The client should respond with the same id.
|
/// The keep alive packet payload we send. The client should respond with the same id.
|
||||||
pub keep_alive_id: AtomicCell<i64>,
|
pub keep_alive_id: AtomicCell<i64>,
|
||||||
/// The last time we sent a keep alive packet.
|
/// The last time we sent a keep alive packet.
|
||||||
@ -254,6 +258,7 @@ impl JavaClient {
|
|||||||
brand: ArcSwap::from_pointee(pending.brand),
|
brand: ArcSwap::from_pointee(pending.brand),
|
||||||
player: ArcSwap::from_pointee(None),
|
player: ArcSwap::from_pointee(None),
|
||||||
wait_for_keep_alive: AtomicBool::new(false),
|
wait_for_keep_alive: AtomicBool::new(false),
|
||||||
|
received_movement_this_tick: AtomicBool::new(false),
|
||||||
keep_alive_id: AtomicCell::new(0),
|
keep_alive_id: AtomicCell::new(0),
|
||||||
last_keep_alive_time: AtomicCell::new(Instant::now()),
|
last_keep_alive_time: AtomicCell::new(Instant::now()),
|
||||||
last_packet_time: AtomicCell::new(Instant::now()),
|
last_packet_time: AtomicCell::new(Instant::now()),
|
||||||
@ -1018,7 +1023,7 @@ impl JavaClient {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
id if id == SClientTickEnd::to_id(version) => {
|
id if id == SClientTickEnd::to_id(version) => {
|
||||||
// TODO
|
self.handle_client_tick_end(player);
|
||||||
}
|
}
|
||||||
id if id == STestInstanceBlockAction::to_id(version) => {
|
id if id == STestInstanceBlockAction::to_id(version) => {
|
||||||
self.handle_test_instance_block_action(
|
self.handle_test_instance_block_action(
|
||||||
|
|||||||
19
crates/pumpkin/src/net/java/play/client_tick_end.rs
Normal file
19
crates/pumpkin/src/net/java/play/client_tick_end.rs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
#[allow(clippy::wildcard_imports)]
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
impl JavaClient {
|
||||||
|
pub fn handle_client_tick_end(&self, player: &Player) {
|
||||||
|
// If no movement packet arrived this tick the player is standing still —
|
||||||
|
// zero out the known movement so consumers (e.g. riptide speed) see 0.
|
||||||
|
// Matches vanilla ServerGamePacketListenerImpl#handleClientTickEnd.
|
||||||
|
if !self
|
||||||
|
.received_movement_this_tick
|
||||||
|
.swap(false, Ordering::Relaxed)
|
||||||
|
{
|
||||||
|
player
|
||||||
|
.get_entity()
|
||||||
|
.movement
|
||||||
|
.store(Vector3::new(0.0, 0.0, 0.0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -223,6 +223,7 @@ pub mod chat_message;
|
|||||||
pub mod chunk_batch;
|
pub mod chunk_batch;
|
||||||
pub mod client_command;
|
pub mod client_command;
|
||||||
pub mod client_information;
|
pub mod client_information;
|
||||||
|
pub mod client_tick_end;
|
||||||
pub mod close_container;
|
pub mod close_container;
|
||||||
pub mod command_suggestion;
|
pub mod command_suggestion;
|
||||||
pub mod configuration_acknowledged;
|
pub mod configuration_acknowledged;
|
||||||
|
|||||||
@ -3,6 +3,9 @@ use super::*;
|
|||||||
|
|
||||||
impl JavaClient {
|
impl JavaClient {
|
||||||
pub fn handle_move_vehicle(&self, player: &Arc<Player>, packet: &SMoveVehicle) {
|
pub fn handle_move_vehicle(&self, player: &Arc<Player>, packet: &SMoveVehicle) {
|
||||||
|
// A movement packet was received this tick — tracked for SClientTickEnd zeroing.
|
||||||
|
self.received_movement_this_tick
|
||||||
|
.store(true, Ordering::Relaxed);
|
||||||
let entity = player.get_entity();
|
let entity = player.get_entity();
|
||||||
let last_pos = entity.pos.load();
|
let last_pos = entity.pos.load();
|
||||||
let pos = Vector3::new(packet.x, packet.y, packet.z);
|
let pos = Vector3::new(packet.x, packet.y, packet.z);
|
||||||
|
|||||||
@ -3,6 +3,9 @@ use super::*;
|
|||||||
|
|
||||||
impl JavaClient {
|
impl JavaClient {
|
||||||
pub fn handle_player_ground(&self, player: &Player, ground: &SSetPlayerGround) {
|
pub fn handle_player_ground(&self, player: &Player, ground: &SSetPlayerGround) {
|
||||||
|
// A movement packet was received this tick — tracked for SClientTickEnd zeroing.
|
||||||
|
self.received_movement_this_tick
|
||||||
|
.store(true, Ordering::Relaxed);
|
||||||
player
|
player
|
||||||
.living_entity
|
.living_entity
|
||||||
.entity
|
.entity
|
||||||
|
|||||||
@ -52,6 +52,9 @@ impl JavaClient {
|
|||||||
if !player.has_client_loaded() {
|
if !player.has_client_loaded() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// A movement packet was received this tick — tracked for SClientTickEnd zeroing.
|
||||||
|
self.received_movement_this_tick
|
||||||
|
.store(true, Ordering::Relaxed);
|
||||||
if player.get_entity().has_vehicle() {
|
if player.get_entity().has_vehicle() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -190,6 +193,9 @@ impl JavaClient {
|
|||||||
if !player.has_client_loaded() {
|
if !player.has_client_loaded() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// A movement packet was received this tick — tracked for SClientTickEnd zeroing.
|
||||||
|
self.received_movement_this_tick
|
||||||
|
.store(true, Ordering::Relaxed);
|
||||||
if player.get_entity().has_vehicle() {
|
if player.get_entity().has_vehicle() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,9 @@ impl JavaClient {
|
|||||||
if !player.has_client_loaded() {
|
if !player.has_client_loaded() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// A movement packet was received this tick — tracked for SClientTickEnd zeroing.
|
||||||
|
self.received_movement_this_tick
|
||||||
|
.store(true, Ordering::Relaxed);
|
||||||
if !rotation.yaw.is_finite() || !rotation.pitch.is_finite() {
|
if !rotation.yaw.is_finite() || !rotation.pitch.is_finite() {
|
||||||
self.try_kick(&TextComponent::translate_cross(
|
self.try_kick(&TextComponent::translate_cross(
|
||||||
translation::java::MULTIPLAYER_DISCONNECT_INVALID_PLAYER_MOVEMENT,
|
translation::java::MULTIPLAYER_DISCONNECT_INVALID_PLAYER_MOVEMENT,
|
||||||
|
|||||||
@ -49,18 +49,24 @@ impl ActiveChunkTracker {
|
|||||||
active_chunks: &mut FxHashSet<Vector2<i32>>,
|
active_chunks: &mut FxHashSet<Vector2<i32>>,
|
||||||
newly_active: &mut Vec<Vector2<i32>>,
|
newly_active: &mut Vec<Vector2<i32>>,
|
||||||
) {
|
) {
|
||||||
for dx in -area.simulation_distance..=area.simulation_distance {
|
let offsets = pumpkin_data::chunk_view_lut::get_chebyshev_square(
|
||||||
for dz in -area.simulation_distance..=area.simulation_distance {
|
area.simulation_distance.max(0) as u8,
|
||||||
self.add_chunk(area.center.add_raw(dx, dz), active_chunks, newly_active);
|
);
|
||||||
}
|
for &(dx, dz) in offsets {
|
||||||
|
self.add_chunk(
|
||||||
|
area.center.add_raw(dx as i32, dz as i32),
|
||||||
|
active_chunks,
|
||||||
|
newly_active,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_area(&mut self, area: ActivePlayerArea, active_chunks: &mut FxHashSet<Vector2<i32>>) {
|
fn remove_area(&mut self, area: ActivePlayerArea, active_chunks: &mut FxHashSet<Vector2<i32>>) {
|
||||||
for dx in -area.simulation_distance..=area.simulation_distance {
|
let offsets = pumpkin_data::chunk_view_lut::get_chebyshev_square(
|
||||||
for dz in -area.simulation_distance..=area.simulation_distance {
|
area.simulation_distance.max(0) as u8,
|
||||||
self.remove_chunk(area.center.add_raw(dx, dz), active_chunks);
|
);
|
||||||
}
|
for &(dx, dz) in offsets {
|
||||||
|
self.remove_chunk(area.center.add_raw(dx as i32, dz as i32), active_chunks);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,24 +81,26 @@ impl ActiveChunkTracker {
|
|||||||
match previous {
|
match previous {
|
||||||
None => self.add_area(area, active_chunks, newly_active),
|
None => self.add_area(area, active_chunks, newly_active),
|
||||||
Some(previous) if previous != area => {
|
Some(previous) if previous != area => {
|
||||||
for dx in -previous.simulation_distance..=previous.simulation_distance {
|
let prev_offsets = pumpkin_data::chunk_view_lut::get_chebyshev_square(
|
||||||
for dz in -previous.simulation_distance..=previous.simulation_distance {
|
previous.simulation_distance.max(0) as u8,
|
||||||
let pos = previous.center.add_raw(dx, dz);
|
);
|
||||||
if (pos.x - area.center.x).abs() > area.simulation_distance
|
for &(dx, dz) in prev_offsets {
|
||||||
|| (pos.y - area.center.y).abs() > area.simulation_distance
|
let pos = previous.center.add_raw(dx as i32, dz as i32);
|
||||||
{
|
if (pos.x - area.center.x).abs() > area.simulation_distance
|
||||||
self.remove_chunk(pos, active_chunks);
|
|| (pos.y - area.center.y).abs() > area.simulation_distance
|
||||||
}
|
{
|
||||||
|
self.remove_chunk(pos, active_chunks);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for dx in -area.simulation_distance..=area.simulation_distance {
|
let curr_offsets = pumpkin_data::chunk_view_lut::get_chebyshev_square(
|
||||||
for dz in -area.simulation_distance..=area.simulation_distance {
|
area.simulation_distance.max(0) as u8,
|
||||||
let pos = area.center.add_raw(dx, dz);
|
);
|
||||||
if (pos.x - previous.center.x).abs() > previous.simulation_distance
|
for &(dx, dz) in curr_offsets {
|
||||||
|| (pos.y - previous.center.y).abs() > previous.simulation_distance
|
let pos = area.center.add_raw(dx as i32, dz as i32);
|
||||||
{
|
if (pos.x - previous.center.x).abs() > previous.simulation_distance
|
||||||
self.add_chunk(pos, active_chunks, newly_active);
|
|| (pos.y - previous.center.y).abs() > previous.simulation_distance
|
||||||
}
|
{
|
||||||
|
self.add_chunk(pos, active_chunks, newly_active);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,16 +28,15 @@ pub fn get_view_distance(player: &Player) -> NonZero<u8> {
|
|||||||
.clamp(fallback, max_view_distance)
|
.clamp(fallback, max_view_distance)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks if the target chunk is within the view distance
|
// Checks if the target chunk is within Chebyshev distance (L_infinity) of the center chunk.
|
||||||
// of the center chunk. Uses Chebyshev distance.
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_within_view_distance(
|
pub fn is_within_chebyshev_distance(
|
||||||
center: Vector2<i32>,
|
center: Vector2<i32>,
|
||||||
target: Vector2<i32>,
|
target: Vector2<i32>,
|
||||||
view_distance: i32,
|
distance: i32,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
(target.x - center.x).abs().max((target.y - center.y).abs()) <= view_distance
|
(target.x - center.x).abs().max((target.y - center.y).abs()) <= distance
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_lines)]
|
#[allow(clippy::too_many_lines)]
|
||||||
@ -99,7 +98,7 @@ pub fn update_position(player: &Arc<Player>) {
|
|||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
let new_sim_level = (!is_spectator).then(|| {
|
let new_sim_level = (!is_spectator || spectators_generate_chunks).then(|| {
|
||||||
let sim_dist = world.server.upgrade().map_or(10, |s| {
|
let sim_dist = world.server.upgrade().map_or(10, |s| {
|
||||||
s.advanced_config.networking.java.simulation_distance.get()
|
s.advanced_config.networking.java.simulation_distance.get()
|
||||||
});
|
});
|
||||||
@ -173,7 +172,7 @@ pub fn update_position(player: &Arc<Player>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !loading_chunks.is_empty() {
|
if !loading_chunks.is_empty() {
|
||||||
world.spawn_world_entity_chunks(player.clone(), loading_chunks, new_chunk_center);
|
world.spawn_world_entity_chunks(player.clone(), loading_chunks);
|
||||||
}
|
}
|
||||||
world.entity_tracker.update_player_position(player, &world);
|
world.entity_tracker.update_player_position(player, &world);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,7 +24,7 @@ use crate::entity::player::Player;
|
|||||||
use crate::net::ClientPlatform;
|
use crate::net::ClientPlatform;
|
||||||
use crate::net::java::JavaClient;
|
use crate::net::java::JavaClient;
|
||||||
use crate::world::World;
|
use crate::world::World;
|
||||||
use crate::world::chunker::{get_view_distance, is_within_view_distance};
|
use crate::world::chunker::get_view_distance;
|
||||||
|
|
||||||
pub struct TrackedEntity {
|
pub struct TrackedEntity {
|
||||||
pub entity: Arc<dyn EntityBase>,
|
pub entity: Arc<dyn EntityBase>,
|
||||||
@ -113,8 +113,10 @@ impl TrackedEntity {
|
|||||||
let range_sq = visible_range_blocks * visible_range_blocks;
|
let range_sq = visible_range_blocks * visible_range_blocks;
|
||||||
|
|
||||||
let entity_chunk = self.entity.get_entity().chunk_pos.load();
|
let entity_chunk = self.entity.get_entity().chunk_pos.load();
|
||||||
let player_chunk = player_entity.chunk_pos.load();
|
let in_view = player
|
||||||
let in_view = is_within_view_distance(entity_chunk, player_chunk, player_vd);
|
.watched_section
|
||||||
|
.load()
|
||||||
|
.is_within_distance(entity_chunk.x, entity_chunk.y);
|
||||||
|
|
||||||
let is_visible = dist_sq <= range_sq && self.broadcast_to_player(player) && in_view;
|
let is_visible = dist_sq <= range_sq && self.broadcast_to_player(player) && in_view;
|
||||||
|
|
||||||
|
|||||||
@ -31,8 +31,7 @@ pub mod time;
|
|||||||
pub mod villager_poi;
|
pub mod villager_poi;
|
||||||
|
|
||||||
use crate::block::RandomTickArgs;
|
use crate::block::RandomTickArgs;
|
||||||
use crate::world::chunker::get_view_distance;
|
use crate::world::chunker::is_within_chebyshev_distance;
|
||||||
use crate::world::chunker::is_within_view_distance;
|
|
||||||
use crate::{block::BlockEvent, entity::item::ItemEntity};
|
use crate::{block::BlockEvent, entity::item::ItemEntity};
|
||||||
use crate::{
|
use crate::{
|
||||||
block::{
|
block::{
|
||||||
@ -447,8 +446,11 @@ impl World {
|
|||||||
let mut newly_active = Vec::new();
|
let mut newly_active = Vec::new();
|
||||||
let mut current_players = FxHashSet::default();
|
let mut current_players = FxHashSet::default();
|
||||||
|
|
||||||
|
let spectators_generate_chunks =
|
||||||
|
self.level_info.load().game_rules.spectators_generate_chunks;
|
||||||
|
|
||||||
for player in players.iter() {
|
for player in players.iter() {
|
||||||
if player.is_spectator() {
|
if player.is_spectator() && !spectators_generate_chunks {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let id = player.gameprofile.id;
|
let id = player.gameprofile.id;
|
||||||
@ -1351,7 +1353,7 @@ impl World {
|
|||||||
let chunk_pos = BlockPos::floored_v(*position).chunk_position();
|
let chunk_pos = BlockPos::floored_v(*position).chunk_position();
|
||||||
|
|
||||||
for player in self.players.load().iter() {
|
for player in self.players.load().iter() {
|
||||||
if is_within_view_distance(chunk_pos, player.get_entity().chunk_pos.load(), 1)
|
if is_within_chebyshev_distance(chunk_pos, player.get_entity().chunk_pos.load(), 1)
|
||||||
&& let ClientPlatform::Bedrock(client) = player.client.as_ref()
|
&& let ClientPlatform::Bedrock(client) = player.client.as_ref()
|
||||||
&& let Ok(data) = client.serialize_packet(&packet)
|
&& let Ok(data) = client.serialize_packet(&packet)
|
||||||
{
|
{
|
||||||
@ -1389,7 +1391,7 @@ impl World {
|
|||||||
let recipients = players.iter().filter(|p| {
|
let recipients = players.iter().filter(|p| {
|
||||||
let center = p.get_entity().chunk_pos.load();
|
let center = p.get_entity().chunk_pos.load();
|
||||||
// If the sound reaches their chunk, send it!
|
// If the sound reaches their chunk, send it!
|
||||||
is_within_view_distance(chunk_pos, center, audible_chunks)
|
is_within_chebyshev_distance(chunk_pos, center, audible_chunks)
|
||||||
});
|
});
|
||||||
|
|
||||||
let recipients_by_version = Self::collect_java_recipients_by_version(recipients);
|
let recipients_by_version = Self::collect_java_recipients_by_version(recipients);
|
||||||
@ -1419,7 +1421,7 @@ impl World {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let center = p.get_entity().chunk_pos.load();
|
let center = p.get_entity().chunk_pos.load();
|
||||||
is_within_view_distance(chunk_pos, center, audible_chunks)
|
is_within_chebyshev_distance(chunk_pos, center, audible_chunks)
|
||||||
});
|
});
|
||||||
|
|
||||||
let recipients_by_version = Self::collect_java_recipients_by_version(recipients);
|
let recipients_by_version = Self::collect_java_recipients_by_version(recipients);
|
||||||
@ -1705,9 +1707,9 @@ impl World {
|
|||||||
let mut java_recipients = Vec::new();
|
let mut java_recipients = Vec::new();
|
||||||
|
|
||||||
let recipients = players.iter().filter(|p| {
|
let recipients = players.iter().filter(|p| {
|
||||||
let center = p.get_entity().chunk_pos.load();
|
p.watched_section
|
||||||
let view_distance = get_view_distance(p).get() as i32;
|
.load()
|
||||||
is_within_view_distance(chunk_pos, center, view_distance)
|
.is_within_distance(chunk_pos.x, chunk_pos.y)
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut bedrock_packets = Vec::new();
|
let mut bedrock_packets = Vec::new();
|
||||||
@ -1784,9 +1786,10 @@ impl World {
|
|||||||
if !bedrock_water_packets.is_empty() {
|
if !bedrock_water_packets.is_empty() {
|
||||||
let players = self.players.load();
|
let players = self.players.load();
|
||||||
let recipients = players.iter().filter(|player| {
|
let recipients = players.iter().filter(|player| {
|
||||||
let center = player.get_entity().chunk_pos.load();
|
player
|
||||||
let view_distance = get_view_distance(player).get() as i32;
|
.watched_section
|
||||||
is_within_view_distance(chunk_pos, center, view_distance)
|
.load()
|
||||||
|
.is_within_distance(chunk_pos.x, chunk_pos.y)
|
||||||
});
|
});
|
||||||
for player in recipients {
|
for player in recipients {
|
||||||
if let ClientPlatform::Bedrock(client) = player.client.as_ref() {
|
if let ClientPlatform::Bedrock(client) = player.client.as_ref() {
|
||||||
@ -4625,22 +4628,13 @@ impl World {
|
|||||||
|
|
||||||
// NOTE: This function doesn't actually await on anything, it just spawns two tokio tasks
|
// NOTE: This function doesn't actually await on anything, it just spawns two tokio tasks
|
||||||
/// IMPORTANT: Chunks have to be non-empty
|
/// IMPORTANT: Chunks have to be non-empty
|
||||||
fn spawn_world_entity_chunks(
|
fn spawn_world_entity_chunks(self: &Arc<Self>, player: Arc<Player>, chunks: Vec<Vector2<i32>>) {
|
||||||
self: &Arc<Self>,
|
|
||||||
player: Arc<Player>,
|
|
||||||
chunks: Vec<Vector2<i32>>,
|
|
||||||
center_chunk: Vector2<i32>,
|
|
||||||
) {
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
let inst = std::time::Instant::now();
|
let inst = std::time::Instant::now();
|
||||||
|
|
||||||
// Sort such that the first chunks are closest to the center.
|
// Note: `chunks` originates from `Cylindrical::changed_chunks`, which is
|
||||||
let mut chunks = chunks;
|
// already ordered from closest to farthest from center by the precompiled
|
||||||
chunks.sort_unstable_by_key(|pos| {
|
// cylindrical chunk view LUT. No re-sorting needed.
|
||||||
let rel_x = pos.x - center_chunk.x;
|
|
||||||
let rel_z = pos.y - center_chunk.y;
|
|
||||||
rel_x * rel_x + rel_z * rel_z
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut entity_receiver = self.level.receive_entity_chunks(chunks);
|
let mut entity_receiver = self.level.receive_entity_chunks(chunks);
|
||||||
let level = self.level.clone();
|
let level = self.level.clone();
|
||||||
@ -5204,10 +5198,11 @@ impl World {
|
|||||||
|
|
||||||
let players = self.players.load();
|
let players = self.players.load();
|
||||||
for player in players.iter() {
|
for player in players.iter() {
|
||||||
let center = player.get_entity().chunk_pos.load();
|
if player
|
||||||
let view_distance = get_view_distance(player).get() as i32;
|
.watched_section
|
||||||
|
.load()
|
||||||
if is_within_view_distance(chunk_pos, center, view_distance) {
|
.is_within_distance(chunk_pos.x, chunk_pos.y)
|
||||||
|
{
|
||||||
player.client.try_enqueue_spawn_packet(entity);
|
player.client.try_enqueue_spawn_packet(entity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -7018,16 +7013,13 @@ impl World {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Broadcasts a packet to all players who currently have the target chunk loaded.
|
/// Broadcasts a packet to all players who currently have the target chunk loaded.
|
||||||
/// This uses highly optimized Chebyshev distance math (Chunk Grid) instead of floating point distance checks.
|
|
||||||
pub fn broadcast_to_chunk<P: ClientPacket>(&self, chunk_pos: Vector2<i32>, packet: &P) {
|
pub fn broadcast_to_chunk<P: ClientPacket>(&self, chunk_pos: Vector2<i32>, packet: &P) {
|
||||||
let players = self.players.load();
|
let players = self.players.load();
|
||||||
|
|
||||||
let recipients = players.iter().filter(|p| {
|
let recipients = players.iter().filter(|p| {
|
||||||
let center = p.get_entity().chunk_pos.load();
|
p.watched_section
|
||||||
let view_distance = get_view_distance(p).get() as i32;
|
.load()
|
||||||
|
.is_within_distance(chunk_pos.x, chunk_pos.y)
|
||||||
// Chebyshev distance (Minecraft's chunk loading shape)
|
|
||||||
is_within_view_distance(chunk_pos, center, view_distance)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let recipients_by_version = Self::collect_java_recipients_by_version(recipients);
|
let recipients_by_version = Self::collect_java_recipients_by_version(recipients);
|
||||||
@ -7041,9 +7033,10 @@ impl World {
|
|||||||
) {
|
) {
|
||||||
let players = self.players.load();
|
let players = self.players.load();
|
||||||
let recipients = players.iter().filter_map(|player| {
|
let recipients = players.iter().filter_map(|player| {
|
||||||
let center = player.get_entity().chunk_pos.load();
|
if player
|
||||||
let view_distance = get_view_distance(player).get() as i32;
|
.watched_section
|
||||||
if is_within_view_distance(chunk_pos, center, view_distance)
|
.load()
|
||||||
|
.is_within_distance(chunk_pos.x, chunk_pos.y)
|
||||||
&& let ClientPlatform::Bedrock(client) = player.client.as_ref()
|
&& let ClientPlatform::Bedrock(client) = player.client.as_ref()
|
||||||
{
|
{
|
||||||
return Some(client);
|
return Some(client);
|
||||||
@ -7064,9 +7057,9 @@ impl World {
|
|||||||
let mut bedrock_recipients = Vec::new();
|
let mut bedrock_recipients = Vec::new();
|
||||||
|
|
||||||
let recipients = players.iter().filter(|p| {
|
let recipients = players.iter().filter(|p| {
|
||||||
let center = p.get_entity().chunk_pos.load();
|
p.watched_section
|
||||||
let view_distance = get_view_distance(p).get() as i32;
|
.load()
|
||||||
is_within_view_distance(chunk_pos, center, view_distance)
|
.is_within_distance(chunk_pos.x, chunk_pos.y)
|
||||||
});
|
});
|
||||||
|
|
||||||
for p in recipients {
|
for p in recipients {
|
||||||
@ -7095,10 +7088,9 @@ impl World {
|
|||||||
if except.contains(&p.get_entity().entity_uuid) {
|
if except.contains(&p.get_entity().entity_uuid) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
let center = p.get_entity().chunk_pos.load();
|
p.watched_section
|
||||||
let view_distance = get_view_distance(p).get() as i32;
|
.load()
|
||||||
|
.is_within_distance(chunk_pos.x, chunk_pos.y)
|
||||||
is_within_view_distance(chunk_pos, center, view_distance)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let recipients_by_version = Self::collect_java_recipients_by_version(recipients);
|
let recipients_by_version = Self::collect_java_recipients_by_version(recipients);
|
||||||
@ -7117,10 +7109,9 @@ impl World {
|
|||||||
if except.contains(&p.get_entity().entity_uuid) {
|
if except.contains(&p.get_entity().entity_uuid) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
let center = p.get_entity().chunk_pos.load();
|
p.watched_section
|
||||||
let view_distance = get_view_distance(p).get() as i32;
|
.load()
|
||||||
|
.is_within_distance(chunk_pos.x, chunk_pos.y)
|
||||||
is_within_view_distance(chunk_pos, center, view_distance)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut java_recipients = Vec::new();
|
let mut java_recipients = Vec::new();
|
||||||
|
|||||||
@ -2,6 +2,7 @@ use proc_macro2::TokenStream;
|
|||||||
use quote::quote;
|
use quote::quote;
|
||||||
|
|
||||||
const MAX_VIEW_DISTANCE: u8 = 32;
|
const MAX_VIEW_DISTANCE: u8 = 32;
|
||||||
|
const MAX_CHEBYSHEV_RADIUS: u8 = 48;
|
||||||
|
|
||||||
pub fn build() -> TokenStream {
|
pub fn build() -> TokenStream {
|
||||||
let entries = (0..=MAX_VIEW_DISTANCE).map(|dist| {
|
let entries = (0..=MAX_VIEW_DISTANCE).map(|dist| {
|
||||||
@ -32,11 +33,81 @@ pub fn build() -> TokenStream {
|
|||||||
|
|
||||||
let array_len = MAX_VIEW_DISTANCE as usize + 1;
|
let array_len = MAX_VIEW_DISTANCE as usize + 1;
|
||||||
|
|
||||||
|
// Generate Chebyshev / square concentric ring offsets
|
||||||
|
let mut all_chebyshev_offsets = vec![];
|
||||||
|
let mut ring_ranges = vec![];
|
||||||
|
let mut square_ranges = vec![];
|
||||||
|
|
||||||
|
let mut current_idx = 0usize;
|
||||||
|
for r in 0..=MAX_CHEBYSHEV_RADIUS {
|
||||||
|
let r_i = r as i8;
|
||||||
|
let start = current_idx;
|
||||||
|
if r == 0 {
|
||||||
|
all_chebyshev_offsets.push((0i8, 0i8));
|
||||||
|
current_idx += 1;
|
||||||
|
} else {
|
||||||
|
// Top and bottom rows
|
||||||
|
for x in -r_i..=r_i {
|
||||||
|
all_chebyshev_offsets.push((x, -r_i));
|
||||||
|
all_chebyshev_offsets.push((x, r_i));
|
||||||
|
current_idx += 2;
|
||||||
|
}
|
||||||
|
// Left and right columns (excluding corners already added above)
|
||||||
|
for z in (-r_i + 1)..=(r_i - 1) {
|
||||||
|
all_chebyshev_offsets.push((-r_i, z));
|
||||||
|
all_chebyshev_offsets.push((r_i, z));
|
||||||
|
current_idx += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let end = current_idx;
|
||||||
|
ring_ranges.push((start, end));
|
||||||
|
square_ranges.push((0usize, end));
|
||||||
|
}
|
||||||
|
|
||||||
|
let chebyshev_total = all_chebyshev_offsets.len();
|
||||||
|
let chebyshev_elems = all_chebyshev_offsets
|
||||||
|
.into_iter()
|
||||||
|
.map(|(x, z)| quote!((#x, #z)));
|
||||||
|
|
||||||
|
let ring_bounds = ring_ranges.iter().map(|(s, e)| quote!((#s, #e)));
|
||||||
|
let square_bounds = square_ranges.iter().map(|(_, e)| quote!(#e));
|
||||||
|
let chebyshev_len = MAX_CHEBYSHEV_RADIUS as usize + 1;
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
/// The maximum supported view distance
|
/// The maximum supported view distance
|
||||||
pub const MAX_VIEW_DISTANCE: u8 = #MAX_VIEW_DISTANCE;
|
pub const MAX_VIEW_DISTANCE: u8 = #MAX_VIEW_DISTANCE;
|
||||||
|
|
||||||
/// Static precomputed lookup table for relative chunk offsets by view distance (0..=32).
|
/// Static precomputed lookup table for relative chunk offsets by view distance (0..=32).
|
||||||
pub static CHUNK_VIEW_LUT: [&[(i8, i8)]; #array_len] = [ #(#entries),* ];
|
pub static CHUNK_VIEW_LUT: [&[(i8, i8)]; #array_len] = [ #(#entries),* ];
|
||||||
|
|
||||||
|
/// The maximum supported Chebyshev / square radius (used for ticket levels and simulation distance)
|
||||||
|
pub const MAX_CHEBYSHEV_RADIUS: u8 = #MAX_CHEBYSHEV_RADIUS;
|
||||||
|
|
||||||
|
/// Flat backing storage for all concentric Chebyshev offsets up to `MAX_CHEBYSHEV_RADIUS`.
|
||||||
|
pub static CHEBYSHEV_OFFSETS: [(i8, i8); #chebyshev_total] = [ #(#chebyshev_elems),* ];
|
||||||
|
|
||||||
|
/// (start, end) index bounds in `CHEBYSHEV_OFFSETS` for each Chebyshev radius (0..=48).
|
||||||
|
pub static CHEBYSHEV_RING_BOUNDS: [(usize, usize); #chebyshev_len] = [ #(#ring_bounds),* ];
|
||||||
|
|
||||||
|
/// End index in `CHEBYSHEV_OFFSETS` for all chunks within Chebyshev radius (0..=48).
|
||||||
|
pub static CHEBYSHEV_SQUARE_BOUNDS: [usize; #chebyshev_len] = [ #(#square_bounds),* ];
|
||||||
|
|
||||||
|
/// Returns a precomputed slice of chunk offsets at exact Chebyshev radius `radius` (0..=48).
|
||||||
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
|
pub fn get_chebyshev_ring(radius: u8) -> &'static [(i8, i8)] {
|
||||||
|
let r = (radius as usize).min(MAX_CHEBYSHEV_RADIUS as usize);
|
||||||
|
let (start, end) = CHEBYSHEV_RING_BOUNDS[r];
|
||||||
|
&CHEBYSHEV_OFFSETS[start..end]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a precomputed slice of all chunk offsets within Chebyshev radius `radius` (0..=48), concentric-sorted.
|
||||||
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
|
pub fn get_chebyshev_square(radius: u8) -> &'static [(i8, i8)] {
|
||||||
|
let r = (radius as usize).min(MAX_CHEBYSHEV_RADIUS as usize);
|
||||||
|
let end = CHEBYSHEV_SQUARE_BOUNDS[r];
|
||||||
|
&CHEBYSHEV_OFFSETS[..end]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user