mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-31 08:22:33 +00:00
feat: add entity knockback & damage to Explosions
Not 100% vanilla accurate but better than nothing
This commit is contained in:
@@ -23,8 +23,8 @@ impl LookControl {
|
||||
|
||||
pub fn look_at_entity(&mut self, mob: &dyn Mob, entity: &Arc<dyn EntityBase>) {
|
||||
let entity = entity.get_entity();
|
||||
let pos = entity.pos.load();
|
||||
self.look_at(mob, pos.x, entity.get_eye_y(), pos.z);
|
||||
let pos = entity.get_eye_pos();
|
||||
self.look_at(mob, pos.x, pos.y, pos.z);
|
||||
}
|
||||
|
||||
pub fn look_at_entity_with_range(
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use pumpkin_data::damage::DamageType;
|
||||
use pumpkin_data::entity::EntityType;
|
||||
use pumpkin_data::meta_data_type::MetaDataType;
|
||||
use pumpkin_data::{Block, tracked_data::TrackedData};
|
||||
@@ -110,6 +111,15 @@ impl EntityBase for FallingEntity {
|
||||
self
|
||||
}
|
||||
|
||||
fn damage<'a>(
|
||||
&'a self,
|
||||
_caller: &'a dyn EntityBase,
|
||||
_amount: f32,
|
||||
_damage_type: DamageType,
|
||||
) -> EntityBaseFuture<'a, bool> {
|
||||
Box::pin(async move { false })
|
||||
}
|
||||
|
||||
fn get_gravity(&self) -> f64 {
|
||||
0.04
|
||||
}
|
||||
|
||||
@@ -146,6 +146,11 @@ pub trait EntityBase: Send + Sync + NBTStorage {
|
||||
true
|
||||
}
|
||||
|
||||
/// Whether the entity is immune from explosion knockback and damage
|
||||
fn is_immune_to_explosion(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn get_gravity(&self) -> f64 {
|
||||
0.0
|
||||
}
|
||||
@@ -194,9 +199,12 @@ pub trait EntityBase: Send + Sync + NBTStorage {
|
||||
cause: Option<&'a dyn EntityBase>,
|
||||
) -> EntityBaseFuture<'a, bool> {
|
||||
Box::pin(async move {
|
||||
caller
|
||||
.damage_with_context(caller, amount, damage_type, position, source, cause)
|
||||
.await
|
||||
if caller.get_living_entity().is_some() {
|
||||
return caller
|
||||
.damage_with_context(caller, amount, damage_type, position, source, cause)
|
||||
.await;
|
||||
}
|
||||
false
|
||||
})
|
||||
}
|
||||
|
||||
@@ -481,6 +489,10 @@ impl Entity {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn add_velocity(&self, velocity: Vector3<f64>) {
|
||||
self.set_velocity(self.velocity.load() + velocity).await;
|
||||
}
|
||||
|
||||
pub async fn set_velocity(&self, velocity: Vector3<f64>) {
|
||||
self.velocity.store(velocity);
|
||||
self.send_velocity().await;
|
||||
@@ -2018,6 +2030,15 @@ impl Entity {
|
||||
.await;
|
||||
}
|
||||
|
||||
pub fn get_eye_pos(&self) -> Vector3<f64> {
|
||||
let pos = self.pos.load();
|
||||
Vector3::new(
|
||||
pos.x,
|
||||
pos.y + f64::from(self.entity_dimension.load().eye_height),
|
||||
pos.z,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_eye_y(&self) -> f64 {
|
||||
self.pos.load().y + f64::from(self.entity_dimension.load().eye_height)
|
||||
}
|
||||
|
||||
@@ -115,4 +115,8 @@ impl EntityBase for TNTEntity {
|
||||
fn as_nbt_storage(&self) -> &dyn NBTStorage {
|
||||
self
|
||||
}
|
||||
|
||||
fn is_immune_to_explosion(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,7 +171,7 @@ impl JavaClient {
|
||||
}
|
||||
Err(error) => {
|
||||
let text = format!("Error while reading incoming packet {error}");
|
||||
log::error!(
|
||||
log::debug!(
|
||||
"Failed to read incoming packet with id {}: {}",
|
||||
packet.id,
|
||||
error
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use pumpkin_data::{Block, BlockState};
|
||||
use pumpkin_util::math::{position::BlockPos, vector3::Vector3};
|
||||
use pumpkin_data::{Block, BlockState, damage::DamageType, entity::EntityType};
|
||||
use pumpkin_util::math::{boundingbox::BoundingBox, position::BlockPos, vector3::Vector3};
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use crate::{
|
||||
block::{ExplodeArgs, drop_loot},
|
||||
entity::{Entity, EntityBase},
|
||||
world::loot::LootContextParameters,
|
||||
};
|
||||
|
||||
@@ -77,10 +78,133 @@ impl Explosion {
|
||||
map
|
||||
}
|
||||
|
||||
async fn damage_entities(&self, world: &Arc<World>) {
|
||||
// Explosion is too small
|
||||
if self.power < 1.0e-5 {
|
||||
return;
|
||||
}
|
||||
|
||||
let radius = self.power as f64 * 2.0;
|
||||
let min_x = (self.pos.x - radius - 1.0).floor() as i32;
|
||||
let max_x = (self.pos.x + radius + 1.0).floor() as i32;
|
||||
let min_y = (self.pos.y - radius - 1.0).floor() as i32;
|
||||
let max_y = (self.pos.y + radius + 1.0).floor() as i32;
|
||||
let min_z = (self.pos.z - radius - 1.0).floor() as i32;
|
||||
let max_z = (self.pos.z + radius + 1.0).floor() as i32;
|
||||
|
||||
let search_box = BoundingBox::new(
|
||||
Vector3::new(min_x as f64, min_y as f64, min_z as f64),
|
||||
Vector3::new(max_x as f64, max_y as f64, max_z as f64),
|
||||
);
|
||||
|
||||
let entities = world.get_all_at_box(&search_box);
|
||||
|
||||
for entity_base in entities {
|
||||
if entity_base.is_immune_to_explosion() {
|
||||
continue;
|
||||
}
|
||||
let entity = entity_base.get_entity();
|
||||
|
||||
let distance = (entity.pos.load().squared_distance_to_vec(&self.pos)).sqrt() / radius;
|
||||
if distance > 1.0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let exposure = Self::calculate_exposure(&self.pos, entity, world).await as f64;
|
||||
if exposure == 0.0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let damage_multiplier = (1.0 - distance) * exposure;
|
||||
let damage = (f64::midpoint(damage_multiplier * damage_multiplier, damage_multiplier)
|
||||
* 7.0
|
||||
* self.power as f64
|
||||
+ 1.0) as f32;
|
||||
|
||||
// TODO: damage type
|
||||
entity
|
||||
.damage(entity_base.as_ref(), damage, DamageType::EXPLOSION)
|
||||
.await;
|
||||
|
||||
// Calculate and apply knockback
|
||||
let dir_pos = if entity.entity_type == &EntityType::TNT {
|
||||
entity.pos.load()
|
||||
} else {
|
||||
entity.get_eye_pos()
|
||||
};
|
||||
let direction = (dir_pos - self.pos).normalize();
|
||||
// TODO
|
||||
let knockback_resistance = 0.0;
|
||||
|
||||
let knockback_multiplier = (1.0 - distance) * exposure * (1.0 - knockback_resistance);
|
||||
let knockback = direction * knockback_multiplier;
|
||||
entity.add_velocity(knockback).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn calculate_exposure(
|
||||
explosion_pos: &Vector3<f64>,
|
||||
entity: &Entity,
|
||||
world: &Arc<World>,
|
||||
) -> f32 {
|
||||
let bbox = entity.bounding_box.load();
|
||||
|
||||
let step_x = 1.0 / ((bbox.max.x - bbox.min.x) * 2.0 + 1.0);
|
||||
let step_y = 1.0 / ((bbox.max.y - bbox.min.y) * 2.0 + 1.0);
|
||||
let step_z = 1.0 / ((bbox.max.z - bbox.min.z) * 2.0 + 1.0);
|
||||
|
||||
if step_x < 0.0 || step_y < 0.0 || step_z < 0.0 {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
let offset_x = (1.0 - (1.0 / step_x).floor() * step_x) / 2.0;
|
||||
let offset_z = (1.0 - (1.0 / step_z).floor() * step_z) / 2.0;
|
||||
|
||||
let mut visible_points = 0;
|
||||
let mut total_points = 0;
|
||||
|
||||
let mut k = 0.0;
|
||||
while k <= 1.0 {
|
||||
let mut l = 0.0;
|
||||
while l <= 1.0 {
|
||||
let mut m = 0.0;
|
||||
while m <= 1.0 {
|
||||
let n = bbox.min.x + (bbox.max.x - bbox.min.x) * k;
|
||||
let o = bbox.min.y + (bbox.max.y - bbox.min.y) * l;
|
||||
let p = bbox.min.z + (bbox.max.z - bbox.min.z) * m;
|
||||
|
||||
let vec3d = Vector3::new(n + offset_x, o, p + offset_z);
|
||||
|
||||
if world
|
||||
.raycast(vec3d, *explosion_pos, async |pos, world_ref| {
|
||||
let state = world_ref.get_block_state(pos).await;
|
||||
!state.is_air() && !state.collision_shapes.is_empty()
|
||||
})
|
||||
.await
|
||||
.is_none()
|
||||
{
|
||||
visible_points += 1;
|
||||
}
|
||||
|
||||
total_points += 1;
|
||||
m += step_z;
|
||||
}
|
||||
l += step_y;
|
||||
}
|
||||
k += step_x;
|
||||
}
|
||||
|
||||
if total_points == 0 {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
visible_points as f32 / total_points as f32
|
||||
}
|
||||
|
||||
/// Returns the removed block count
|
||||
pub async fn explode(&self, world: &Arc<World>) -> u32 {
|
||||
let blocks = self.get_blocks_to_destroy(world).await;
|
||||
// TODO: Entity damage, fire
|
||||
self.damage_entities(world).await;
|
||||
for (pos, (block, state)) in &blocks {
|
||||
world.set_block_state(pos, 0, BlockFlags::NOTIFY_ALL).await;
|
||||
|
||||
@@ -104,6 +228,7 @@ impl Explosion {
|
||||
.await;
|
||||
}
|
||||
}
|
||||
// TODO: fire
|
||||
blocks.len() as u32
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2297,6 +2297,24 @@ impl World {
|
||||
None
|
||||
}
|
||||
|
||||
// Gets all entities at a Box
|
||||
pub fn get_all_at_box(&self, aabb: &BoundingBox) -> Vec<Arc<dyn EntityBase>> {
|
||||
let entities_guard = self.entities.load();
|
||||
let players_guard = self.players.load();
|
||||
|
||||
entities_guard
|
||||
.iter()
|
||||
.map(|e| e.clone() as Arc<dyn EntityBase>)
|
||||
.chain(
|
||||
players_guard
|
||||
.iter()
|
||||
.map(|p| p.clone() as Arc<dyn EntityBase>),
|
||||
)
|
||||
.filter(|entity| entity.get_entity().bounding_box.load().intersects(aabb))
|
||||
.collect()
|
||||
}
|
||||
|
||||
// Gets all non Player entities at a Box
|
||||
pub fn get_entities_at_box(&self, aabb: &BoundingBox) -> Vec<Arc<dyn EntityBase>> {
|
||||
self.entities
|
||||
.load()
|
||||
@@ -2305,6 +2323,8 @@ impl World {
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
|
||||
// Gets all Player entities at a Box
|
||||
pub fn get_players_at_box(&self, aabb: &BoundingBox) -> Vec<Arc<Player>> {
|
||||
let players_guard = self.players.load();
|
||||
players_guard
|
||||
@@ -3296,19 +3316,19 @@ impl World {
|
||||
}
|
||||
|
||||
async fn ray_outline_check(
|
||||
self: &Arc<Self>,
|
||||
&self,
|
||||
block_pos: &BlockPos,
|
||||
from: Vector3<f64>,
|
||||
to: Vector3<f64>,
|
||||
) -> (bool, Option<BlockDirection>) {
|
||||
let state = self.get_block_state(block_pos).await;
|
||||
|
||||
let bounding_boxes = state.get_block_outline_shapes();
|
||||
|
||||
if state.outline_shapes.is_empty() {
|
||||
return (true, None);
|
||||
}
|
||||
|
||||
let bounding_boxes = state.get_block_outline_shapes();
|
||||
|
||||
for shape in bounding_boxes {
|
||||
let world_min = shape.min.add(&block_pos.0.to_f64());
|
||||
let world_max = shape.max.add(&block_pos.0.to_f64());
|
||||
|
||||
Reference in New Issue
Block a user