mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 11:06:11 -10:00
Creative players are pushed by explosions and crystals
ServerExplosion.hurtEntities puts each pushed player in hitPlayers and ServerLevel.explode sends them that push in ClientboundExplodePacket. Horizon pushed the server's copy only and sent it to the client with a hurt's motion packet, which a creative player never gets: a crystal they hit left them standing, and TNT replaced their motion with the server copy's. The pushes now go in the explosion packet (deferred with it in a guest's turn), except for a hurt player, whose motion packet already carries the push. Fixes #2755 (its survival part was fixed by #2730). Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
bd665f28ad
commit
51cd5409da
3 files changed
+51
-27
No files matched your search
@@ -57,8 +57,9 @@ pub(super) struct Creatures {
|
||||
/// (game/lan_pvp.rs).
|
||||
pub sweeps: Vec<super::lan_pvp::Sweep>,
|
||||
/// Explosion packets from a guest's turn, waiting for it to end
|
||||
/// (centre, small, radius, blocks hit; game/lan_particles.rs).
|
||||
pub lan_explosions: Vec<([f64; 3], bool, f32, usize)>,
|
||||
/// (centre, small, radius, blocks hit, the players it pushed;
|
||||
/// game/lan_particles.rs).
|
||||
pub lan_explosions: Vec<([f64; 3], bool, f32, usize, Vec<(u32, [f64; 3])>)>,
|
||||
/// Ticks left of the elder guardian's apparition (MobAppearanceParticle).
|
||||
pub elder_apparition: i32,
|
||||
/// The wandering trader spawner's clock.
|
||||
|
||||
@@ -224,20 +224,24 @@ impl Game<'_> {
|
||||
}
|
||||
set.entries().map(|(k, _)| k.0).collect()
|
||||
}
|
||||
/// `ServerExplosion.hurtEntities` (player, mobs, items, primed TNT, minecarts);
|
||||
/// the player takes `damage_type`.
|
||||
/// `ServerExplosion.hurtEntities` for the player: damage scaled by
|
||||
/// distance and exposure, through a raised shield (`hurtServer` blocks
|
||||
/// explosions like any attack from where the blast is), then the push
|
||||
/// (less with explosion knockback resistance; none for spectators or a
|
||||
/// creative player flying).
|
||||
///
|
||||
/// Returns the push their client adds from the explosion packet
|
||||
/// (`hitPlayers`), unless a hurt marked their velocity: that goes to them
|
||||
/// whole, this push in it, once their server copy has travelled
|
||||
/// (Entity.markHurt, then ServerEntity.sendChanges), and replaces what the
|
||||
/// packet would add.
|
||||
pub(super) fn explosion_hurt_player(
|
||||
&mut self,
|
||||
center: [f64; 3],
|
||||
radius: f32,
|
||||
damage_type: &str,
|
||||
exploding: Option<u32>,
|
||||
) {
|
||||
) -> Option<[f64; 3]> {
|
||||
let diameter = f64::from(radius * 2.0);
|
||||
// Blast protection: `EXPLOSION_KNOCKBACK_RESISTANCE` (0.15 per level and
|
||||
// piece, so two Blast Protection IV pieces cancel it).
|
||||
@@ -246,9 +250,7 @@ impl Game<'_> {
|
||||
.as_mut()
|
||||
.and_then(|p| p.attribute("minecraft:explosion_knockback_resistance").ok())
|
||||
.unwrap_or(0.0);
|
||||
let Some(p) = self.player.as_ref() else {
|
||||
return;
|
||||
};
|
||||
let p = self.player.as_ref()?;
|
||||
let feet = [p.position.x, p.position.y, p.position.z];
|
||||
let dist = ((feet[0] - center[0]).powi(2)
|
||||
+ (feet[1] - center[1]).powi(2)
|
||||
@@ -256,7 +258,7 @@ impl Game<'_> {
|
||||
.sqrt()
|
||||
/ diameter;
|
||||
if dist > 1.0 || p.health.dead_or_dying() {
|
||||
return;
|
||||
return None;
|
||||
}
|
||||
// ServerPlayer.onExplosionHit: the blast that hit the player is theirs.
|
||||
self.adv.marks.blast = Some((feet, false));
|
||||
@@ -291,6 +293,7 @@ impl Game<'_> {
|
||||
let (creative, spectator) = (self.creative, self.spectator());
|
||||
let flying = self.player.as_ref().is_some_and(|p| p.flying);
|
||||
let mut outcome = horizon_client::health::BlockOutcome::default();
|
||||
let mut pushed = None;
|
||||
if let Some(p) = self.player.as_mut()
|
||||
&& let Some(id) = self.motion.health.damage.id(damage_type)
|
||||
{
|
||||
@@ -317,21 +320,32 @@ impl Game<'_> {
|
||||
}
|
||||
}
|
||||
if !spectator && !(creative && flying) {
|
||||
let push = [dir[0] * power, dir[1] * power, dir[2] * power];
|
||||
p.velocity = Vec3::new(
|
||||
p.velocity.x + dir[0] * power,
|
||||
p.velocity.y + dir[1] * power,
|
||||
p.velocity.z + dir[2] * power,
|
||||
p.velocity.x + push[0],
|
||||
p.velocity.y + push[1],
|
||||
p.velocity.z + push[2],
|
||||
);
|
||||
pushed = (!p.health.sync_velocity).then_some(push);
|
||||
}
|
||||
}
|
||||
if raised {
|
||||
self.after_block(outcome, amount, None, false);
|
||||
}
|
||||
pushed
|
||||
}
|
||||
|
||||
fn explosion_hurt_entities(&mut self, center: [f64; 3], radius: f32, damage_type: &str) {
|
||||
/// `ServerExplosion.hurtEntities` (player, mobs, items, primed TNT, minecarts);
|
||||
/// the player takes `damage_type`. Returns the players whose clients add a
|
||||
/// push from the explosion packet, and how much (`explosion_hurt_player`).
|
||||
fn explosion_hurt_entities(
|
||||
&mut self,
|
||||
center: [f64; 3],
|
||||
radius: f32,
|
||||
damage_type: &str,
|
||||
) -> Vec<(u32, [f64; 3])> {
|
||||
if radius < 1.0e-5 {
|
||||
return;
|
||||
return Vec::new();
|
||||
}
|
||||
self.explosion_hits_special(center, radius);
|
||||
let diameter = f64::from(radius * 2.0);
|
||||
@@ -365,8 +379,10 @@ impl Game<'_> {
|
||||
})
|
||||
};
|
||||
// The player, then every other player in the level (LAN guests),
|
||||
// each swapped in as the player.
|
||||
self.explosion_hurt_player(center, radius, damage_type, exploding);
|
||||
// each swapped in as the player. In a guest's turn (their hit on an
|
||||
// end crystal) the player is that guest.
|
||||
let own = self.explosion_hurt_player(center, radius, damage_type, exploding);
|
||||
let mut pushed: Vec<_> = self.lan_acting.zip(own).into_iter().collect();
|
||||
if let Some(mut host) = self.lan.take() {
|
||||
let guests: Vec<u32> = host
|
||||
.hub
|
||||
@@ -376,9 +392,12 @@ impl Game<'_> {
|
||||
.map(|g| g.conn)
|
||||
.collect();
|
||||
for conn in guests {
|
||||
self.guest_hit_by(&mut host, conn, |g| {
|
||||
g.explosion_hurt_player(center, radius, damage_type, exploding);
|
||||
let push = self.as_guest(&mut host, conn, |g| {
|
||||
g.explosion_hurt_player(center, radius, damage_type, exploding)
|
||||
});
|
||||
if let Some(Some(push)) = push {
|
||||
pushed.push((conn, push));
|
||||
}
|
||||
}
|
||||
self.lan = Some(host);
|
||||
}
|
||||
@@ -518,6 +537,7 @@ impl Game<'_> {
|
||||
c.velocity.z + dir[2] * power,
|
||||
);
|
||||
}
|
||||
pushed
|
||||
}
|
||||
/// `ServerLevel.getDestroyType`: a drop decay rule picks between decaying
|
||||
/// and plain destruction.
|
||||
@@ -579,7 +599,7 @@ impl Game<'_> {
|
||||
let ctx = horizon_client::sculk::Context::default();
|
||||
self.post_game_event("minecraft:explode", center, ctx);
|
||||
let mut targets = self.exploded_positions(center, radius, resists);
|
||||
self.explosion_hurt_entities(center, radius, damage_type);
|
||||
let pushed = self.explosion_hurt_entities(center, radius, damage_type);
|
||||
if interaction != BlockInteraction::Keep {
|
||||
// Util.shuffle(list, level.random)
|
||||
for i in (2..=targets.len()).rev() {
|
||||
@@ -628,7 +648,7 @@ impl Game<'_> {
|
||||
.explosion(center, small, block_count, radius, &|b| {
|
||||
facts.is_air(world.block(b[0], b[1], b[2]))
|
||||
});
|
||||
self.lan_explode(center, small, radius, block_count);
|
||||
self.lan_explode(center, small, radius, block_count, pushed);
|
||||
Ok(())
|
||||
}
|
||||
/// `BlockBehaviour.onExplosionHit` (TNT: `wasExploded` primes a short fuse).
|
||||
|
||||
@@ -409,15 +409,18 @@ impl Game<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
/// `ServerLevel.explode`'s packet to each player within 64 blocks. The
|
||||
/// sound and the push reach them on their own (the level's sounds, the
|
||||
/// motion a hurt player is sent), so the packet carries neither.
|
||||
/// `ServerLevel.explode`'s packet to each player within 64 blocks, with
|
||||
/// the push `pushed` holds for them (`ServerExplosion.hitPlayers`: a
|
||||
/// creative player, whom nothing hurt). The sound reaches them on its own
|
||||
/// (the level's sounds), and so does a hurt player's push (the motion
|
||||
/// they are sent).
|
||||
pub(super) fn lan_explode(
|
||||
&mut self,
|
||||
center: [f64; 3],
|
||||
small: bool,
|
||||
radius: f32,
|
||||
block_count: usize,
|
||||
pushed: Vec<(u32, [f64; 3])>,
|
||||
) {
|
||||
// In a guest's turn (their hit on an end crystal or a minecart) the
|
||||
// host holds the players (`as_guest`): the packet waits for the turn
|
||||
@@ -425,7 +428,7 @@ impl Game<'_> {
|
||||
if self.lan_acting.is_some() && self.lan.is_none() {
|
||||
self.creatures
|
||||
.lan_explosions
|
||||
.push((center, small, radius, block_count));
|
||||
.push((center, small, radius, block_count, pushed));
|
||||
return;
|
||||
}
|
||||
let emitter = if small {
|
||||
@@ -437,16 +440,16 @@ impl Game<'_> {
|
||||
if let Some(packet) =
|
||||
self.explosion_packet(center, radius, block_count, emitter, sound, true)
|
||||
{
|
||||
self.send_explosion(packet, &[]);
|
||||
self.send_explosion(packet, &pushed);
|
||||
}
|
||||
}
|
||||
|
||||
/// The explosions guests' turns put off.
|
||||
pub(super) fn flush_lan_explosions(&mut self) {
|
||||
for (center, small, radius, block_count) in
|
||||
for (center, small, radius, block_count, pushed) in
|
||||
std::mem::take(&mut self.creatures.lan_explosions)
|
||||
{
|
||||
self.lan_explode(center, small, radius, block_count);
|
||||
self.lan_explode(center, small, radius, block_count, pushed);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user