Skip to main content

steel_core/command/builtins/
damage.rs

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