mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-12 18:35:16 -10:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
862c7e58de | ||
|
|
3af552f880 |
No files matched your search
@@ -3098,6 +3098,9 @@ struct Game<'a> {
|
||||
mod_chunk_tickets: HashMap<Dim, HashMap<(i32, i32), std::collections::BTreeSet<String>>>,
|
||||
/// Where item entities are, while the block entities tick (hoppers).
|
||||
item_columns: Option<machines::ItemColumns>,
|
||||
/// Where container carts and chest boats are (hoppers and droppers
|
||||
/// looking for a container entity), kept until either list is written.
|
||||
container_entity_columns: machines::ContainerEntityColumns,
|
||||
/// Where the mobs are, while the block entities tick (potent sulfur gas).
|
||||
pass_mobs: Option<potent_sulfur::PassMobs>,
|
||||
/// Block-entity positions retained between ticks for client effects.
|
||||
@@ -17521,6 +17524,7 @@ impl<'a> Game<'a> {
|
||||
mod_container_viewers: HashMap::new(),
|
||||
mod_chunk_tickets: HashMap::new(),
|
||||
item_columns: None,
|
||||
container_entity_columns: Default::default(),
|
||||
pass_mobs: None,
|
||||
spawner_tick_positions: Vec::new(),
|
||||
trial_spawner_tick_positions: Vec::new(),
|
||||
|
||||
@@ -480,6 +480,73 @@ impl ItemColumns {
|
||||
}
|
||||
}
|
||||
|
||||
/// The block columns that container carts' and chest boats' boxes reach. The
|
||||
/// lists' stamps say when they hold: after any write to either (a move, a
|
||||
/// spawn, a removal, items put in) the first question scans as before, and
|
||||
/// the second with nothing written in between builds them. A tick's hoppers
|
||||
/// share one build while nothing moves; the carts' own pass, which writes the
|
||||
/// carts between questions, never pays for one.
|
||||
#[derive(Default)]
|
||||
pub(super) struct ContainerEntityColumns {
|
||||
/// The carts' and boats' stamps when last asked, and whether the columns
|
||||
/// were built for them.
|
||||
seen: Option<([horizon_client::join_seq::ListStamp; 2], bool)>,
|
||||
cells: horizon_client::fast_hash::FxHashSet<(i32, i32)>,
|
||||
}
|
||||
impl ContainerEntityColumns {
|
||||
/// Whether a container entity's box may meet the unit box around `at`
|
||||
/// (`entity_container_at`'s test). A box that meets it shares a point with
|
||||
/// it, and that point's column is both in the box's columns and in the
|
||||
/// query's, so `false` is exact.
|
||||
pub(super) fn may_meet(
|
||||
&mut self,
|
||||
carts: &horizon_client::join_seq::JoinList<super::minecarts::Minecart>,
|
||||
boats: &horizon_client::join_seq::JoinList<super::boats::Boat>,
|
||||
at: [f64; 3],
|
||||
) -> bool {
|
||||
let stamps = [carts.stamp(), boats.stamp()];
|
||||
match self.seen {
|
||||
Some((seen, true)) if seen == stamps => {}
|
||||
Some((seen, false)) if seen == stamps => {
|
||||
self.seen = Some((stamps, true));
|
||||
self.build(carts, boats);
|
||||
}
|
||||
_ => {
|
||||
self.seen = Some((stamps, false));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
let (x0, x1) = ((at[0] - 0.5).floor() as i32, (at[0] + 0.5).floor() as i32);
|
||||
let (z0, z1) = ((at[2] - 0.5).floor() as i32, (at[2] + 0.5).floor() as i32);
|
||||
(x0..=x1).any(|x| (z0..=z1).any(|z| self.cells.contains(&(x, z))))
|
||||
}
|
||||
fn build(
|
||||
&mut self,
|
||||
carts: &horizon_client::join_seq::JoinList<super::minecarts::Minecart>,
|
||||
boats: &horizon_client::join_seq::JoinList<super::boats::Boat>,
|
||||
) {
|
||||
self.cells.clear();
|
||||
let boxes = carts
|
||||
.iter()
|
||||
.filter(|c| {
|
||||
matches!(
|
||||
c.kind,
|
||||
super::minecarts::CartKind::Chest | super::minecarts::CartKind::Hopper
|
||||
)
|
||||
})
|
||||
.map(|c| c.bounds())
|
||||
.chain(boats.iter().filter(|b| b.chest).map(|b| b.bounds()));
|
||||
for b in boxes {
|
||||
let (lo, hi) = (b.min_position(), b.max_position());
|
||||
for cx in lo.x.floor() as i32..=hi.x.floor() as i32 {
|
||||
for cz in lo.z.floor() as i32..=hi.z.floor() as i32 {
|
||||
self.cells.insert((cx, cz));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn bone_meal() -> ItemStack {
|
||||
ItemStack {
|
||||
item: "minecraft:bone_meal".into(),
|
||||
@@ -1183,6 +1250,14 @@ impl Game<'_> {
|
||||
&& lo.z < at[2] + 0.5
|
||||
&& hi.z > at[2] - 0.5
|
||||
};
|
||||
// Most hoppers have no container entity next to them: the columns
|
||||
// the query box spans rule that out without scanning every cart.
|
||||
if !self
|
||||
.container_entity_columns
|
||||
.may_meet(&self.carts, &self.boats, at)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let mut found: Vec<u32> = self
|
||||
.carts
|
||||
.iter()
|
||||
@@ -1666,6 +1741,88 @@ mod brewing_fuel_transfer_tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod container_entity_column_tests {
|
||||
use super::super::{
|
||||
boats::Boat,
|
||||
minecarts::{CartKind, Minecart},
|
||||
};
|
||||
use super::ContainerEntityColumns;
|
||||
use horizon_client::join_seq::JoinList;
|
||||
use horizon_core::geometry::Vec3;
|
||||
|
||||
/// `entity_container_at`'s scan: the container entities whose box meets
|
||||
/// the unit box around `at`.
|
||||
fn scan(carts: &[Minecart], boats: &[Boat], at: [f64; 3]) -> usize {
|
||||
let meets = |b: horizon_core::geometry::Aabb| {
|
||||
let (lo, hi) = (b.min_position(), b.max_position());
|
||||
(0..3).all(|i| {
|
||||
let (l, h) = ([lo.x, lo.y, lo.z][i], [hi.x, hi.y, hi.z][i]);
|
||||
l < at[i] + 0.5 && h > at[i] - 0.5
|
||||
})
|
||||
};
|
||||
carts
|
||||
.iter()
|
||||
.filter(|c| matches!(c.kind, CartKind::Chest | CartKind::Hopper) && meets(c.bounds()))
|
||||
.count()
|
||||
+ boats
|
||||
.iter()
|
||||
.filter(|b| b.chest && meets(b.bounds()))
|
||||
.count()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_container_entity_is_never_ruled_out_and_a_move_is_seen() {
|
||||
let mut seed = 7u64;
|
||||
let mut next = |range: f64| {
|
||||
seed = seed.wrapping_mul(6_364_136_223_846_793_005).wrapping_add(1);
|
||||
(seed >> 11) as f64 / (1u64 << 53) as f64 * range
|
||||
};
|
||||
let kinds = [CartKind::Chest, CartKind::Hopper, CartKind::Rideable];
|
||||
let mut carts = JoinList::default();
|
||||
let mut boats = JoinList::default();
|
||||
for id in 0..60u32 {
|
||||
let at = Vec3::new(next(24.0) - 12.0, 64.0, next(24.0) - 12.0);
|
||||
carts.push(Minecart::new(id, kinds[id as usize % 3], at));
|
||||
let boat = Boat::new(
|
||||
100 + id,
|
||||
"oak".into(),
|
||||
Vec3::new(at.x + 3.0, at.y, at.z),
|
||||
0.0,
|
||||
);
|
||||
boats.push(if id % 2 == 0 { boat.with_chest() } else { boat });
|
||||
}
|
||||
let mut columns = ContainerEntityColumns::default();
|
||||
let (mut met, mut ruled_out) = (0, 0);
|
||||
for x in -16..16 {
|
||||
for z in -16..16 {
|
||||
let at = [f64::from(x) + 0.5, 64.5, f64::from(z) + 0.5];
|
||||
let found = scan(&carts, &boats, at);
|
||||
if !columns.may_meet(&carts, &boats, at) {
|
||||
assert_eq!(found, 0, "ruled out a container entity at {at:?}");
|
||||
ruled_out += 1;
|
||||
}
|
||||
met += usize::from(found > 0);
|
||||
}
|
||||
}
|
||||
assert!(met > 0 && ruled_out > 0, "met {met}, ruled out {ruled_out}");
|
||||
|
||||
// A chest cart moved somewhere empty is found there on the next question.
|
||||
let far = [200.5, 64.5, 200.5];
|
||||
assert!(!columns.may_meet(&carts, &boats, far));
|
||||
let chest = carts
|
||||
.iter()
|
||||
.position(|c| c.kind == CartKind::Chest)
|
||||
.unwrap();
|
||||
carts[chest].position = Vec3::new(200.5, 64.0, 200.5);
|
||||
assert_eq!(scan(&carts, &boats, far), 1);
|
||||
// The first question after a write scans; the second builds.
|
||||
assert!(columns.may_meet(&carts, &boats, far));
|
||||
assert!(columns.may_meet(&carts, &boats, far));
|
||||
assert!(!columns.may_meet(&carts, &boats, [-300.5, 64.5, 0.5]));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod item_column_tests {
|
||||
use super::ItemColumns;
|
||||
|
||||
Reference in new issue
Block a user