steel_core/command/builtins/
swing.rs1use 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}