Skip to main content

steel_core/command/builtins/execute/
source.rs

1//! `/execute` operations that transform or fork the command source.
2
3use steel_utils::translations;
4use text_components::TextComponent;
5
6use super::super::super::{
7    brigadier::{CommandNodeBuilder, CommandRedirectTarget, CommandSyntaxError},
8    execution::{
9        CommandSource, SteelArgumentType, SteelCommandContext, SteelCommandRuntime, argument,
10        literal,
11    },
12};
13use super::super::summon;
14use crate::entity::{EntityAnchor, SharedEntity};
15
16type Builder = CommandNodeBuilder<CommandSource, SteelCommandRuntime>;
17
18const EXECUTE_ROOT: CommandRedirectTarget = CommandRedirectTarget::CommandRoot;
19
20pub(super) fn as_operation() -> Builder {
21    literal("as").then(argument("targets", SteelArgumentType::entities()).forks(
22        EXECUTE_ROOT,
23        |context| {
24            Ok(context
25                .optional_entities("targets")?
26                .into_iter()
27                .map(|entity| context.source().with_entity(entity))
28                .collect())
29        },
30    ))
31}
32
33pub(super) fn at_operation() -> Builder {
34    literal("at").then(argument("targets", SteelArgumentType::entities()).forks(
35        EXECUTE_ROOT,
36        |context| {
37            context
38                .optional_entities("targets")?
39                .into_iter()
40                .map(|entity| {
41                    let world = entity.level().ok_or_else(|| {
42                        CommandSyntaxError::dynamic("Selected entity is not in a loaded world")
43                    })?;
44                    Ok(context
45                        .source()
46                        .with_world(world)?
47                        .with_position(entity.position())
48                        .with_rotation(entity.rotation()))
49                })
50                .collect::<Result<Vec<_>, CommandSyntaxError>>()
51        },
52    ))
53}
54
55pub(super) fn positioned_operation() -> Builder {
56    literal("positioned")
57        .then(
58            argument("pos", SteelArgumentType::vec3(true)).redirects_with(
59                EXECUTE_ROOT,
60                |context: &SteelCommandContext<CommandSource>| {
61                    let position = required_coordinates(context, "pos")?.position(context.source());
62                    Ok(context
63                        .source()
64                        .with_position(position)
65                        .with_anchor(EntityAnchor::Feet))
66                },
67            ),
68        )
69        .then(
70            literal("as").then(argument("targets", SteelArgumentType::entities()).forks(
71                EXECUTE_ROOT,
72                |context| {
73                    Ok(context
74                        .optional_entities("targets")?
75                        .into_iter()
76                        .map(|entity| context.source().with_position(entity.position()))
77                        .collect())
78                },
79            )),
80        )
81        .then(literal("over").then(
82            argument("heightmap", SteelArgumentType::heightmap()).redirects_with(
83                EXECUTE_ROOT,
84                |context: &SteelCommandContext<CommandSource>| {
85                    let source = context.source();
86                    let position = source.position();
87                    let heightmap = context
88                        .heightmap("heightmap")
89                        .ok_or_else(|| missing_argument("heightmap"))?;
90                    let Some(height) = source.world().height_at(
91                        heightmap,
92                        position.x.floor() as i32,
93                        position.z.floor() as i32,
94                    ) else {
95                        return Err(CommandSyntaxError::dynamic(TextComponent::from(
96                            &translations::ARGUMENT_POS_UNLOADED,
97                        )));
98                    };
99                    Ok(source.with_position(position.with_y(f64::from(height))))
100                },
101            ),
102        ))
103}
104
105pub(super) fn rotated_operation() -> Builder {
106    literal("rotated")
107        .then(
108            argument("rot", SteelArgumentType::rotation()).redirects_with(
109                EXECUTE_ROOT,
110                |context| {
111                    let rotation = required_coordinates(context, "rot")?.rotation(context.source());
112                    Ok(context.source().with_rotation(rotation))
113                },
114            ),
115        )
116        .then(
117            literal("as").then(argument("targets", SteelArgumentType::entities()).forks(
118                EXECUTE_ROOT,
119                |context| {
120                    Ok(context
121                        .optional_entities("targets")?
122                        .into_iter()
123                        .map(|entity| context.source().with_rotation(entity.rotation()))
124                        .collect())
125                },
126            )),
127        )
128}
129
130pub(super) fn facing_operation() -> Builder {
131    literal("facing")
132        .then(
133            literal("entity").then(argument("targets", SteelArgumentType::entities()).then(
134                argument("anchor", SteelArgumentType::entity_anchor()).forks(
135                    EXECUTE_ROOT,
136                    |context| {
137                        let anchor = context
138                            .entity_anchor("anchor")
139                            .ok_or_else(|| missing_argument("anchor"))?;
140                        Ok(context
141                            .optional_entities("targets")?
142                            .into_iter()
143                            .map(|entity| {
144                                context
145                                    .source()
146                                    .facing_position(anchor.position(entity.as_ref()))
147                            })
148                            .collect())
149                    },
150                ),
151            )),
152        )
153        .then(
154            argument("pos", SteelArgumentType::vec3(true)).redirects_with(
155                EXECUTE_ROOT,
156                |context| {
157                    let position = required_coordinates(context, "pos")?.position(context.source());
158                    Ok(context.source().facing_position(position))
159                },
160            ),
161        )
162}
163
164pub(super) fn align_operation() -> Builder {
165    literal("align").then(
166        argument("axes", SteelArgumentType::swizzle()).redirects_with(
167            EXECUTE_ROOT,
168            |context: &SteelCommandContext<CommandSource>| {
169                let axes = context
170                    .swizzle("axes")
171                    .ok_or_else(|| missing_argument("axes"))?;
172                Ok(context
173                    .source()
174                    .with_position(axes.align(context.source().position())))
175            },
176        ),
177    )
178}
179
180pub(super) fn anchored_operation() -> Builder {
181    literal("anchored").then(
182        argument("anchor", SteelArgumentType::entity_anchor()).redirects_with(
183            EXECUTE_ROOT,
184            |context: &SteelCommandContext<CommandSource>| {
185                let anchor = context
186                    .entity_anchor("anchor")
187                    .ok_or_else(|| missing_argument("anchor"))?;
188                Ok(context.source().with_anchor(anchor))
189            },
190        ),
191    )
192}
193
194pub(super) fn in_operation() -> Builder {
195    literal("in").then(
196        argument("dimension", SteelArgumentType::world()).redirects_with(
197            EXECUTE_ROOT,
198            |context: &SteelCommandContext<CommandSource>| {
199                let world = context
200                    .world_argument("dimension")
201                    .ok_or_else(|| missing_argument("dimension"))?
202                    .resolve(context.source())?;
203                context.source().with_world(world)
204            },
205        ),
206    )
207}
208
209pub(super) fn summon_operation() -> Builder {
210    literal("summon").then(
211        argument("entity", SteelArgumentType::summonable_entity()).redirects_with(
212            EXECUTE_ROOT,
213            |context| {
214                let entity_type = context
215                    .entity_type("entity")
216                    .ok_or_else(|| missing_argument("entity"))?;
217                let entity =
218                    summon::create_entity(context, entity_type, context.source().position())?;
219                Ok(context.source().with_entity(entity))
220            },
221        ),
222    )
223}
224
225pub(super) fn on_relations() -> Builder {
226    literal("on")
227        .then(literal("vehicle").forks(EXECUTE_ROOT, |context| {
228            Ok(one_relation_sources(context.source(), |entity| {
229                entity.vehicle()
230            }))
231        }))
232        .then(literal("controller").forks(EXECUTE_ROOT, |context| {
233            Ok(one_relation_sources(context.source(), |entity| {
234                entity.controlling_passenger()
235            }))
236        }))
237        .then(literal("passengers").forks(EXECUTE_ROOT, |context| {
238            Ok(passenger_sources(context.source()))
239        }))
240}
241
242fn one_relation_sources(
243    source: &CommandSource,
244    relation: impl FnOnce(&SharedEntity) -> Option<SharedEntity>,
245) -> Vec<CommandSource> {
246    let entity = source.entity().and_then(relation);
247    entity
248        .filter(|entity| !entity.is_removed())
249        .map_or_else(Vec::new, |entity| vec![source.with_entity(entity)])
250}
251
252fn passenger_sources(source: &CommandSource) -> Vec<CommandSource> {
253    source.entity().map_or_else(Vec::new, |entity| {
254        entity
255            .passengers()
256            .into_iter()
257            .filter(|passenger| !passenger.is_removed())
258            .map(|passenger| source.with_entity(passenger))
259            .collect()
260    })
261}
262
263fn required_coordinates(
264    context: &SteelCommandContext<CommandSource>,
265    name: &str,
266) -> Result<super::super::super::execution::Coordinates, CommandSyntaxError> {
267    context
268        .coordinates(name)
269        .ok_or_else(|| missing_argument(name))
270}
271
272fn missing_argument(name: &str) -> CommandSyntaxError {
273    CommandSyntaxError::dynamic(format!(
274        "Parsed value for {name} is missing from the command context"
275    ))
276}