mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
Add damage types & damage command (#526)
This commit is contained in:
1
assets/damage_type.json
Normal file
1
assets/damage_type.json
Normal file
File diff suppressed because one or more lines are too long
@@ -7,6 +7,7 @@ use syn::Ident;
|
||||
|
||||
mod biome;
|
||||
mod chunk_status;
|
||||
mod damage_type;
|
||||
mod entity_pose;
|
||||
mod entity_type;
|
||||
mod game_event;
|
||||
@@ -34,6 +35,7 @@ pub fn main() {
|
||||
write_generated_file(entity_type::build(), "entity_type.rs");
|
||||
write_generated_file(noise_parameter::build(), "noise_parameter.rs");
|
||||
write_generated_file(biome::build(), "biome.rs");
|
||||
write_generated_file(damage_type::build(), "damage_type.rs");
|
||||
write_generated_file(message_type::build(), "message_type.rs");
|
||||
}
|
||||
|
||||
|
||||
141
pumpkin-data/build/damage_type.rs
Normal file
141
pumpkin-data/build/damage_type.rs
Normal file
@@ -0,0 +1,141 @@
|
||||
use heck::{ToPascalCase, ToShoutySnakeCase};
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use serde::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct DamageTypeEntry {
|
||||
id: u32,
|
||||
components: DamageTypeData,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct DamageTypeData {
|
||||
death_message_type: Option<String>,
|
||||
exhaustion: f32,
|
||||
message_id: String,
|
||||
scaling: String,
|
||||
}
|
||||
|
||||
pub(crate) fn build() -> TokenStream {
|
||||
println!("cargo:rerun-if-changed=../assets/damage_type.json");
|
||||
|
||||
let damage_types: HashMap<String, DamageTypeEntry> =
|
||||
serde_json::from_str(include_str!("../../assets/damage_type.json"))
|
||||
.expect("Failed to parse damage_type.json");
|
||||
|
||||
let mut constants = Vec::new();
|
||||
let mut enum_variants = Vec::new();
|
||||
|
||||
for (name, entry) in damage_types {
|
||||
let const_ident = crate::ident(name.to_shouty_snake_case());
|
||||
let enum_ident = crate::ident(name.to_pascal_case());
|
||||
|
||||
enum_variants.push(enum_ident.clone());
|
||||
|
||||
let data = &entry.components;
|
||||
let death_message_type = match &data.death_message_type {
|
||||
Some(msg) => quote! { Some(#msg) },
|
||||
None => quote! { None },
|
||||
};
|
||||
|
||||
let exhaustion = data.exhaustion;
|
||||
let message_id = &data.message_id;
|
||||
let scaling = &data.scaling;
|
||||
let id = entry.id;
|
||||
|
||||
constants.push(quote! {
|
||||
pub const #const_ident: DamageTypeData = DamageTypeData {
|
||||
death_message_type: #death_message_type,
|
||||
exhaustion: #exhaustion,
|
||||
message_id: #message_id,
|
||||
scaling: #scaling,
|
||||
id: #id,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
let enum_arms = enum_variants.iter().map(|variant| {
|
||||
let const_name = variant.to_string().to_shouty_snake_case();
|
||||
let const_ident = crate::ident(&const_name);
|
||||
quote! {
|
||||
DamageType::#variant => &#const_ident,
|
||||
}
|
||||
});
|
||||
|
||||
let type_name_pairs = enum_variants.iter().map(|variant| {
|
||||
let name = variant.to_string();
|
||||
let name_lowercase = name.to_lowercase();
|
||||
let resource_name = format!("minecraft:{}", name_lowercase);
|
||||
quote! {
|
||||
#resource_name => Some(Self::#variant)
|
||||
}
|
||||
});
|
||||
|
||||
let type_to_name_pairs = enum_variants.iter().map(|variant| {
|
||||
let name = variant.to_string();
|
||||
let name_lowercase = name.to_lowercase();
|
||||
let resource_name = format!("minecraft:{}", name_lowercase);
|
||||
quote! {
|
||||
Self::#variant => #resource_name
|
||||
}
|
||||
});
|
||||
|
||||
// Create array of all variants for values() method
|
||||
let variant_array = enum_variants.iter().map(|variant| {
|
||||
quote! {
|
||||
DamageType::#variant
|
||||
}
|
||||
});
|
||||
|
||||
quote! {
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DamageTypeData {
|
||||
pub death_message_type: Option<&'static str>,
|
||||
pub exhaustion: f32,
|
||||
pub message_id: &'static str,
|
||||
pub scaling: &'static str,
|
||||
pub id: u32,
|
||||
}
|
||||
|
||||
#(#constants)*
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
pub enum DamageType {
|
||||
#(#enum_variants,)*
|
||||
}
|
||||
|
||||
impl DamageType {
|
||||
pub const fn data(&self) -> &'static DamageTypeData {
|
||||
match self {
|
||||
#(#enum_arms)*
|
||||
}
|
||||
}
|
||||
|
||||
#[doc = r" Get all possible damage types"]
|
||||
pub fn values() -> &'static [DamageType] {
|
||||
static VALUES: &[DamageType] = &[
|
||||
#(#variant_array,)*
|
||||
];
|
||||
VALUES
|
||||
}
|
||||
|
||||
#[doc = r" Try to parse a damage type from a resource location string"]
|
||||
pub fn from_name(name: &str) -> Option<Self> {
|
||||
match name {
|
||||
#(#type_name_pairs,)*
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
#[doc = r" Get the resource location string for this damage type"]
|
||||
pub const fn to_name(&self) -> &'static str {
|
||||
match self {
|
||||
#(#type_to_name_pairs,)*
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,3 +38,7 @@ pub mod world {
|
||||
pub mod scoreboard {
|
||||
include!(concat!(env!("OUT_DIR"), "/scoreboard_slot.rs"));
|
||||
}
|
||||
|
||||
pub mod damage {
|
||||
include!(concat!(env!("OUT_DIR"), "/damage_type.rs"));
|
||||
}
|
||||
|
||||
74
pumpkin/src/command/args/damage_type.rs
Normal file
74
pumpkin/src/command/args/damage_type.rs
Normal file
@@ -0,0 +1,74 @@
|
||||
use async_trait::async_trait;
|
||||
use pumpkin_data::damage::DamageType;
|
||||
use pumpkin_protocol::client::play::{ArgumentType, CommandSuggestion, SuggestionProviders};
|
||||
|
||||
use crate::command::{
|
||||
args::{Arg, ArgumentConsumer, DefaultNameArgConsumer, FindArg, GetClientSideArgParser},
|
||||
dispatcher::CommandError,
|
||||
tree::RawArgs,
|
||||
CommandSender,
|
||||
};
|
||||
use crate::server::Server;
|
||||
|
||||
pub struct DamageTypeArgumentConsumer;
|
||||
|
||||
impl GetClientSideArgParser for DamageTypeArgumentConsumer {
|
||||
fn get_client_side_parser(&self) -> ArgumentType {
|
||||
ArgumentType::ResourceLocation
|
||||
}
|
||||
|
||||
fn get_client_side_suggestion_type_override(&self) -> Option<SuggestionProviders> {
|
||||
Some(SuggestionProviders::AskServer)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ArgumentConsumer for DamageTypeArgumentConsumer {
|
||||
async fn consume<'a>(
|
||||
&'a self,
|
||||
_sender: &CommandSender<'a>,
|
||||
_server: &'a Server,
|
||||
args: &mut RawArgs<'a>,
|
||||
) -> Option<Arg<'a>> {
|
||||
let s = args.pop()?;
|
||||
|
||||
// Create a static damage type first
|
||||
let damage_type = DamageType::from_name(s)?;
|
||||
// Find matching static damage type from values array
|
||||
DamageType::values()
|
||||
.iter()
|
||||
.find(|&&dt| std::mem::discriminant(&dt) == std::mem::discriminant(&damage_type))
|
||||
.map(Arg::DamageType)
|
||||
}
|
||||
|
||||
async fn suggest<'a>(
|
||||
&'a self,
|
||||
_sender: &CommandSender<'a>,
|
||||
_server: &'a Server,
|
||||
_input: &'a str,
|
||||
) -> Result<Option<Vec<CommandSuggestion>>, CommandError> {
|
||||
// Get all available damage types
|
||||
let suggestions = DamageType::values()
|
||||
.iter()
|
||||
.map(|dt| CommandSuggestion::new(dt.to_name().to_string(), None))
|
||||
.collect();
|
||||
Ok(Some(suggestions))
|
||||
}
|
||||
}
|
||||
|
||||
impl DefaultNameArgConsumer for DamageTypeArgumentConsumer {
|
||||
fn default_name(&self) -> &'static str {
|
||||
"damageType"
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FindArg<'a> for DamageTypeArgumentConsumer {
|
||||
type Data = &'a DamageType;
|
||||
|
||||
fn find_arg(args: &'a super::ConsumedArgs, name: &str) -> Result<Self::Data, CommandError> {
|
||||
match args.get(name) {
|
||||
Some(Arg::DamageType(data)) => Ok(data),
|
||||
_ => Err(CommandError::InvalidConsumption(Some(name.to_string()))),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ use std::{collections::HashMap, hash::Hash, sync::Arc};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use bounded_num::{NotInBounds, Number};
|
||||
use pumpkin_data::damage::DamageType;
|
||||
use pumpkin_data::sound::SoundCategory;
|
||||
use pumpkin_protocol::client::play::{ArgumentType, CommandSuggestion, SuggestionProviders};
|
||||
use pumpkin_util::text::TextComponent;
|
||||
@@ -25,6 +26,7 @@ pub mod bossbar_style;
|
||||
pub mod bounded_num;
|
||||
pub mod command;
|
||||
mod coordinate;
|
||||
pub mod damage_type;
|
||||
pub mod entities;
|
||||
pub mod entity;
|
||||
pub mod gamemode;
|
||||
@@ -97,6 +99,7 @@ pub enum Arg<'a> {
|
||||
#[allow(unused)]
|
||||
Simple(&'a str),
|
||||
SoundCategory(SoundCategory),
|
||||
DamageType(&'a DamageType),
|
||||
}
|
||||
|
||||
/// see [`crate::commands::tree::builder::argument`] and [`CommandTree::execute`]/[`crate::commands::tree::builder::NonLeafNodeBuilder::execute`]
|
||||
|
||||
188
pumpkin/src/command/commands/damage.rs
Normal file
188
pumpkin/src/command/commands/damage.rs
Normal file
@@ -0,0 +1,188 @@
|
||||
use async_trait::async_trait;
|
||||
use pumpkin_data::damage::DamageType;
|
||||
use pumpkin_util::text::{
|
||||
color::{Color, NamedColor},
|
||||
TextComponent,
|
||||
};
|
||||
|
||||
use crate::command::{
|
||||
args::{
|
||||
bounded_num::BoundedNumArgumentConsumer, damage_type::DamageTypeArgumentConsumer,
|
||||
entity::EntityArgumentConsumer, position_3d::Position3DArgumentConsumer, Arg, ConsumedArgs,
|
||||
FindArg,
|
||||
},
|
||||
tree::builder::{argument, literal},
|
||||
tree::CommandTree,
|
||||
CommandError, CommandExecutor, CommandSender,
|
||||
};
|
||||
|
||||
const NAMES: [&str; 1] = ["damage"];
|
||||
const DESCRIPTION: &str = "Deals damage to entities";
|
||||
const ARG_TARGET: &str = "target";
|
||||
const ARG_AMOUNT: &str = "amount";
|
||||
const ARG_DAMAGE_TYPE: &str = "damageType";
|
||||
const ARG_LOCATION: &str = "location";
|
||||
const ARG_ENTITY: &str = "entity";
|
||||
const ARG_CAUSE: &str = "cause";
|
||||
|
||||
fn amount_consumer() -> BoundedNumArgumentConsumer<f32> {
|
||||
BoundedNumArgumentConsumer::new().name(ARG_AMOUNT).min(0.0)
|
||||
}
|
||||
|
||||
struct DamageLocationExecutor;
|
||||
struct DamageEntityExecutor(bool);
|
||||
|
||||
async fn send_damage_result(
|
||||
sender: &mut CommandSender<'_>,
|
||||
success: bool,
|
||||
amount: f32,
|
||||
target_name: String,
|
||||
) {
|
||||
if !success {
|
||||
sender
|
||||
.send_message(
|
||||
TextComponent::translate("commands.damage.invulnerable", [].into())
|
||||
.color(Color::Named(NamedColor::Red)),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
|
||||
sender
|
||||
.send_message(TextComponent::translate(
|
||||
"commands.damage.success",
|
||||
[
|
||||
TextComponent::text(amount.to_string()),
|
||||
TextComponent::text(target_name),
|
||||
]
|
||||
.into(),
|
||||
))
|
||||
.await;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl CommandExecutor for DamageLocationExecutor {
|
||||
async fn execute<'a>(
|
||||
&self,
|
||||
sender: &mut CommandSender<'a>,
|
||||
_server: &crate::server::Server,
|
||||
args: &ConsumedArgs<'a>,
|
||||
) -> Result<(), CommandError> {
|
||||
let target = EntityArgumentConsumer::find_arg(args, ARG_TARGET)?;
|
||||
|
||||
let Ok(Ok(amount)) = BoundedNumArgumentConsumer::<f32>::find_arg(args, ARG_AMOUNT) else {
|
||||
sender
|
||||
.send_message(
|
||||
TextComponent::text("Invalid damage amount")
|
||||
.color(Color::Named(NamedColor::Red)),
|
||||
)
|
||||
.await;
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let damage_type = args
|
||||
.get(ARG_DAMAGE_TYPE)
|
||||
.map_or(DamageType::Generic, |arg| match arg {
|
||||
Arg::DamageType(dt) => **dt,
|
||||
_ => DamageType::Generic,
|
||||
});
|
||||
|
||||
let location = Position3DArgumentConsumer::find_arg(args, ARG_LOCATION)?;
|
||||
|
||||
let success = target
|
||||
.living_entity
|
||||
.damage_with_context(amount, damage_type, Some(location), None, None)
|
||||
.await;
|
||||
|
||||
send_damage_result(sender, success, amount, target.gameprofile.name.clone()).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl CommandExecutor for DamageEntityExecutor {
|
||||
async fn execute<'a>(
|
||||
&self,
|
||||
sender: &mut CommandSender<'a>,
|
||||
_server: &crate::server::Server,
|
||||
args: &ConsumedArgs<'a>,
|
||||
) -> Result<(), CommandError> {
|
||||
let target = EntityArgumentConsumer::find_arg(args, ARG_TARGET)?;
|
||||
|
||||
let Ok(Ok(amount)) = BoundedNumArgumentConsumer::<f32>::find_arg(args, ARG_AMOUNT) else {
|
||||
sender
|
||||
.send_message(
|
||||
TextComponent::text("Invalid damage amount")
|
||||
.color(Color::Named(NamedColor::Red)),
|
||||
)
|
||||
.await;
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let damage_type = args
|
||||
.get(ARG_DAMAGE_TYPE)
|
||||
.map_or(DamageType::Generic, |arg| match arg {
|
||||
Arg::DamageType(dt) => **dt,
|
||||
_ => DamageType::Generic,
|
||||
});
|
||||
|
||||
let source = EntityArgumentConsumer::find_arg(args, ARG_ENTITY).ok();
|
||||
let cause = if self.0 {
|
||||
EntityArgumentConsumer::find_arg(args, ARG_CAUSE).ok()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let success = target
|
||||
.living_entity
|
||||
.damage_with_context(
|
||||
amount,
|
||||
damage_type,
|
||||
None,
|
||||
source.as_ref().map(|e| &e.living_entity.entity),
|
||||
cause.as_ref().map(|e| &e.living_entity.entity),
|
||||
)
|
||||
.await;
|
||||
|
||||
send_damage_result(sender, success, amount, target.gameprofile.name.clone()).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init_command_tree() -> CommandTree {
|
||||
CommandTree::new(NAMES, DESCRIPTION).then(
|
||||
argument(ARG_TARGET, EntityArgumentConsumer).then(
|
||||
argument(ARG_AMOUNT, amount_consumer())
|
||||
// Basic damage
|
||||
.execute(DamageEntityExecutor(false))
|
||||
// With damage type
|
||||
.then(
|
||||
argument(ARG_DAMAGE_TYPE, DamageTypeArgumentConsumer)
|
||||
.execute(DamageEntityExecutor(false))
|
||||
// At location
|
||||
.then(
|
||||
literal("at").then(
|
||||
argument(ARG_LOCATION, Position3DArgumentConsumer)
|
||||
.execute(DamageLocationExecutor),
|
||||
),
|
||||
)
|
||||
// By entity
|
||||
.then(
|
||||
literal("by").then(
|
||||
argument(ARG_ENTITY, EntityArgumentConsumer)
|
||||
.execute(DamageEntityExecutor(false))
|
||||
// From cause
|
||||
.then(
|
||||
literal("from").then(
|
||||
argument(ARG_CAUSE, EntityArgumentConsumer)
|
||||
.execute(DamageEntityExecutor(true)),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -3,6 +3,7 @@ pub mod banip;
|
||||
pub mod banlist;
|
||||
pub mod bossbar;
|
||||
pub mod clear;
|
||||
pub mod damage;
|
||||
pub mod deop;
|
||||
pub mod experience;
|
||||
pub mod fill;
|
||||
|
||||
@@ -10,9 +10,9 @@ use crate::world::World;
|
||||
use args::ConsumedArgs;
|
||||
use async_trait::async_trait;
|
||||
use commands::{
|
||||
ban, banip, banlist, clear, deop, experience, fill, gamemode, give, help, kick, kill, list, me,
|
||||
msg, op, pardon, pardonip, playsound, plugin, plugins, pumpkin, say, setblock, stop, summon,
|
||||
teleport, time, title, worldborder,
|
||||
ban, banip, banlist, clear, damage, deop, experience, fill, gamemode, give, help, kick, kill,
|
||||
list, me, msg, op, pardon, pardonip, playsound, plugin, plugins, pumpkin, say, setblock, stop,
|
||||
summon, teleport, time, title, worldborder,
|
||||
};
|
||||
use dispatcher::CommandError;
|
||||
use pumpkin_util::math::vector3::Vector3;
|
||||
@@ -143,6 +143,7 @@ pub fn default_dispatcher() -> CommandDispatcher {
|
||||
dispatcher.register(pardon::init_command_tree(), PermissionLvl::Three);
|
||||
dispatcher.register(pardonip::init_command_tree(), PermissionLvl::Three);
|
||||
dispatcher.register(experience::init_command_tree(), PermissionLvl::Two);
|
||||
dispatcher.register(damage::init_command_tree(), PermissionLvl::Two);
|
||||
|
||||
dispatcher
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::sync::atomic::AtomicI32;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use crossbeam::atomic::AtomicCell;
|
||||
use pumpkin_data::sound::Sound;
|
||||
use pumpkin_data::{damage::DamageType, sound::Sound};
|
||||
use pumpkin_nbt::tag::NbtTag;
|
||||
use pumpkin_protocol::client::play::{CDamageEvent, CEntityStatus, MetaDataType, Metadata};
|
||||
use pumpkin_util::math::vector3::Vector3;
|
||||
@@ -66,16 +66,27 @@ impl LivingEntity {
|
||||
self.entity.entity_id
|
||||
}
|
||||
|
||||
// TODO add damage_type enum
|
||||
pub async fn damage(&self, amount: f32, damage_type: u8) {
|
||||
pub async fn damage_with_context(
|
||||
&self,
|
||||
amount: f32,
|
||||
damage_type: DamageType,
|
||||
position: Option<Vector3<f64>>,
|
||||
source: Option<&Entity>,
|
||||
cause: Option<&Entity>,
|
||||
) -> bool {
|
||||
// Check invulnerability before applying damage
|
||||
if self.entity.is_invulnerable_to(damage_type) {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.entity
|
||||
.world
|
||||
.broadcast_packet_all(&CDamageEvent::new(
|
||||
self.entity.entity_id.into(),
|
||||
damage_type.into(),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
damage_type.data().id.into(),
|
||||
source.map(|e| e.entity_id.into()),
|
||||
cause.map(|e| e.entity_id.into()),
|
||||
position,
|
||||
))
|
||||
.await;
|
||||
|
||||
@@ -86,6 +97,13 @@ impl LivingEntity {
|
||||
} else {
|
||||
self.set_health(new_health).await;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub async fn damage(&self, amount: f32, damage_type: DamageType) -> bool {
|
||||
self.damage_with_context(amount, damage_type, None, None, None)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Returns if the entity was damaged or not
|
||||
@@ -132,7 +150,7 @@ impl LivingEntity {
|
||||
.play_sound(Self::get_fall_sound(fall_distance as i32))
|
||||
.await;
|
||||
// TODO: Play block fall sound
|
||||
self.damage(damage, 10).await; // Fall
|
||||
self.damage(damage, DamageType::Fall).await; // Fall
|
||||
} else if height_difference < 0.0 {
|
||||
let distance = self.fall_distance.load();
|
||||
self.fall_distance
|
||||
|
||||
@@ -9,6 +9,7 @@ use crossbeam::atomic::AtomicCell;
|
||||
use living::LivingEntity;
|
||||
use player::Player;
|
||||
use pumpkin_data::{
|
||||
damage::DamageType,
|
||||
entity::{EntityPose, EntityType},
|
||||
sound::{Sound, SoundCategory},
|
||||
};
|
||||
@@ -91,6 +92,10 @@ pub struct Entity {
|
||||
pub bounding_box: AtomicCell<BoundingBox>,
|
||||
///The size (width and height) of the bounding box
|
||||
pub bounding_box_size: AtomicCell<BoundingBoxSize>,
|
||||
/// Whether this entity is invulnerable to all damage
|
||||
pub invulnerable: AtomicBool,
|
||||
/// List of damage types this entity is immune to
|
||||
pub damage_immunities: Vec<DamageType>,
|
||||
}
|
||||
|
||||
impl Entity {
|
||||
@@ -104,6 +109,7 @@ impl Entity {
|
||||
standing_eye_height: f32,
|
||||
bounding_box: AtomicCell<BoundingBox>,
|
||||
bounding_box_size: AtomicCell<BoundingBoxSize>,
|
||||
invulnerable: bool,
|
||||
) -> Self {
|
||||
let floor_x = position.x.floor() as i32;
|
||||
let floor_y = position.y.floor() as i32;
|
||||
@@ -130,6 +136,8 @@ impl Entity {
|
||||
pose: AtomicCell::new(EntityPose::Standing),
|
||||
bounding_box,
|
||||
bounding_box_size,
|
||||
invulnerable: AtomicBool::new(invulnerable),
|
||||
damage_immunities: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -352,6 +360,11 @@ impl Entity {
|
||||
self.send_meta_data(Metadata::new(6, MetaDataType::EntityPose, VarInt(pose)))
|
||||
.await;
|
||||
}
|
||||
|
||||
pub fn is_invulnerable_to(&self, damage_type: DamageType) -> bool {
|
||||
self.invulnerable.load(std::sync::atomic::Ordering::Relaxed)
|
||||
|| self.damage_immunities.contains(&damage_type)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
|
||||
@@ -11,6 +11,7 @@ use async_trait::async_trait;
|
||||
use crossbeam::atomic::AtomicCell;
|
||||
use pumpkin_config::{ADVANCED_CONFIG, BASIC_CONFIG};
|
||||
use pumpkin_data::{
|
||||
damage::DamageType,
|
||||
entity::EntityType,
|
||||
sound::{Sound, SoundCategory},
|
||||
};
|
||||
@@ -183,6 +184,7 @@ impl Player {
|
||||
1.62,
|
||||
AtomicCell::new(BoundingBox::new_default(&bounding_box_size)),
|
||||
AtomicCell::new(bounding_box_size),
|
||||
matches!(gamemode, GameMode::Creative | GameMode::Spectator),
|
||||
)),
|
||||
config: Mutex::new(config),
|
||||
gameprofile,
|
||||
@@ -354,7 +356,7 @@ impl Player {
|
||||
|
||||
victim
|
||||
.living_entity
|
||||
.damage(damage as f32, 34) // PlayerAttack
|
||||
.damage(damage as f32, DamageType::PlayerAttack) // PlayerAttack
|
||||
.await;
|
||||
|
||||
let mut knockback_strength = 1.0;
|
||||
@@ -675,6 +677,11 @@ impl Player {
|
||||
abilities.set_for_gamemode(gamemode);
|
||||
};
|
||||
self.send_abilities_update().await;
|
||||
|
||||
self.living_entity.entity.invulnerable.store(
|
||||
matches!(gamemode, GameMode::Creative | GameMode::Spectator),
|
||||
std::sync::atomic::Ordering::Relaxed,
|
||||
);
|
||||
self.living_entity
|
||||
.entity
|
||||
.world
|
||||
@@ -686,6 +693,7 @@ impl Player {
|
||||
}],
|
||||
))
|
||||
.await;
|
||||
|
||||
#[allow(clippy::cast_precision_loss)]
|
||||
self.client
|
||||
.send_packet(&CGameEvent::new(
|
||||
|
||||
@@ -252,6 +252,7 @@ impl Server {
|
||||
&bounding_box_size,
|
||||
)),
|
||||
AtomicCell::new(bounding_box_size),
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user