mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 11:06:11 -10:00
WIP: tick orbs, primed TNT and falling blocks in join order between the other kinds
Not ready: the wind burst cells got worse (see the proof runs). Not pushed. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
This commit is contained in:
1 parent
f42ef42ef6
commit
3b7fe12fe2
8 files changed
+256
-142
No files matched your search
@@ -50,6 +50,7 @@ fn pack_files(world_tables: &Option<WorldTables>) -> horizon_world::loot::PackOv
|
||||
}
|
||||
|
||||
mod advancements;
|
||||
mod ordered_tick;
|
||||
mod advancements_screen;
|
||||
mod agent;
|
||||
mod agent_settings;
|
||||
@@ -2677,6 +2678,7 @@ struct Game<'a> {
|
||||
experience: Experience,
|
||||
orbs: horizon_client::join_seq::JoinList<ExperienceOrb>,
|
||||
xp_delay: u32,
|
||||
ordered: ordered_tick::OrderedTicks,
|
||||
last_level_up: u64,
|
||||
/// The arm swing (`LivingEntity.SwingState`) and how many swings started.
|
||||
swing: horizon_client::first_person::SwingState,
|
||||
@@ -5668,38 +5670,22 @@ impl Game<'_> {
|
||||
self.right = false;
|
||||
}
|
||||
/// FallingBlockEntity.tick: fall with 0.04 gravity and 0.98 drag, then land.
|
||||
fn tick_falling(&mut self) -> Result<()> {
|
||||
let falling = std::mem::take(&mut self.falling);
|
||||
let mut kept = Vec::with_capacity(falling.len());
|
||||
let mut collision_error = None;
|
||||
/// One falling block's `tick`: places or drops its block in its own tick,
|
||||
/// so a block falling onto one that just landed lands on top of it. `i` is
|
||||
/// its place in the list; the caller has checked it is ticking.
|
||||
fn tick_one_falling(&mut self, i: usize) -> Result<()> {
|
||||
let entity_drops = self.ui.rules.bool("entity_drops");
|
||||
// ServerLevel.tick: only entities in entity-ticking chunks tick.
|
||||
let ticking = self.entity_ticking_area();
|
||||
// FallingBlockEntity.tick places or drops each block in its own tick,
|
||||
// so a block falling onto one that just landed lands on top of it.
|
||||
for mut f in falling {
|
||||
if !ticking(f.position.x.floor() as i32, f.position.z.floor() as i32) {
|
||||
kept.push(f);
|
||||
continue;
|
||||
let mut f = self.falling.remove(i);
|
||||
let original = f.clone();
|
||||
match self.fall_step(&mut f, entity_drops) {
|
||||
Ok(FallStep::Falling) => self.falling.insert(i.min(self.falling.len()), f),
|
||||
Ok(FallStep::Gone) => {}
|
||||
Ok(FallStep::Dropped(at)) => self.drop_falling_block(f.state, at),
|
||||
Ok(FallStep::Landed(pos)) => self.finish_falling_block(f, pos),
|
||||
Err(error) => {
|
||||
self.falling.insert(i.min(self.falling.len()), original);
|
||||
return Err(error);
|
||||
}
|
||||
let original = f.clone();
|
||||
match self.fall_step(&mut f, entity_drops) {
|
||||
Ok(FallStep::Falling) => kept.push(f),
|
||||
Ok(FallStep::Gone) => {}
|
||||
Ok(FallStep::Dropped(at)) => self.drop_falling_block(f.state, at),
|
||||
Ok(FallStep::Landed(pos)) => self.finish_falling_block(f, pos),
|
||||
Err(error) => {
|
||||
kept.push(original);
|
||||
collision_error.get_or_insert(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Blocks that started falling meanwhile tick from the next tick on.
|
||||
let started = std::mem::replace(&mut self.falling, kept.into());
|
||||
self.falling.extend(started);
|
||||
self.process_level_events()?;
|
||||
if let Some(error) = collision_error {
|
||||
return Err(error);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -7527,9 +7513,11 @@ impl Game<'_> {
|
||||
self.native_joins_flush(false, 0);
|
||||
self.lan_mark_synced();
|
||||
}
|
||||
self.begin_ordered_ticks();
|
||||
if running {
|
||||
horizon_client::spans::time("tick_paintings", || self.tick_paintings());
|
||||
horizon_client::spans::time("tick_item_frames", || self.tick_item_frames());
|
||||
self.ordered_before(ordered_tick::first_join(&self.armor_stands))?;
|
||||
horizon_client::spans::time("tick_armor_stands", || self.tick_armor_stands())?;
|
||||
horizon_client::spans::time("tick_cushions", || self.tick_cushions());
|
||||
// ServerLevel.tick: the arrivals of this server tick are in the level
|
||||
@@ -7537,7 +7525,9 @@ impl Game<'_> {
|
||||
// own tick opens.
|
||||
self.land_portal_arrivals();
|
||||
self.handle_vehicle_portals();
|
||||
self.ordered_before(ordered_tick::first_join(&self.boats))?;
|
||||
horizon_client::spans::time("tick_boats", || self.tick_boats());
|
||||
self.ordered_before(ordered_tick::first_join(&self.carts))?;
|
||||
horizon_client::spans::time("tick_carts", || self.tick_carts());
|
||||
self.tick_vehicle_watch();
|
||||
let from = self.lan_synced_from();
|
||||
@@ -7548,7 +7538,9 @@ impl Game<'_> {
|
||||
self.tick_lodestone_compasses();
|
||||
horizon_client::spans::time("tick_bolts", || self.tick_bolts());
|
||||
horizon_client::spans::time("tick_fishing", || self.tick_fishing());
|
||||
self.ordered_before(ordered_tick::first_join(&self.tridents))?;
|
||||
horizon_client::spans::time("tick_tridents", || self.tick_tridents());
|
||||
self.ordered_before(ordered_tick::first_join(&self.rockets))?;
|
||||
horizon_client::spans::time("tick_rockets", || self.tick_rockets());
|
||||
horizon_client::spans::time("tick_ominous_item_spawners", || {
|
||||
self.tick_ominous_item_spawners()
|
||||
@@ -7559,6 +7551,7 @@ impl Game<'_> {
|
||||
self.handle_entity_portals();
|
||||
self.handle_end_gateway_portals();
|
||||
});
|
||||
self.ordered_before(ordered_tick::first_join(&self.dropped))?;
|
||||
horizon_client::spans::time("tick_items", || self.tick_items());
|
||||
if self.remote.is_none() {
|
||||
self.hoppers_catch_items();
|
||||
@@ -7572,11 +7565,14 @@ impl Game<'_> {
|
||||
// The ride the player's file names, once the vehicle is in the world.
|
||||
self.restore_root_vehicle();
|
||||
if running {
|
||||
self.ordered_before(ordered_tick::first_join(&self.mobs))?;
|
||||
horizon_client::spans::time("tick_mobs", || self.tick_mobs())?;
|
||||
// Dead, despawned or gone into a hive.
|
||||
self.native_leaves_flush();
|
||||
horizon_client::spans::time("recheck_golem_openers", || self.recheck_golem_openers());
|
||||
self.tick_mod_container_viewers();
|
||||
// Orbs, primed TNT and falling blocks still to tick, in join order.
|
||||
self.run_ordered_ticks(horizon_client::join_seq::JoinSeq::MAX)?;
|
||||
horizon_client::spans::time("tick_experience", || self.tick_experience());
|
||||
// Merged, despawned or picked up.
|
||||
self.native_leaves_flush();
|
||||
@@ -7603,8 +7599,6 @@ impl Game<'_> {
|
||||
self.keep_gateway_destinations();
|
||||
}
|
||||
if running {
|
||||
horizon_client::spans::time("tick_falling", || self.tick_falling())?;
|
||||
horizon_client::spans::time("tick_tnt", || self.tick_tnt())?;
|
||||
self.apply_late_wind_pushes();
|
||||
}
|
||||
if running {
|
||||
@@ -13512,17 +13506,15 @@ impl Game<'_> {
|
||||
}
|
||||
(min + self.world.random.next_int_bound(max - min + 1).unwrap_or(0)) as u32
|
||||
}
|
||||
fn tick_experience(&mut self) {
|
||||
if self.xp_delay > 0 {
|
||||
self.xp_delay -= 1;
|
||||
}
|
||||
/// `ExperienceOrb.followNearbyPlayer`'s candidates: each orb goes for the
|
||||
/// nearest living player within 8 blocks, the host's own or a guest. Each
|
||||
/// is the player's feet and the point the orb homes on.
|
||||
pub(super) fn orb_followed(&self) -> Vec<([f64; 3], [f64; 3])> {
|
||||
let spectator = self.spectator();
|
||||
let player = self
|
||||
.player
|
||||
.as_ref()
|
||||
.filter(|p| !p.health.dead_or_dying() && !spectator);
|
||||
// ExperienceOrb.followNearbyPlayer: each orb goes for the nearest
|
||||
// living player within 8 blocks, the host's own or a guest.
|
||||
let mut followed: Vec<([f64; 3], [f64; 3])> = player
|
||||
.map(|p| {
|
||||
let feet = [p.position.x, p.position.y, p.position.z];
|
||||
@@ -13544,6 +13536,19 @@ impl Game<'_> {
|
||||
}),
|
||||
);
|
||||
}
|
||||
followed
|
||||
}
|
||||
/// The orbs' tick, but for the movement: that is `tick_ordered_before`'s,
|
||||
/// in join order with the other kinds.
|
||||
fn tick_experience(&mut self) {
|
||||
if self.xp_delay > 0 {
|
||||
self.xp_delay -= 1;
|
||||
}
|
||||
let spectator = self.spectator();
|
||||
let player = self
|
||||
.player
|
||||
.as_ref()
|
||||
.filter(|p| !p.health.dead_or_dying() && !spectator);
|
||||
// Player.aiStep: the pickup area is the box inflated by (1, 0.5, 1).
|
||||
let bounds = player.map(|p| {
|
||||
let w = f64::from(p.dimensions.width) / 2.0;
|
||||
@@ -13560,38 +13565,6 @@ impl Game<'_> {
|
||||
],
|
||||
)
|
||||
});
|
||||
let mut orbs = std::mem::take(&mut self.orbs);
|
||||
// An orb has no random of its own here: lava draws from one made on
|
||||
// first use, apart from the level stream.
|
||||
let mut lava_random = None;
|
||||
let mut next_float = || {
|
||||
lava_random
|
||||
.get_or_insert_with(dispenser::entity_random)
|
||||
.next_float()
|
||||
};
|
||||
orbs.retain_mut(|o| {
|
||||
let distance = |feet: &[f64; 3]| -> f64 {
|
||||
(feet[0] - o.position.x).powi(2)
|
||||
+ (feet[1] - o.position.y).powi(2)
|
||||
+ (feet[2] - o.position.z).powi(2)
|
||||
};
|
||||
let target = followed
|
||||
.iter()
|
||||
.filter(|(feet, _)| distance(feet) < 64.0)
|
||||
.min_by(|a, b| distance(&a.0).total_cmp(&distance(&b.0)))
|
||||
.map(|(_, target)| *target);
|
||||
o.tick(
|
||||
&self.world,
|
||||
self.res.facts(),
|
||||
self.collision,
|
||||
target,
|
||||
self.remote.is_none(),
|
||||
&mut next_float,
|
||||
Some(self.border.geometry()),
|
||||
&self.res.motion().fluids,
|
||||
)
|
||||
});
|
||||
self.orbs = orbs;
|
||||
// ExperienceOrb.scanForMerges runs on the server only.
|
||||
if self.remote.is_none() {
|
||||
self.merge_orbs();
|
||||
@@ -18825,6 +18798,7 @@ impl<'a> Game<'a> {
|
||||
experience: Experience::default(),
|
||||
orbs: Default::default(),
|
||||
xp_delay: 0,
|
||||
ordered: Default::default(),
|
||||
last_level_up: 0,
|
||||
swing: Default::default(),
|
||||
swings_started: 0,
|
||||
|
||||
@@ -1188,6 +1188,7 @@ impl Game<'_> {
|
||||
}
|
||||
self.handle_mob_events(events)?;
|
||||
self.lan_guest_mob_events(guest_events);
|
||||
self.ordered_before(super::ordered_tick::first_join(&self.creatures.projectiles))?;
|
||||
self.tick_projectiles()?;
|
||||
self.tick_fangs();
|
||||
self.tick_allays();
|
||||
|
||||
@@ -155,8 +155,10 @@ pub(super) trait Entity: EntityRules {
|
||||
let b = self.explosion_box();
|
||||
f64::from((b.max_position().y - b.min_position().y) as f32 * 0.85f32)
|
||||
}
|
||||
/// Whether this kind ticks after the projectiles do (orbs, falling blocks,
|
||||
/// primed TNT), where vanilla ticks every entity in the order it joined.
|
||||
/// Whether this kind ticks after the projectiles do whatever its join
|
||||
/// number, where vanilla ticks every entity in the order it joined. No kind
|
||||
/// does since orbs, falling blocks and primed TNT tick in join order
|
||||
/// (`game/ordered_tick.rs`).
|
||||
fn ticks_after_projectiles(&self) -> bool {
|
||||
false
|
||||
}
|
||||
@@ -839,9 +841,6 @@ impl Entity for ItemEntity {
|
||||
}
|
||||
|
||||
impl Entity for ExperienceOrb {
|
||||
fn ticks_after_projectiles(&self) -> bool {
|
||||
true
|
||||
}
|
||||
fn native_type(&self) -> Cow<'static, str> {
|
||||
"minecraft:experience_orb".into()
|
||||
}
|
||||
@@ -1118,9 +1117,6 @@ impl Entity for ArmorStand {
|
||||
}
|
||||
|
||||
impl Entity for PrimedTnt {
|
||||
fn ticks_after_projectiles(&self) -> bool {
|
||||
true
|
||||
}
|
||||
fn native_type(&self) -> Cow<'static, str> {
|
||||
"minecraft:tnt".into()
|
||||
}
|
||||
@@ -1178,9 +1174,6 @@ impl Entity for PrimedTnt {
|
||||
}
|
||||
|
||||
impl Entity for FallingBlock {
|
||||
fn ticks_after_projectiles(&self) -> bool {
|
||||
true
|
||||
}
|
||||
fn native_type(&self) -> Cow<'static, str> {
|
||||
"minecraft:falling_block".into()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
//! PURPOSE: ticks experience orbs, primed TNT and falling blocks in the order
|
||||
//! they joined the level, between the other kinds' batches.
|
||||
//! WHY: `ServerLevel.tick` ticks every entity in join order, one kind among
|
||||
//! the rest; these three used to tick as three fixed batches (orbs after the
|
||||
//! mobs, falling blocks and TNT after the gateways), so a TNT that joined
|
||||
//! before a boat blew up after the boat moved (#6509, tick order slice 2).
|
||||
//! REFS: vanilla `ServerLevel.tick` (`entityTickList.forEach`),
|
||||
//! `EntityTickList`; `join_seq.rs`; `tools/test_tick_order_parity.py`.
|
||||
//! LOGS: none. READS-BY: `Game::tick_world`, `tick_mobs`.
|
||||
//! ENDS-WHEN: every kind ticks from the join-order walk.
|
||||
|
||||
use super::{Game, Result};
|
||||
use horizon_client::join_seq::{JoinSeq, Joined};
|
||||
use horizon_core::random::RandomSource as _;
|
||||
|
||||
/// What this tick has ticked of the three kinds.
|
||||
#[derive(Default, Clone)]
|
||||
pub(super) struct OrderedTicks {
|
||||
/// Joins at or above this started after the tick began; they tick next tick.
|
||||
born: JoinSeq,
|
||||
/// The last join number ticked, if any: later ones are still to come.
|
||||
done: Option<JoinSeq>,
|
||||
/// The orbs' lava random, made on first use.
|
||||
lava_random: Option<horizon_core::random::LegacyRandom>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum Pending {
|
||||
Orb,
|
||||
Tnt,
|
||||
Falling,
|
||||
}
|
||||
|
||||
/// The lowest join number in a batch, or none when it is empty.
|
||||
pub(super) fn first_join<T: Joined>(list: &[T]) -> Option<JoinSeq> {
|
||||
list.iter().map(Joined::join_seq).min()
|
||||
}
|
||||
|
||||
impl Game<'_> {
|
||||
/// A level tick starts: what has joined so far ticks this tick.
|
||||
pub(super) fn begin_ordered_ticks(&mut self) {
|
||||
self.ordered = OrderedTicks {
|
||||
born: JoinSeq::upcoming(),
|
||||
done: None,
|
||||
lava_random: None,
|
||||
};
|
||||
}
|
||||
|
||||
/// Ticks, one at a time in join order, every orb, primed TNT and falling
|
||||
/// block that joined before `cut` and has not ticked this tick.
|
||||
pub(super) fn run_ordered_ticks(&mut self, cut: JoinSeq) -> Result<()> {
|
||||
let mut first_error = None;
|
||||
let mut followed = None;
|
||||
let mut events = false;
|
||||
let entity_ticking = self.entity_ticking_area();
|
||||
loop {
|
||||
let (done, born) = (self.ordered.done, self.ordered.born);
|
||||
let wanted = |j: JoinSeq| done.is_none_or(|d| j > d) && j < cut && j < born;
|
||||
let mut next: Option<(JoinSeq, Pending)> = None;
|
||||
let mut offer = |j: JoinSeq, kind: Pending| {
|
||||
if wanted(j) && next.is_none_or(|(n, _)| j < n) {
|
||||
next = Some((j, kind));
|
||||
}
|
||||
};
|
||||
self.orbs.iter().for_each(|e| offer(e.join, Pending::Orb));
|
||||
self.tnts.iter().for_each(|e| offer(e.join, Pending::Tnt));
|
||||
self.falling
|
||||
.iter()
|
||||
.for_each(|e| offer(e.join, Pending::Falling));
|
||||
let Some((join, kind)) = next else { break };
|
||||
self.ordered.done = Some(join);
|
||||
// ServerLevel.tick: only entities in entity-ticking chunks tick.
|
||||
match kind {
|
||||
Pending::Tnt => {
|
||||
let Some(i) = self.tnts.iter().position(|e| e.join == join) else {
|
||||
continue;
|
||||
};
|
||||
let at = self.tnts[i].position;
|
||||
if entity_ticking(at.x.floor() as i32, at.z.floor() as i32) {
|
||||
if let Err(e) = self.tick_one_tnt(i) {
|
||||
first_error.get_or_insert(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Pending::Falling => {
|
||||
let Some(i) = self.falling.iter().position(|e| e.join == join) else {
|
||||
continue;
|
||||
};
|
||||
let at = self.falling[i].position;
|
||||
if entity_ticking(at.x.floor() as i32, at.z.floor() as i32) {
|
||||
events = true;
|
||||
if let Err(e) = self.tick_one_falling(i) {
|
||||
first_error.get_or_insert(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Pending::Orb => {
|
||||
let Some(i) = self.orbs.iter().position(|e| e.join == join) else {
|
||||
continue;
|
||||
};
|
||||
let followed = followed.get_or_insert_with(|| self.orb_followed());
|
||||
let o = &mut self.orbs[i];
|
||||
let distance = |feet: &[f64; 3]| -> f64 {
|
||||
(feet[0] - o.position.x).powi(2)
|
||||
+ (feet[1] - o.position.y).powi(2)
|
||||
+ (feet[2] - o.position.z).powi(2)
|
||||
};
|
||||
let target = followed
|
||||
.iter()
|
||||
.filter(|(feet, _)| distance(feet) < 64.0)
|
||||
.min_by(|a, b| distance(&a.0).total_cmp(&distance(&b.0)))
|
||||
.map(|(_, target)| *target);
|
||||
// An orb has no random of its own here: lava draws from one
|
||||
// made on first use, apart from the level stream.
|
||||
let random = &mut self.ordered.lava_random;
|
||||
let mut next_float = || {
|
||||
random
|
||||
.get_or_insert_with(super::dispenser::entity_random)
|
||||
.next_float()
|
||||
};
|
||||
let stays = o.tick(
|
||||
&self.world,
|
||||
self.res.facts(),
|
||||
self.collision,
|
||||
target,
|
||||
self.remote.is_none(),
|
||||
&mut next_float,
|
||||
Some(self.border.geometry()),
|
||||
&self.res.motion().fluids,
|
||||
);
|
||||
if !stays {
|
||||
self.orbs.remove(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if events && let Err(e) = self.process_level_events() {
|
||||
first_error.get_or_insert(e);
|
||||
}
|
||||
first_error.map_or(Ok(()), Err)
|
||||
}
|
||||
|
||||
/// Before a batch of another kind ticks: the three kinds' entities that
|
||||
/// joined before its first one tick first.
|
||||
pub(super) fn ordered_before(&mut self, first: Option<JoinSeq>) -> Result<()> {
|
||||
match first {
|
||||
Some(first) => self.run_ordered_ticks(first),
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -229,65 +229,53 @@ impl Game<'_> {
|
||||
/// `PrimedTnt.tick` for every primed TNT, in order; an expired fuse
|
||||
/// explodes immediately (later TNT in the list feel its knockback this
|
||||
/// tick). TNT primed by an explosion starts ticking next tick.
|
||||
pub(super) fn tick_tnt(&mut self) -> Result<()> {
|
||||
let mut count = self.tnts.len();
|
||||
let mut i = 0;
|
||||
// ServerLevel.tick: only entities in entity-ticking chunks tick.
|
||||
let ticking = self.entity_ticking_area();
|
||||
while i < count {
|
||||
let t = &mut self.tnts[i];
|
||||
if !ticking(t.position.x.floor() as i32, t.position.z.floor() as i32) {
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
let tops = t.tick(
|
||||
&self.world,
|
||||
self.collision,
|
||||
&self.pack.assets,
|
||||
self.res.facts(),
|
||||
&self.res.motion().fluids,
|
||||
Some(self.border.geometry()),
|
||||
)?;
|
||||
for pos in tops {
|
||||
self.bubble_column_particles(pos);
|
||||
}
|
||||
let t = &mut self.tnts[i];
|
||||
if t.fuse <= 0 {
|
||||
let center = [
|
||||
t.position.x,
|
||||
t.position.y + PrimedTnt::SIZE * 0.0625,
|
||||
t.position.z,
|
||||
];
|
||||
let bounds = t.bounds();
|
||||
let (owner, guest, power) = (t.owner, t.guest, t.power);
|
||||
let in_water = self.bounds_in_water(bounds);
|
||||
self.tnts.remove(i);
|
||||
count -= 1;
|
||||
// ExplosionInteraction.TNT: the tnt_explosion_drop_decay rule.
|
||||
let interaction = self.destroy_type("tnt_explosion_drop_decay");
|
||||
self.combat.explosion_in_water = in_water;
|
||||
// `Explosion.getIndirectSourceEntity`: a player-lit TNT names
|
||||
// its igniter, which `blast_attacker` and the damage type read.
|
||||
self.combat.blast_guest = guest;
|
||||
self.combat.blast_owner = tnt_blast_owner(
|
||||
owner,
|
||||
|| {
|
||||
self.lan
|
||||
.as_ref()
|
||||
.and_then(|host| self.player_attacker(host, guest))
|
||||
},
|
||||
|id| self.mob_display_name(id),
|
||||
);
|
||||
let result = self.with_hive_emptying_blast(|g| {
|
||||
g.server_explode(center, power, interaction, false)
|
||||
});
|
||||
self.combat.blast_owner = None;
|
||||
self.combat.blast_guest = None;
|
||||
self.combat.explosion_in_water = false;
|
||||
result?;
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
/// One primed TNT's `tick`: it moves, and explodes when its fuse is out.
|
||||
/// `i` is its place in the list; the caller has checked it is ticking.
|
||||
pub(super) fn tick_one_tnt(&mut self, i: usize) -> Result<()> {
|
||||
let t = &mut self.tnts[i];
|
||||
let tops = t.tick(
|
||||
&self.world,
|
||||
self.collision,
|
||||
&self.pack.assets,
|
||||
self.res.facts(),
|
||||
&self.res.motion().fluids,
|
||||
Some(self.border.geometry()),
|
||||
)?;
|
||||
for pos in tops {
|
||||
self.bubble_column_particles(pos);
|
||||
}
|
||||
let t = &mut self.tnts[i];
|
||||
if t.fuse <= 0 {
|
||||
let center = [
|
||||
t.position.x,
|
||||
t.position.y + PrimedTnt::SIZE * 0.0625,
|
||||
t.position.z,
|
||||
];
|
||||
let bounds = t.bounds();
|
||||
let (owner, guest, power) = (t.owner, t.guest, t.power);
|
||||
let in_water = self.bounds_in_water(bounds);
|
||||
self.tnts.remove(i);
|
||||
// ExplosionInteraction.TNT: the tnt_explosion_drop_decay rule.
|
||||
let interaction = self.destroy_type("tnt_explosion_drop_decay");
|
||||
self.combat.explosion_in_water = in_water;
|
||||
// `Explosion.getIndirectSourceEntity`: a player-lit TNT names
|
||||
// its igniter, which `blast_attacker` and the damage type read.
|
||||
self.combat.blast_guest = guest;
|
||||
self.combat.blast_owner = tnt_blast_owner(
|
||||
owner,
|
||||
|| {
|
||||
self.lan
|
||||
.as_ref()
|
||||
.and_then(|host| self.player_attacker(host, guest))
|
||||
},
|
||||
|id| self.mob_display_name(id),
|
||||
);
|
||||
let result = self
|
||||
.with_hive_emptying_blast(|g| g.server_explode(center, power, interaction, false));
|
||||
self.combat.blast_owner = None;
|
||||
self.combat.blast_guest = None;
|
||||
self.combat.explosion_in_water = false;
|
||||
result?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -651,7 +651,7 @@ mod tests {
|
||||
power: 4.0,
|
||||
join: Default::default(),
|
||||
};
|
||||
assert!(Entity::ticks_after_projectiles(&tnt));
|
||||
assert!(!Entity::ticks_after_projectiles(&tnt));
|
||||
let arrow = Projectile::new(ProjectileKind::Snowball, Vec3::ZERO, None);
|
||||
assert!(!Entity::ticks_after_projectiles(&arrow));
|
||||
}
|
||||
|
||||
@@ -27,6 +27,13 @@ impl JoinSeq {
|
||||
fn next() -> Self {
|
||||
Self(NEXT.fetch_add(1, Ordering::Relaxed))
|
||||
}
|
||||
/// Above every number a join can get.
|
||||
pub const MAX: Self = Self(u64::MAX);
|
||||
/// The number the next join will get or a later one: every entity that has
|
||||
/// joined so far is below it.
|
||||
pub fn upcoming() -> Self {
|
||||
Self(NEXT.load(Ordering::Relaxed))
|
||||
}
|
||||
pub fn is_joined(self) -> bool {
|
||||
self.0 != 0
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ except ImportError:
|
||||
from test_locator_bar_parity import generate_world
|
||||
|
||||
NAME = 'tick-order-parity'
|
||||
TOLERANCE = 1e-6
|
||||
TOLERANCE = float(os.environ.get('TICK_ORDER_TOLERANCE', '1e-6')) # 0 for exact
|
||||
PROPERTIES = {
|
||||
'online-mode': 'false',
|
||||
'white-list': 'false',
|
||||
|
||||
Reference in new issue
Block a user