Skip to main content

steel_core/command/builtins/
damage.rs

1//! Vanilla damage entity command.
2
3use super::super::{
4    brigadier::{ArgumentType, CommandNodeBuilder, CommandSyntaxError},
5    execution::{
6        CommandSource, SteelArgumentType, SteelCommandContext, SteelCommandRuntime, argument,
7        literal,
8    },
9    registration::CommandRegistration,
10};
11use crate::entity::damage::DamageSource;
12use steel_registry::vanilla_damage_types;
13use steel_utils::Identifier;
14use steel_utils::translations::{COMMANDS_DAMAGE_INVULNERABLE, COMMANDS_DAMAGE_SUCCESS};
15use text_components::TextComponent;
16
17pub(super) fn registration() -> CommandRegistration<CommandSource> {
18    CommandRegistration::new(Identifier::vanilla_static("damage"), |_| command())
19}
20
21fn command() -> CommandNodeBuilder<CommandSource, SteelCommandRuntime> {
22    literal("damage").then(
23        argument("target", SteelArgumentType::entity()).then(
24            argument("amount", ArgumentType::float(0.0, f32::MAX))
25                .executes(damage)
26                .then(
27                    argument("damageType", SteelArgumentType::damage_type())
28                        .executes(damage)
29                        .then(literal("at").then(
30                            argument("location", SteelArgumentType::vec3(true)).executes(damage),
31                        ))
32                        .then(
33                            literal("by").then(
34                                argument("entity", SteelArgumentType::entity())
35                                    .executes(damage)
36                                    .then(
37                                        literal("from").then(
38                                            argument("cause", SteelArgumentType::entity())
39                                                .executes(damage),
40                                        ),
41                                    ),
42                            ),
43                        ),
44                ),
45        ),
46    )
47}
48
49fn damage(context: &SteelCommandContext<CommandSource>) -> Result<i32, CommandSyntaxError> {
50    let target = context.entity("target")?;
51    let amount = context.float("amount")?;
52
53    // The base damage type for this command is generic
54    let damage_type = context
55        .damage_type("damageType")
56        .unwrap_or(&vanilla_damage_types::GENERIC);
57
58    // Create the DamageSource to apply modifiers after
59    let mut damage_source = DamageSource::environment(damage_type);
60
61    // If we can get "location" from the context, it's from "at"
62    if let Ok(coordinates) = context.coordinates("location") {
63        damage_source.source_position = Some(coordinates.position(context.source()));
64    }
65
66    // Else, it's from the "by", or maybe it's nothing
67    if let Ok(entity) = context.entity("entity") {
68        let entity_id = entity.id();
69
70        damage_source.direct_entity_id = Some(entity_id);
71        damage_source.causing_entity_id = Some(entity_id);
72
73        // Maybe even the causing entity is known
74        if let Ok(cause) = context.entity("cause") {
75            damage_source.causing_entity_id = Some(cause.id());
76        }
77    }
78
79    let Some(target_world) = target.level() else {
80        return Err(CommandSyntaxError::dynamic(
81            "The entity is not in a world or the world was dropped.",
82        ));
83    };
84
85    if target.hurt(&target_world, &damage_source, amount) {
86        context.source().send_success(
87            &COMMANDS_DAMAGE_SUCCESS
88                .message([
89                    TextComponent::plain(format!("{amount:?}")),
90                    target.display_name(),
91                ])
92                .component(),
93            true,
94        );
95        Ok(1)
96    } else {
97        context
98            .source()
99            .send_failure(COMMANDS_DAMAGE_INVULNERABLE.msg().component());
100        Ok(0)
101    }
102}