diff --git a/crates/horizon-client/src/game/explosion.rs b/crates/horizon-client/src/game/explosion.rs index 70aa5bd5dc..60443582f8 100644 --- a/crates/horizon-client/src/game/explosion.rs +++ b/crates/horizon-client/src/game/explosion.rs @@ -148,8 +148,12 @@ fn blast_direction(center: [f64; 3], origin: [f64; 3]) -> [f64; 3] { } } -/// An entity in the blast's reach: how far (as a share of the blast's -/// diameter), how much of it the rays see, and the knockback. +struct ExplosionHookContext<'a> { + center: [f64; 3], + blocks: &'a [[i32; 3]], +} + +/// An entity in the blast's reach: distance, exposure, and knockback. struct Hit { dist: f64, exposure: f32, @@ -389,16 +393,16 @@ impl Game<'_> { /// /// `cause` is the blast's source as predicates see it, read before the /// guests' turns swap their own combat state in. - pub(super) fn explosion_hurt_player( + fn explosion_hurt_player( &mut self, - center: [f64; 3], + context: &ExplosionHookContext<'_>, radius: f32, damage_type: &str, exploding: Option, attacker: Option<&(u32, Text)>, cause: Option<&horizon_client::advancements::EntityView>, - blocks: &[[i32; 3]], ) -> Option<[f64; 3]> { + let ExplosionHookContext { center, blocks } = *context; 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). @@ -451,10 +455,6 @@ impl Game<'_> { let flying = self.player.as_ref().is_some_and(|p| p.flying); let mut outcome = horizon_client::health::BlockOutcome::default(); let mut pushed = None; - let mut push = [dir[0] * power, dir[1] * power, dir[2] * power]; - if !spectator && !(creative && flying) { - self.native_explosion_knockback(self.dim.id(), center, blocks, None, &mut push); - } // The death message names the blast's causing entity. It goes to this // player's tracker, the host's or a guest's. if let Some((id, name)) = attacker.filter(|_| !creative && !spectator && hurtable) { @@ -486,7 +486,11 @@ impl Game<'_> { .indicate_damage(center[0] - feet[0], center[2] - feet[2], p.yaw); } } - if !spectator && !(creative && flying) { + } + if !spectator && !(creative && flying) { + let mut push = [dir[0] * power, dir[1] * power, dir[2] * power]; + self.native_explosion_knockback(self.dim.id(), center, blocks, None, &mut push); + if let Some(p) = self.player.as_mut() { p.velocity = Vec3::new( p.velocity.x + push[0], p.velocity.y + push[1], @@ -573,14 +577,14 @@ impl Game<'_> { // same for all of them (`onExplosionHit(this.source)`). let attacker = self.blast_attacker(); let blast_source = self.combat.explosion_cause.clone(); + let context = ExplosionHookContext { center, blocks }; let own = self.explosion_hurt_player( - center, + &context, radius, damage_type, exploding, attacker.as_ref(), blast_source.as_ref(), - blocks, ); let mut pushed: Vec<_> = self.lan_acting.zip(own).into_iter().collect(); if let Some(mut host) = self.lan.take() { @@ -594,13 +598,12 @@ impl Game<'_> { for conn in guests { let push = self.as_guest(&mut host, conn, |g| { g.explosion_hurt_player( - center, + &context, radius, damage_type, exploding, attacker.as_ref(), blast_source.as_ref(), - blocks, ) }); if let Some(Some(push)) = push { @@ -649,6 +652,12 @@ impl Game<'_> { }) = hit { let amount = damage(dist, exposure); + let entity = with_entity!(self, handle, e => e.id()); + let mut velocity = [push.x, push.y, push.z]; + self.native_explosion_knockback( + self.dim.id(), center, blocks, Some(entity), &mut velocity, + ); + let push = Vec3::new(velocity[0], velocity[1], velocity[2]); match handle { EntityHandle::Mob(i) => { let hurtable = teams_allow(self, self.mob_team(self.mobs[i].id)); diff --git a/crates/horizon-client/src/game/native_hooks.rs b/crates/horizon-client/src/game/native_hooks.rs index e1b4b98701..7b1bb49ddc 100644 --- a/crates/horizon-client/src/game/native_hooks.rs +++ b/crates/horizon-client/src/game/native_hooks.rs @@ -5294,7 +5294,7 @@ impl Game<'_> { mod_api::dispatch_explosion_detonate(self, mod_api::mods(), dimension, center, blocks); } /// Server explosion knockback for `entity` (None for the acting player). - /// Replace the velocity added to its existing velocity. API @API:explosion_knockback@. + /// Replace the velocity added to its existing velocity. API @API:native_explosion_knockback@. pub(super) fn native_explosion_knockback( &mut self, dimension: &str, diff --git a/crates/horizon-client/src/game/wind.rs b/crates/horizon-client/src/game/wind.rs index 441d51aa14..e25ed724ea 100644 --- a/crates/horizon-client/src/game/wind.rs +++ b/crates/horizon-client/src/game/wind.rs @@ -226,8 +226,9 @@ impl Game<'_> { multiplier: f32, breeze: bool, wind_charge: bool, - blocklike: bool, + blocks: &[[i32; 3]], ) -> Vec<(u32, [f64; 3])> { + let blocklike = !breeze && !wind_charge && self.ui.rules.bool("mob_griefing"); let diameter = f64::from(radius * 2.0); let distance = |p: [f64; 3]| { ((p[0] - center[0]).powi(2) + (p[1] - center[1]).powi(2) + (p[2] - center[2]).powi(2)) @@ -249,7 +250,7 @@ impl Game<'_> { // The player, then every other player in the level (LAN guests), // each swapped in as the player. In a guest's turn (a mace's wind // burst) the player is that guest. - let own = self.wind_push_player(center, radius, multiplier, breeze, wind_charge); + let own = self.wind_push_player(center, radius, multiplier, breeze, wind_charge, blocks); let mut pushed: Vec<_> = self.lan_acting.zip(own).into_iter().collect(); if let Some(mut host) = self.lan.take() { let guests: Vec = host @@ -261,7 +262,7 @@ impl Game<'_> { .collect(); for conn in guests { let push = self.as_guest(&mut host, conn, |g| { - g.wind_push_player(center, radius, multiplier, breeze, wind_charge) + g.wind_push_player(center, radius, multiplier, breeze, wind_charge, blocks) }); if let Some(Some(push)) = push { pushed.push((conn, push)); @@ -295,7 +296,9 @@ impl Game<'_> { let exposure = self.seen_percent(center, [lo.x, lo.y, lo.z], [hi.x, hi.y, hi.z]); let power = (1.0 - dist) * f64::from(exposure) * multiplier; let dir = direction([feet[0], feet[1] + entity.explosion_eye_height(), feet[2]]); - entity.push_from_explosion(Vec3::new(dir[0] * power, dir[1] * power, dir[2] * power)); + let mut push = [dir[0] * power, dir[1] * power, dir[2] * power]; + self.native_explosion_knockback(self.dim.id(), center, blocks, Some(entity.id()), &mut push); + entity.push_from_explosion(Vec3::new(push[0], push[1], push[2])); }); self.restore_entities(entities); pushed @@ -310,6 +313,7 @@ impl Game<'_> { multiplier: f32, breeze: bool, wind_charge: bool, + blocks: &[[i32; 3]], ) -> Option<[f64; 3]> { if self.spectator() { return None; @@ -344,11 +348,12 @@ impl Game<'_> { ); let power = (1.0 - dist) * f64::from(exposure) * f64::from(multiplier) * (1.0 - resistance); let l = (eye[0] * eye[0] + eye[1] * eye[1] + eye[2] * eye[2]).sqrt(); - let push = if l < 1.0e-5 { + let mut push = if l < 1.0e-5 { [0.0; 3] } else { eye.map(|d| d / l * power) }; + self.native_explosion_knockback(self.dim.id(), center, blocks, None, &mut push); let p = self.player.as_mut()?; p.velocity = Vec3::new( p.velocity.x + push[0], @@ -402,10 +407,8 @@ impl Game<'_> { let ctx = horizon_client::sculk::Context::default(); self.post_game_event("minecraft:explode", center, ctx); let mut targets = self.wind_positions(center, radius); - // ServerExplosion.shouldAffectBlocklikeEntities - let blocklike = !breeze && !wind_charge && self.ui.rules.bool("mob_griefing"); let pushed = - self.wind_knockback(center, radius, multiplier, breeze, wind_charge, blocklike); + self.wind_knockback(center, radius, multiplier, breeze, wind_charge, &targets); let block_count = targets.len(); // Explosion.canTriggerBlocks: a breeze's charge needs mob griefing. if !breeze || self.ui.rules.bool("mob_griefing") { diff --git a/tools/native-hook-fragments/native_explosion_knockback.json b/tools/native-hook-fragments/native_explosion_knockback.json index 354a8561a5..17e53b03bf 100644 --- a/tools/native-hook-fragments/native_explosion_knockback.json +++ b/tools/native-hook-fragments/native_explosion_knockback.json @@ -13,5 +13,5 @@ } ], "rust_summary": "Explosion velocity replacement (`native_explosion_knockback`).", - "doc_summary": "A server explosion calculates an entity's added velocity: `native_explosion_knockback(dimension, center, blocks, entity, knockback_velocity)` can replace it. Bundles need \"hooks\": @API:explosion_knockback@." + "doc_summary": "A server explosion calculates an entity's added velocity: `native_explosion_knockback(dimension, center, blocks, entity, knockback_velocity)` can replace it. Bundles need \"hooks\": @API:native_explosion_knockback@." }