Skip to main content

steel_core/command/builtins/
swing.rs

1//! swing animation command
2
3use std::slice;
4
5use steel_registry::data_components::vanilla_components::SwingAnimation;
6use steel_utils::{Identifier, translations, types::InteractionHand};
7use text_components::TextComponent;
8
9use super::super::{
10    brigadier::{CommandNodeBuilder, CommandSyntaxError},
11    execution::{
12        CommandSource, SteelArgumentType, SteelCommandContext, SteelCommandRuntime, argument,
13        literal,
14    },
15    registration::CommandRegistration,
16};
17use crate::entity::SharedEntity;
18
19pub(super) fn registration() -> CommandRegistration<CommandSource> {
20    CommandRegistration::new(Identifier::vanilla_static("swing"), |_| command())
21}
22
23fn command() -> CommandNodeBuilder<CommandSource, SteelCommandRuntime> {
24    literal("swing").executes(swing_self).then(
25        argument("targets", SteelArgumentType::entities())
26            .executes(|context| {
27                swing_targets(context, InteractionHand::MainHand, SwingAnimation::DEFAULT)
28            })
29            .then(hand_swing("mainhand", InteractionHand::MainHand))
30            .then(hand_swing("offhand", InteractionHand::OffHand)),
31    )
32}
33
34fn hand_swing(
35    name: &'static str,
36    hand: InteractionHand,
37) -> CommandNodeBuilder<CommandSource, SteelCommandRuntime> {
38    literal(name)
39        .executes(move |context| swing_targets(context, hand, SwingAnimation::DEFAULT))
40        .then(
41            argument("animation", SteelArgumentType::swing_animation())
42                .executes(move |context| {
43                    let animation = SwingAnimation::new(
44                        context.swing_animation("animation")?,
45                        SwingAnimation::DEFAULT.duration,
46                    );
47                    swing_targets(context, hand, animation)
48                })
49                .then(
50                    argument("duration", SteelArgumentType::time(1)).executes(move |context| {
51                        let animation = SwingAnimation::new(
52                            context.swing_animation("animation")?,
53                            context.time("duration")?,
54                        );
55                        swing_targets(context, hand, animation)
56                    }),
57                ),
58        )
59}
60
61fn swing_self(context: &SteelCommandContext<CommandSource>) -> Result<i32, CommandSyntaxError> {
62    let Some(entity) = context.source().entity() else {
63        return Err(CommandSyntaxError::dynamic(TextComponent::from(
64            &translations::PERMISSIONS_REQUIRES_ENTITY,
65        )));
66    };
67    let (result, message) = swing_entities(
68        slice::from_ref(entity),
69        InteractionHand::MainHand,
70        SwingAnimation::DEFAULT,
71    )?;
72    context.source().send_success(&message, true);
73    Ok(result)
74}
75
76fn swing_targets(
77    context: &SteelCommandContext<CommandSource>,
78    hand: InteractionHand,
79    animation: SwingAnimation,
80) -> Result<i32, CommandSyntaxError> {
81    let targets = context.entities("targets")?;
82    let (result, message) = swing_entities(&targets, hand, animation)?;
83    context.source().send_success(&message, true);
84    Ok(result)
85}
86
87fn swing_entities(
88    targets: &[SharedEntity],
89    hand: InteractionHand,
90    animation: SwingAnimation,
91) -> Result<(i32, TextComponent), CommandSyntaxError> {
92    let living_targets = targets
93        .iter()
94        .filter_map(|target| target.as_living_entity())
95        .collect::<Vec<_>>();
96
97    if living_targets.is_empty() {
98        return Err(CommandSyntaxError::dynamic(TextComponent::from(
99            &translations::COMMANDS_SWING_FAILED_NOTLIVING,
100        )));
101    }
102
103    let result = i32::try_from(living_targets.len()).map_err(|_| {
104        CommandSyntaxError::dynamic("Target count exceeds the command result range")
105    })?;
106
107    for living in &living_targets {
108        living.swing(hand, animation, true);
109    }
110
111    let message = if let [target] = living_targets.as_slice() {
112        translations::COMMANDS_SWING_SUCCESS_SINGLE
113            .message([target.display_name()])
114            .component()
115    } else {
116        translations::COMMANDS_SWING_SUCCESS_MULTIPLE
117            .message([result.to_string()])
118            .component()
119    };
120
121    Ok((result, message))
122}