feat(command): add attributes
This commit is contained in:
parent
ce05798fac
commit
486f8d2cfe
1
pumpkin-codegen/Cargo.lock
generated
1
pumpkin-codegen/Cargo.lock
generated
@ -803,6 +803,7 @@ dependencies = [
|
||||
"serde",
|
||||
"thiserror",
|
||||
"tracing",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@ -25,17 +25,21 @@ pub fn build() -> TokenStream {
|
||||
sorted_attributes.sort_by_key(|(_, raw)| raw.id);
|
||||
|
||||
let mut constant_defs = Vec::new();
|
||||
let mut constant_idents = Vec::new();
|
||||
|
||||
for (raw_name, raw_value) in sorted_attributes {
|
||||
let constant_ident = format_ident!("{}", raw_name.to_shouty_snake_case());
|
||||
constant_idents.push(constant_ident.clone());
|
||||
|
||||
let id_lit = LitInt::new(&raw_value.id.to_string(), Span::call_site());
|
||||
let default_value_lit = raw_value.default_value;
|
||||
let name_str = format!("minecraft:{raw_name}");
|
||||
|
||||
constant_defs.push(quote!(
|
||||
pub const #constant_ident: Self = Self {
|
||||
id: #id_lit,
|
||||
default_value: #default_value_lit,
|
||||
name: #name_str,
|
||||
};
|
||||
));
|
||||
}
|
||||
@ -47,6 +51,7 @@ pub fn build() -> TokenStream {
|
||||
pub struct Attributes {
|
||||
pub id: u8,
|
||||
pub default_value: f64,
|
||||
pub name: &'static str,
|
||||
}
|
||||
impl PartialEq for Attributes {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
@ -61,6 +66,10 @@ pub fn build() -> TokenStream {
|
||||
}
|
||||
impl Attributes {
|
||||
#(#constant_defs)*
|
||||
|
||||
pub const ALL: &'static [Self] = &[
|
||||
#(Self::#constant_idents),*
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ use std::hash::Hash;
|
||||
pub struct Attributes {
|
||||
pub id: u8,
|
||||
pub default_value: f64,
|
||||
pub name: &'static str,
|
||||
}
|
||||
impl PartialEq for Attributes {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
@ -20,161 +21,243 @@ impl Attributes {
|
||||
pub const AIR_DRAG_MODIFIER: Self = Self {
|
||||
id: 0,
|
||||
default_value: 1f64,
|
||||
name: "minecraft:air_drag_modifier",
|
||||
};
|
||||
pub const ARMOR: Self = Self {
|
||||
id: 1,
|
||||
default_value: 0f64,
|
||||
name: "minecraft:armor",
|
||||
};
|
||||
pub const ARMOR_TOUGHNESS: Self = Self {
|
||||
id: 2,
|
||||
default_value: 0f64,
|
||||
name: "minecraft:armor_toughness",
|
||||
};
|
||||
pub const ATTACK_DAMAGE: Self = Self {
|
||||
id: 3,
|
||||
default_value: 2f64,
|
||||
name: "minecraft:attack_damage",
|
||||
};
|
||||
pub const ATTACK_KNOCKBACK: Self = Self {
|
||||
id: 4,
|
||||
default_value: 0f64,
|
||||
name: "minecraft:attack_knockback",
|
||||
};
|
||||
pub const ATTACK_SPEED: Self = Self {
|
||||
id: 5,
|
||||
default_value: 4f64,
|
||||
name: "minecraft:attack_speed",
|
||||
};
|
||||
pub const BELOW_NAME_DISTANCE: Self = Self {
|
||||
id: 6,
|
||||
default_value: 10f64,
|
||||
name: "minecraft:below_name_distance",
|
||||
};
|
||||
pub const BLOCK_BREAK_SPEED: Self = Self {
|
||||
id: 7,
|
||||
default_value: 1f64,
|
||||
name: "minecraft:block_break_speed",
|
||||
};
|
||||
pub const BLOCK_INTERACTION_RANGE: Self = Self {
|
||||
id: 8,
|
||||
default_value: 4.5f64,
|
||||
name: "minecraft:block_interaction_range",
|
||||
};
|
||||
pub const BOUNCINESS: Self = Self {
|
||||
id: 9,
|
||||
default_value: 0f64,
|
||||
name: "minecraft:bounciness",
|
||||
};
|
||||
pub const BURNING_TIME: Self = Self {
|
||||
id: 10,
|
||||
default_value: 1f64,
|
||||
name: "minecraft:burning_time",
|
||||
};
|
||||
pub const CAMERA_DISTANCE: Self = Self {
|
||||
id: 11,
|
||||
default_value: 4f64,
|
||||
name: "minecraft:camera_distance",
|
||||
};
|
||||
pub const EXPLOSION_KNOCKBACK_RESISTANCE: Self = Self {
|
||||
id: 12,
|
||||
default_value: 0f64,
|
||||
name: "minecraft:explosion_knockback_resistance",
|
||||
};
|
||||
pub const ENTITY_INTERACTION_RANGE: Self = Self {
|
||||
id: 13,
|
||||
default_value: 3f64,
|
||||
name: "minecraft:entity_interaction_range",
|
||||
};
|
||||
pub const FALL_DAMAGE_MULTIPLIER: Self = Self {
|
||||
id: 14,
|
||||
default_value: 1f64,
|
||||
name: "minecraft:fall_damage_multiplier",
|
||||
};
|
||||
pub const FLYING_SPEED: Self = Self {
|
||||
id: 15,
|
||||
default_value: 0.4f64,
|
||||
name: "minecraft:flying_speed",
|
||||
};
|
||||
pub const FOLLOW_RANGE: Self = Self {
|
||||
id: 16,
|
||||
default_value: 32f64,
|
||||
name: "minecraft:follow_range",
|
||||
};
|
||||
pub const FRICTION_MODIFIER: Self = Self {
|
||||
id: 17,
|
||||
default_value: 1f64,
|
||||
name: "minecraft:friction_modifier",
|
||||
};
|
||||
pub const GRAVITY: Self = Self {
|
||||
id: 18,
|
||||
default_value: 0.08f64,
|
||||
name: "minecraft:gravity",
|
||||
};
|
||||
pub const JUMP_STRENGTH: Self = Self {
|
||||
id: 19,
|
||||
default_value: 0.41999998688697815f64,
|
||||
name: "minecraft:jump_strength",
|
||||
};
|
||||
pub const KNOCKBACK_RESISTANCE: Self = Self {
|
||||
id: 20,
|
||||
default_value: 0f64,
|
||||
name: "minecraft:knockback_resistance",
|
||||
};
|
||||
pub const LUCK: Self = Self {
|
||||
id: 21,
|
||||
default_value: 0f64,
|
||||
name: "minecraft:luck",
|
||||
};
|
||||
pub const MAX_ABSORPTION: Self = Self {
|
||||
id: 22,
|
||||
default_value: 0f64,
|
||||
name: "minecraft:max_absorption",
|
||||
};
|
||||
pub const MAX_HEALTH: Self = Self {
|
||||
id: 23,
|
||||
default_value: 20f64,
|
||||
name: "minecraft:max_health",
|
||||
};
|
||||
pub const MINING_EFFICIENCY: Self = Self {
|
||||
id: 24,
|
||||
default_value: 0f64,
|
||||
name: "minecraft:mining_efficiency",
|
||||
};
|
||||
pub const MOVEMENT_EFFICIENCY: Self = Self {
|
||||
id: 25,
|
||||
default_value: 0f64,
|
||||
name: "minecraft:movement_efficiency",
|
||||
};
|
||||
pub const MOVEMENT_SPEED: Self = Self {
|
||||
id: 26,
|
||||
default_value: 0.7f64,
|
||||
name: "minecraft:movement_speed",
|
||||
};
|
||||
pub const NAME_TAG_DISTANCE: Self = Self {
|
||||
id: 27,
|
||||
default_value: 64f64,
|
||||
name: "minecraft:name_tag_distance",
|
||||
};
|
||||
pub const OXYGEN_BONUS: Self = Self {
|
||||
id: 28,
|
||||
default_value: 0f64,
|
||||
name: "minecraft:oxygen_bonus",
|
||||
};
|
||||
pub const SAFE_FALL_DISTANCE: Self = Self {
|
||||
id: 29,
|
||||
default_value: 3f64,
|
||||
name: "minecraft:safe_fall_distance",
|
||||
};
|
||||
pub const SCALE: Self = Self {
|
||||
id: 30,
|
||||
default_value: 1f64,
|
||||
name: "minecraft:scale",
|
||||
};
|
||||
pub const SNEAKING_SPEED: Self = Self {
|
||||
id: 31,
|
||||
default_value: 0.3f64,
|
||||
name: "minecraft:sneaking_speed",
|
||||
};
|
||||
pub const SPAWN_REINFORCEMENTS: Self = Self {
|
||||
id: 32,
|
||||
default_value: 0f64,
|
||||
name: "minecraft:spawn_reinforcements",
|
||||
};
|
||||
pub const STEP_HEIGHT: Self = Self {
|
||||
id: 33,
|
||||
default_value: 0.6f64,
|
||||
name: "minecraft:step_height",
|
||||
};
|
||||
pub const SUBMERGED_MINING_SPEED: Self = Self {
|
||||
id: 34,
|
||||
default_value: 0.2f64,
|
||||
name: "minecraft:submerged_mining_speed",
|
||||
};
|
||||
pub const SWEEPING_DAMAGE_RATIO: Self = Self {
|
||||
id: 35,
|
||||
default_value: 0f64,
|
||||
name: "minecraft:sweeping_damage_ratio",
|
||||
};
|
||||
pub const TEMPT_RANGE: Self = Self {
|
||||
id: 36,
|
||||
default_value: 10f64,
|
||||
name: "minecraft:tempt_range",
|
||||
};
|
||||
pub const WATER_MOVEMENT_EFFICIENCY: Self = Self {
|
||||
id: 37,
|
||||
default_value: 0f64,
|
||||
name: "minecraft:water_movement_efficiency",
|
||||
};
|
||||
pub const WAYPOINT_TRANSMIT_RANGE: Self = Self {
|
||||
id: 38,
|
||||
default_value: 0f64,
|
||||
name: "minecraft:waypoint_transmit_range",
|
||||
};
|
||||
pub const WAYPOINT_RECEIVE_RANGE: Self = Self {
|
||||
id: 39,
|
||||
default_value: 0f64,
|
||||
name: "minecraft:waypoint_receive_range",
|
||||
};
|
||||
pub const ALL: &'static [Self] = &[
|
||||
Self::AIR_DRAG_MODIFIER,
|
||||
Self::ARMOR,
|
||||
Self::ARMOR_TOUGHNESS,
|
||||
Self::ATTACK_DAMAGE,
|
||||
Self::ATTACK_KNOCKBACK,
|
||||
Self::ATTACK_SPEED,
|
||||
Self::BELOW_NAME_DISTANCE,
|
||||
Self::BLOCK_BREAK_SPEED,
|
||||
Self::BLOCK_INTERACTION_RANGE,
|
||||
Self::BOUNCINESS,
|
||||
Self::BURNING_TIME,
|
||||
Self::CAMERA_DISTANCE,
|
||||
Self::EXPLOSION_KNOCKBACK_RESISTANCE,
|
||||
Self::ENTITY_INTERACTION_RANGE,
|
||||
Self::FALL_DAMAGE_MULTIPLIER,
|
||||
Self::FLYING_SPEED,
|
||||
Self::FOLLOW_RANGE,
|
||||
Self::FRICTION_MODIFIER,
|
||||
Self::GRAVITY,
|
||||
Self::JUMP_STRENGTH,
|
||||
Self::KNOCKBACK_RESISTANCE,
|
||||
Self::LUCK,
|
||||
Self::MAX_ABSORPTION,
|
||||
Self::MAX_HEALTH,
|
||||
Self::MINING_EFFICIENCY,
|
||||
Self::MOVEMENT_EFFICIENCY,
|
||||
Self::MOVEMENT_SPEED,
|
||||
Self::NAME_TAG_DISTANCE,
|
||||
Self::OXYGEN_BONUS,
|
||||
Self::SAFE_FALL_DISTANCE,
|
||||
Self::SCALE,
|
||||
Self::SNEAKING_SPEED,
|
||||
Self::SPAWN_REINFORCEMENTS,
|
||||
Self::STEP_HEIGHT,
|
||||
Self::SUBMERGED_MINING_SPEED,
|
||||
Self::SWEEPING_DAMAGE_RATIO,
|
||||
Self::TEMPT_RANGE,
|
||||
Self::WATER_MOVEMENT_EFFICIENCY,
|
||||
Self::WAYPOINT_TRANSMIT_RANGE,
|
||||
Self::WAYPOINT_RECEIVE_RANGE,
|
||||
];
|
||||
}
|
||||
|
||||
66
pumpkin/src/command/argument_types/attribute.rs
Normal file
66
pumpkin/src/command/argument_types/attribute.rs
Normal file
@ -0,0 +1,66 @@
|
||||
use std::pin::Pin;
|
||||
|
||||
use crate::command::{
|
||||
argument_types::FromStringReader,
|
||||
argument_types::argument_type::{ArgumentType, JavaClientArgumentType},
|
||||
context::command_context::CommandContext,
|
||||
errors::command_syntax_error::CommandSyntaxError,
|
||||
errors::error_types::CommandErrorType,
|
||||
string_reader::StringReader,
|
||||
suggestion::suggestions::{Suggestions, SuggestionsBuilder},
|
||||
};
|
||||
use pumpkin_data::attributes::Attributes;
|
||||
use pumpkin_data::translation;
|
||||
use pumpkin_util::identifier::Identifier;
|
||||
use pumpkin_util::text::TextComponent;
|
||||
|
||||
pub const INVALID_ATTRIBUTE_ERROR_TYPE: CommandErrorType<1> = CommandErrorType::new(
|
||||
translation::java::ARGUMENT_ID_INVALID,
|
||||
translation::java::ARGUMENT_ID_INVALID,
|
||||
);
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct AttributeArgumentType;
|
||||
|
||||
impl ArgumentType for AttributeArgumentType {
|
||||
type Item = Attributes;
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
|
||||
let identifier = Identifier::from_reader(reader)?;
|
||||
let ident_str = identifier.to_string();
|
||||
|
||||
let attr = Attributes::ALL
|
||||
.iter()
|
||||
.find(|a| a.name == ident_str)
|
||||
.cloned()
|
||||
.ok_or_else(|| {
|
||||
INVALID_ATTRIBUTE_ERROR_TYPE.create(reader, TextComponent::text(ident_str))
|
||||
})?;
|
||||
|
||||
Ok(attr)
|
||||
}
|
||||
|
||||
fn client_side_parser(&'_ self) -> JavaClientArgumentType {
|
||||
JavaClientArgumentType::Resource {
|
||||
identifier: Identifier::vanilla("attribute").unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
fn list_suggestions<'a>(
|
||||
&'a self,
|
||||
_context: &'a CommandContext,
|
||||
builder: SuggestionsBuilder,
|
||||
) -> Pin<Box<dyn Future<Output = Suggestions> + Send + 'a>> {
|
||||
Box::pin(async move {
|
||||
builder
|
||||
.filter_and_suggest_iter(Attributes::ALL.iter().map(|a| a.name))
|
||||
.build()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl AttributeArgumentType {
|
||||
pub fn get(context: &CommandContext, name: &str) -> Result<Attributes, CommandSyntaxError> {
|
||||
context.get_argument::<Attributes>(name).cloned()
|
||||
}
|
||||
}
|
||||
@ -217,6 +217,7 @@ impl FromStringReader for Identifier {
|
||||
}
|
||||
|
||||
pub mod argument_type;
|
||||
pub mod attribute;
|
||||
pub mod block;
|
||||
pub mod coordinates;
|
||||
pub mod core;
|
||||
|
||||
531
pumpkin/src/command/commands/attribute.rs
Normal file
531
pumpkin/src/command/commands/attribute.rs
Normal file
@ -0,0 +1,531 @@
|
||||
use crate::command::argument_builder::{ArgumentBuilder, argument, command, literal};
|
||||
use crate::command::argument_types::attribute::AttributeArgumentType;
|
||||
use crate::command::argument_types::core::double::DoubleArgumentType;
|
||||
use crate::command::argument_types::core::string::StringArgumentType;
|
||||
use crate::command::argument_types::entity::EntityArgumentType;
|
||||
use crate::command::argument_types::uuid::UuidArgumentType;
|
||||
use crate::command::context::command_context::CommandContext;
|
||||
use crate::command::errors::error_types::CommandErrorType;
|
||||
use crate::command::node::dispatcher::CommandDispatcher;
|
||||
use crate::command::node::{CommandExecutor, CommandExecutorResult};
|
||||
use crate::entity::attributes::ModifierOperation;
|
||||
use pumpkin_data::attributes::Attributes;
|
||||
use pumpkin_data::translation;
|
||||
use pumpkin_util::PermissionLvl;
|
||||
use pumpkin_util::permission::{Permission, PermissionDefault, PermissionRegistry};
|
||||
use pumpkin_util::text::TextComponent;
|
||||
|
||||
const DESCRIPTION: &str = "Queries or modifies attributes of an entity.";
|
||||
const PERMISSION: &str = "minecraft:command.attribute";
|
||||
|
||||
const ARG_TARGET: &str = "target";
|
||||
const ARG_ATTRIBUTE: &str = "attribute";
|
||||
const ARG_SCALE: &str = "scale";
|
||||
const ARG_VALUE: &str = "value";
|
||||
const ARG_UUID: &str = "uuid";
|
||||
const ARG_NAME: &str = "name";
|
||||
|
||||
const FAILED_ENTITY_ERROR: CommandErrorType<1> = CommandErrorType::new(
|
||||
translation::java::COMMANDS_ATTRIBUTE_FAILED_ENTITY,
|
||||
translation::java::COMMANDS_ATTRIBUTE_FAILED_ENTITY,
|
||||
);
|
||||
|
||||
const NO_ATTRIBUTE_ERROR: CommandErrorType<2> = CommandErrorType::new(
|
||||
translation::java::COMMANDS_ATTRIBUTE_FAILED_NO_ATTRIBUTE,
|
||||
translation::java::COMMANDS_ATTRIBUTE_FAILED_NO_ATTRIBUTE,
|
||||
);
|
||||
|
||||
const MODIFIER_ALREADY_PRESENT_ERROR: CommandErrorType<3> = CommandErrorType::new(
|
||||
translation::java::COMMANDS_ATTRIBUTE_FAILED_MODIFIER_ALREADY_PRESENT,
|
||||
translation::java::COMMANDS_ATTRIBUTE_FAILED_MODIFIER_ALREADY_PRESENT,
|
||||
);
|
||||
|
||||
const NO_MODIFIER_ERROR: CommandErrorType<3> = CommandErrorType::new(
|
||||
translation::java::COMMANDS_ATTRIBUTE_FAILED_NO_MODIFIER,
|
||||
translation::java::COMMANDS_ATTRIBUTE_FAILED_NO_MODIFIER,
|
||||
);
|
||||
|
||||
fn attribute_translation_key(attribute: &Attributes) -> String {
|
||||
let path = attribute
|
||||
.name
|
||||
.strip_prefix("minecraft:")
|
||||
.unwrap_or(attribute.name);
|
||||
format!("attribute.name.{path}")
|
||||
}
|
||||
|
||||
struct GetExecutor {
|
||||
is_base: bool,
|
||||
has_scale: bool,
|
||||
}
|
||||
|
||||
impl CommandExecutor for GetExecutor {
|
||||
fn execute<'a>(&'a self, context: &'a CommandContext) -> CommandExecutorResult<'a> {
|
||||
Box::pin(async move {
|
||||
let target = EntityArgumentType::get_entity(context, ARG_TARGET).await?;
|
||||
let living = target
|
||||
.get_living_entity()
|
||||
.ok_or_else(|| FAILED_ENTITY_ERROR.create_without_context(target.get_name()))?;
|
||||
|
||||
let attribute = AttributeArgumentType::get(context, ARG_ATTRIBUTE)?;
|
||||
let scale = if self.has_scale {
|
||||
DoubleArgumentType::get(context, ARG_SCALE)?
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
|
||||
let has_attr = living
|
||||
.attributes
|
||||
.read()
|
||||
.unwrap()
|
||||
.contains_key(&attribute.id);
|
||||
if !has_attr {
|
||||
return Err(NO_ATTRIBUTE_ERROR.create_without_context(
|
||||
target.get_name(),
|
||||
TextComponent::translate(attribute_translation_key(&attribute), []),
|
||||
));
|
||||
}
|
||||
|
||||
if self.is_base {
|
||||
let base_value = living.get_attribute_base(&attribute);
|
||||
let scaled_value = base_value * scale;
|
||||
|
||||
context
|
||||
.source
|
||||
.send_feedback(
|
||||
TextComponent::translate_cross(
|
||||
translation::java::COMMANDS_ATTRIBUTE_BASE_VALUE_GET_SUCCESS,
|
||||
translation::java::COMMANDS_ATTRIBUTE_BASE_VALUE_GET_SUCCESS,
|
||||
[
|
||||
TextComponent::translate(attribute_translation_key(&attribute), []),
|
||||
target.get_name(),
|
||||
TextComponent::text(base_value.to_string()),
|
||||
],
|
||||
),
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
|
||||
Ok(scaled_value as i32)
|
||||
} else {
|
||||
let value = living.get_attribute_value(&attribute);
|
||||
let scaled_value = value * scale;
|
||||
|
||||
context
|
||||
.source
|
||||
.send_feedback(
|
||||
TextComponent::translate_cross(
|
||||
translation::java::COMMANDS_ATTRIBUTE_VALUE_GET_SUCCESS,
|
||||
translation::java::COMMANDS_ATTRIBUTE_VALUE_GET_SUCCESS,
|
||||
[
|
||||
TextComponent::translate(attribute_translation_key(&attribute), []),
|
||||
target.get_name(),
|
||||
TextComponent::text(value.to_string()),
|
||||
],
|
||||
),
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
|
||||
Ok(scaled_value as i32)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct BaseSetExecutor;
|
||||
|
||||
impl CommandExecutor for BaseSetExecutor {
|
||||
fn execute<'a>(&'a self, context: &'a CommandContext) -> CommandExecutorResult<'a> {
|
||||
Box::pin(async move {
|
||||
let target = EntityArgumentType::get_entity(context, ARG_TARGET).await?;
|
||||
let living = target
|
||||
.get_living_entity()
|
||||
.ok_or_else(|| FAILED_ENTITY_ERROR.create_without_context(target.get_name()))?;
|
||||
|
||||
let attribute = AttributeArgumentType::get(context, ARG_ATTRIBUTE)?;
|
||||
let value = DoubleArgumentType::get(context, ARG_VALUE)?;
|
||||
|
||||
let has_attr = living
|
||||
.attributes
|
||||
.read()
|
||||
.unwrap()
|
||||
.contains_key(&attribute.id);
|
||||
if !has_attr {
|
||||
return Err(NO_ATTRIBUTE_ERROR.create_without_context(
|
||||
target.get_name(),
|
||||
TextComponent::translate(attribute_translation_key(&attribute), []),
|
||||
));
|
||||
}
|
||||
|
||||
living.set_attribute_base(&attribute, value);
|
||||
crate::entity::attributes::send_attribute_updates_for_living(
|
||||
living,
|
||||
vec![attribute.clone()],
|
||||
)
|
||||
.await;
|
||||
|
||||
context
|
||||
.source
|
||||
.send_feedback(
|
||||
TextComponent::translate_cross(
|
||||
translation::java::COMMANDS_ATTRIBUTE_BASE_VALUE_SET_SUCCESS,
|
||||
translation::java::COMMANDS_ATTRIBUTE_BASE_VALUE_SET_SUCCESS,
|
||||
[
|
||||
TextComponent::translate(attribute_translation_key(&attribute), []),
|
||||
target.get_name(),
|
||||
TextComponent::text(value.to_string()),
|
||||
],
|
||||
),
|
||||
true,
|
||||
)
|
||||
.await;
|
||||
|
||||
Ok(value as i32)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct BaseResetExecutor;
|
||||
|
||||
impl CommandExecutor for BaseResetExecutor {
|
||||
fn execute<'a>(&'a self, context: &'a CommandContext) -> CommandExecutorResult<'a> {
|
||||
Box::pin(async move {
|
||||
let target = EntityArgumentType::get_entity(context, ARG_TARGET).await?;
|
||||
let living = target
|
||||
.get_living_entity()
|
||||
.ok_or_else(|| FAILED_ENTITY_ERROR.create_without_context(target.get_name()))?;
|
||||
|
||||
let attribute = AttributeArgumentType::get(context, ARG_ATTRIBUTE)?;
|
||||
|
||||
let has_attr = living
|
||||
.attributes
|
||||
.read()
|
||||
.unwrap()
|
||||
.contains_key(&attribute.id);
|
||||
if !has_attr {
|
||||
return Err(NO_ATTRIBUTE_ERROR.create_without_context(
|
||||
target.get_name(),
|
||||
TextComponent::translate(attribute_translation_key(&attribute), []),
|
||||
));
|
||||
}
|
||||
|
||||
let default_val = attribute.default_value;
|
||||
living.set_attribute_base(&attribute, default_val);
|
||||
crate::entity::attributes::send_attribute_updates_for_living(
|
||||
living,
|
||||
vec![attribute.clone()],
|
||||
)
|
||||
.await;
|
||||
|
||||
context
|
||||
.source
|
||||
.send_feedback(
|
||||
TextComponent::translate_cross(
|
||||
translation::java::COMMANDS_ATTRIBUTE_BASE_VALUE_RESET_SUCCESS,
|
||||
translation::java::COMMANDS_ATTRIBUTE_BASE_VALUE_RESET_SUCCESS,
|
||||
[
|
||||
TextComponent::translate(attribute_translation_key(&attribute), []),
|
||||
target.get_name(),
|
||||
TextComponent::text(default_val.to_string()),
|
||||
],
|
||||
),
|
||||
true,
|
||||
)
|
||||
.await;
|
||||
|
||||
Ok(default_val as i32)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct ModifierAddExecutor {
|
||||
operation: ModifierOperation,
|
||||
}
|
||||
|
||||
impl CommandExecutor for ModifierAddExecutor {
|
||||
fn execute<'a>(&'a self, context: &'a CommandContext) -> CommandExecutorResult<'a> {
|
||||
Box::pin(async move {
|
||||
let target = EntityArgumentType::get_entity(context, ARG_TARGET).await?;
|
||||
let living = target
|
||||
.get_living_entity()
|
||||
.ok_or_else(|| FAILED_ENTITY_ERROR.create_without_context(target.get_name()))?;
|
||||
|
||||
let attribute = AttributeArgumentType::get(context, ARG_ATTRIBUTE)?;
|
||||
let uuid = UuidArgumentType::get(context, ARG_UUID)?;
|
||||
let _name = StringArgumentType::get(context, ARG_NAME)?;
|
||||
let value = DoubleArgumentType::get(context, ARG_VALUE)?;
|
||||
|
||||
let modifier_id = uuid.to_string();
|
||||
|
||||
let res = {
|
||||
let mut map = living.attributes.write().unwrap();
|
||||
let inst = map.get_mut(&attribute.id).ok_or_else(|| {
|
||||
NO_ATTRIBUTE_ERROR.create_without_context(
|
||||
target.get_name(),
|
||||
TextComponent::translate(attribute_translation_key(&attribute), []),
|
||||
)
|
||||
})?;
|
||||
|
||||
if inst.modifiers.iter().any(|m| m.id == modifier_id) {
|
||||
Err(MODIFIER_ALREADY_PRESENT_ERROR.create_without_context(
|
||||
TextComponent::text(modifier_id.clone()),
|
||||
TextComponent::translate(attribute_translation_key(&attribute), []),
|
||||
target.get_name(),
|
||||
))
|
||||
} else {
|
||||
inst.modifiers.push(crate::entity::attributes::Modifier {
|
||||
id: modifier_id.clone(),
|
||||
amount: value,
|
||||
operation: self.operation,
|
||||
});
|
||||
inst.dirty.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
res?;
|
||||
|
||||
crate::entity::attributes::send_attribute_updates_for_living(
|
||||
living,
|
||||
vec![attribute.clone()],
|
||||
)
|
||||
.await;
|
||||
|
||||
context
|
||||
.source
|
||||
.send_feedback(
|
||||
TextComponent::translate_cross(
|
||||
translation::java::COMMANDS_ATTRIBUTE_MODIFIER_ADD_SUCCESS,
|
||||
translation::java::COMMANDS_ATTRIBUTE_MODIFIER_ADD_SUCCESS,
|
||||
[
|
||||
TextComponent::text(modifier_id),
|
||||
TextComponent::translate(attribute_translation_key(&attribute), []),
|
||||
target.get_name(),
|
||||
],
|
||||
),
|
||||
true,
|
||||
)
|
||||
.await;
|
||||
|
||||
Ok(1)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct ModifierRemoveExecutor;
|
||||
|
||||
impl CommandExecutor for ModifierRemoveExecutor {
|
||||
fn execute<'a>(&'a self, context: &'a CommandContext) -> CommandExecutorResult<'a> {
|
||||
Box::pin(async move {
|
||||
let target = EntityArgumentType::get_entity(context, ARG_TARGET).await?;
|
||||
let living = target
|
||||
.get_living_entity()
|
||||
.ok_or_else(|| FAILED_ENTITY_ERROR.create_without_context(target.get_name()))?;
|
||||
|
||||
let attribute = AttributeArgumentType::get(context, ARG_ATTRIBUTE)?;
|
||||
let uuid = UuidArgumentType::get(context, ARG_UUID)?;
|
||||
|
||||
let modifier_id = uuid.to_string();
|
||||
|
||||
let res = {
|
||||
let mut map = living.attributes.write().unwrap();
|
||||
let inst = map.get_mut(&attribute.id).ok_or_else(|| {
|
||||
NO_ATTRIBUTE_ERROR.create_without_context(
|
||||
target.get_name(),
|
||||
TextComponent::translate(attribute_translation_key(&attribute), []),
|
||||
)
|
||||
})?;
|
||||
|
||||
let index = inst
|
||||
.modifiers
|
||||
.iter()
|
||||
.position(|m| m.id == modifier_id)
|
||||
.ok_or_else(|| {
|
||||
NO_MODIFIER_ERROR.create_without_context(
|
||||
TextComponent::translate(attribute_translation_key(&attribute), []),
|
||||
target.get_name(),
|
||||
TextComponent::text(modifier_id.clone()),
|
||||
)
|
||||
})?;
|
||||
|
||||
inst.modifiers.remove(index);
|
||||
inst.dirty.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
Ok(())
|
||||
};
|
||||
res?;
|
||||
|
||||
crate::entity::attributes::send_attribute_updates_for_living(
|
||||
living,
|
||||
vec![attribute.clone()],
|
||||
)
|
||||
.await;
|
||||
|
||||
context
|
||||
.source
|
||||
.send_feedback(
|
||||
TextComponent::translate_cross(
|
||||
translation::java::COMMANDS_ATTRIBUTE_MODIFIER_REMOVE_SUCCESS,
|
||||
translation::java::COMMANDS_ATTRIBUTE_MODIFIER_REMOVE_SUCCESS,
|
||||
[
|
||||
TextComponent::text(modifier_id),
|
||||
TextComponent::translate(attribute_translation_key(&attribute), []),
|
||||
target.get_name(),
|
||||
],
|
||||
),
|
||||
true,
|
||||
)
|
||||
.await;
|
||||
|
||||
Ok(1)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct ModifierGetExecutor;
|
||||
|
||||
impl CommandExecutor for ModifierGetExecutor {
|
||||
fn execute<'a>(&'a self, context: &'a CommandContext) -> CommandExecutorResult<'a> {
|
||||
Box::pin(async move {
|
||||
let target = EntityArgumentType::get_entity(context, ARG_TARGET).await?;
|
||||
let living = target
|
||||
.get_living_entity()
|
||||
.ok_or_else(|| FAILED_ENTITY_ERROR.create_without_context(target.get_name()))?;
|
||||
|
||||
let attribute = AttributeArgumentType::get(context, ARG_ATTRIBUTE)?;
|
||||
let uuid = UuidArgumentType::get(context, ARG_UUID)?;
|
||||
|
||||
let modifier_id = uuid.to_string();
|
||||
|
||||
let val = {
|
||||
let map = living.attributes.read().unwrap();
|
||||
let inst = map.get(&attribute.id).ok_or_else(|| {
|
||||
NO_ATTRIBUTE_ERROR.create_without_context(
|
||||
target.get_name(),
|
||||
TextComponent::translate(attribute_translation_key(&attribute), []),
|
||||
)
|
||||
})?;
|
||||
|
||||
let modifier = inst
|
||||
.modifiers
|
||||
.iter()
|
||||
.find(|m| m.id == modifier_id)
|
||||
.ok_or_else(|| {
|
||||
NO_MODIFIER_ERROR.create_without_context(
|
||||
TextComponent::translate(attribute_translation_key(&attribute), []),
|
||||
target.get_name(),
|
||||
TextComponent::text(modifier_id.clone()),
|
||||
)
|
||||
})?;
|
||||
|
||||
modifier.amount
|
||||
};
|
||||
|
||||
context
|
||||
.source
|
||||
.send_feedback(
|
||||
TextComponent::translate_cross(
|
||||
translation::java::COMMANDS_ATTRIBUTE_MODIFIER_VALUE_GET_SUCCESS,
|
||||
translation::java::COMMANDS_ATTRIBUTE_MODIFIER_VALUE_GET_SUCCESS,
|
||||
[
|
||||
TextComponent::text(modifier_id),
|
||||
TextComponent::translate(attribute_translation_key(&attribute), []),
|
||||
target.get_name(),
|
||||
TextComponent::text(val.to_string()),
|
||||
],
|
||||
),
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
|
||||
Ok(val as i32)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[expect(clippy::too_many_lines)]
|
||||
pub fn register(dispatcher: &mut CommandDispatcher, registry: &mut PermissionRegistry) {
|
||||
registry.register_permission_or_panic(Permission::new(
|
||||
PERMISSION,
|
||||
DESCRIPTION,
|
||||
PermissionDefault::Op(PermissionLvl::Two),
|
||||
));
|
||||
|
||||
dispatcher.register(
|
||||
command("attribute", DESCRIPTION).requires(PERMISSION).then(
|
||||
argument(ARG_TARGET, EntityArgumentType::Entity).then(
|
||||
argument(ARG_ATTRIBUTE, AttributeArgumentType)
|
||||
.then(
|
||||
literal("get")
|
||||
.executes(GetExecutor {
|
||||
is_base: false,
|
||||
has_scale: false,
|
||||
})
|
||||
.then(argument(ARG_SCALE, DoubleArgumentType::any()).executes(
|
||||
GetExecutor {
|
||||
is_base: false,
|
||||
has_scale: true,
|
||||
},
|
||||
)),
|
||||
)
|
||||
.then(
|
||||
literal("base")
|
||||
.then(
|
||||
literal("get")
|
||||
.executes(GetExecutor {
|
||||
is_base: true,
|
||||
has_scale: false,
|
||||
})
|
||||
.then(argument(ARG_SCALE, DoubleArgumentType::any()).executes(
|
||||
GetExecutor {
|
||||
is_base: true,
|
||||
has_scale: true,
|
||||
},
|
||||
)),
|
||||
)
|
||||
.then(
|
||||
literal("set").then(
|
||||
argument(ARG_VALUE, DoubleArgumentType::any())
|
||||
.executes(BaseSetExecutor),
|
||||
),
|
||||
)
|
||||
.then(literal("reset").executes(BaseResetExecutor)),
|
||||
)
|
||||
.then(
|
||||
literal("modifier")
|
||||
.then(
|
||||
literal("add").then(
|
||||
argument(ARG_UUID, UuidArgumentType).then(
|
||||
argument(ARG_NAME, StringArgumentType::SingleWord).then(
|
||||
argument(ARG_VALUE, DoubleArgumentType::any())
|
||||
.then(literal("add").executes(
|
||||
ModifierAddExecutor {
|
||||
operation: ModifierOperation::Add,
|
||||
},
|
||||
))
|
||||
.then(literal("multiply").executes(
|
||||
ModifierAddExecutor {
|
||||
operation: ModifierOperation::MultiplyTotal,
|
||||
},
|
||||
))
|
||||
.then(literal("multiply_base").executes(
|
||||
ModifierAddExecutor {
|
||||
operation: ModifierOperation::MultiplyBase,
|
||||
},
|
||||
)),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.then(
|
||||
literal("remove").then(
|
||||
argument(ARG_UUID, UuidArgumentType)
|
||||
.executes(ModifierRemoveExecutor),
|
||||
),
|
||||
)
|
||||
.then(literal("value").then(literal("get").then(
|
||||
argument(ARG_UUID, UuidArgumentType).executes(ModifierGetExecutor),
|
||||
))),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -7,6 +7,7 @@ use pumpkin_util::{
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
mod advancement;
|
||||
mod attribute;
|
||||
mod ban;
|
||||
mod banip;
|
||||
mod banlist;
|
||||
@ -175,6 +176,7 @@ pub async fn default_dispatcher(
|
||||
trigger::register(&mut dispatcher, registry);
|
||||
scoreboard::register(&mut dispatcher, registry);
|
||||
clone::register(&mut dispatcher, registry);
|
||||
attribute::register(&mut dispatcher, registry);
|
||||
dispatcher
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user