Wire explosion knockback for other entities and wind

This commit is contained in:
arismoko committed 2026-11-02 13:33:21 +00:00
1 parent e1084abe75
commit 355becd82d
4 files changed
+36 -24

No files matched your search

+23 -14
View File
@@ -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<u32>,
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));
@@ -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,
+11 -8
View File
@@ -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<u32> = 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") {
@@ -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@."
}