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.heightmap("heightmap")?;
88                    let Some(height) = source.world().height_at(
89                        heightmap,
90                        position.x.floor() as i32,
91                        position.z.floor() as i32,
92                    ) else {
93                        return Err(CommandSyntaxError::dynamic(TextComponent::from(
94                            &translations::ARGUMENT_POS_UNLOADED,
95                        )));
96                    };
97                    Ok(source.with_position(position.with_y(f64::from(height))))
98                },
99            ),
100        ))
101}
102
103pub(super) fn rotated_operation() -> Builder {
104    literal("rotated")
105        .then(
106            argument("rot", SteelArgumentType::rotation()).redirects_with(
107                EXECUTE_ROOT,
108                |context| {
109                    let rotation = required_coordinates(context, "rot")?.rotation(context.source());
110                    Ok(context.source().with_rotation(rotation))
111                },
112            ),
113        )
114        .then(
115            literal("as").then(argument("targets", SteelArgumentType::entities()).forks(
116                EXECUTE_ROOT,
117                |context| {
118                    Ok(context
119                        .optional_entities("targets")?
120                        .into_iter()
121                        .map(|entity| context.source().with_rotation(entity.rotation()))
122                        .collect())
123                },
124            )),
125        )
126}
127
128pub(super) fn facing_operation() -> Builder {
129    literal("facing")
130        .then(
131            literal("entity").then(argument("targets", SteelArgumentType::entities()).then(
132                argument("anchor", SteelArgumentType::entity_anchor()).forks(
133                    EXECUTE_ROOT,
134                    |context| {
135                        let anchor = context.entity_anchor("anchor")?;
136                        Ok(context
137                            .optional_entities("targets")?
138                            .into_iter()
139                            .map(|entity| {
140                                context
141                                    .source()
142                                    .facing_position(anchor.position(entity.as_ref()))
143                            })
144                            .collect())
145                    },
146                ),
147            )),
148        )
149        .then(
150            argument("pos", SteelArgumentType::vec3(true)).redirects_with(
151                EXECUTE_ROOT,
152                |context| {
153                    let position = required_coordinates(context, "pos")?.position(context.source());
154                    Ok(context.source().facing_position(position))
155                },
156            ),
157        )
158}
159
160pub(super) fn align_operation() -> Builder {
161    literal("align").then(
162        argument("axes", SteelArgumentType::swizzle()).redirects_with(
163            EXECUTE_ROOT,
164            |context: &SteelCommandContext<CommandSource>| {
165                let axes = context.swizzle("axes")?;
166                Ok(context
167                    .source()
168                    .with_position(axes.align(context.source().position())))
169            },
170        ),
171    )
172}
173
174pub(super) fn anchored_operation() -> Builder {
175    literal("anchored").then(
176        argument("anchor", SteelArgumentType::entity_anchor()).redirects_with(
177            EXECUTE_ROOT,
178            |context: &SteelCommandContext<CommandSource>| {
179                let anchor = context.entity_anchor("anchor")?;
180                Ok(context.source().with_anchor(anchor))
181            },
182        ),
183    )
184}
185
186pub(super) fn in_operation() -> Builder {
187    literal("in").then(
188        argument("dimension", SteelArgumentType::world()).redirects_with(
189            EXECUTE_ROOT,
190            |context: &SteelCommandContext<CommandSource>| {
191                let world = context
192                    .world_argument("dimension")?
193                    .resolve(context.source())?;
194                context.source().with_world(world)
195            },
196        ),
197    )
198}
199
200pub(super) fn summon_operation() -> Builder {
201    literal("summon").then(
202        argument("entity", SteelArgumentType::summonable_entity()).redirects_with(
203            EXECUTE_ROOT,
204            |context| {
205                let entity_type = context.entity_type("entity")?;
206                let entity =
207                    summon::create_entity(context, entity_type, context.source().position())?;
208                Ok(context.source().with_entity(entity))
209            },
210        ),
211    )
212}
213
214pub(super) fn on_relations() -> Builder {
215    literal("on")
216        .then(literal("vehicle").forks(EXECUTE_ROOT, |context| {
217            Ok(one_relation_sources(context.source(), |entity| {
218                entity.vehicle()
219            }))
220        }))
221        .then(literal("controller").forks(EXECUTE_ROOT, |context| {
222            Ok(one_relation_sources(context.source(), |entity| {
223                entity.controlling_passenger()
224            }))
225        }))
226        .then(literal("passengers").forks(EXECUTE_ROOT, |context| {
227            Ok(passenger_sources(context.source()))
228        }))
229}
230
231fn one_relation_sources(
232    source: &CommandSource,
233    relation: impl FnOnce(&SharedEntity) -> Option<SharedEntity>,
234) -> Vec<CommandSource> {
235    let entity = source.entity().and_then(relation);
236    entity
237        .filter(|entity| !entity.is_removed())
238        .map_or_else(Vec::new, |entity| vec![source.with_entity(entity)])
239}
240
241fn passenger_sources(source: &CommandSource) -> Vec<CommandSource> {
242    source.entity().map_or_else(Vec::new, |entity| {
243        entity
244            .passengers()
245            .into_iter()
246            .filter(|passenger| !passenger.is_removed())
247            .map(|passenger| source.with_entity(passenger))
248            .collect()
249    })
250}
251
252fn required_coordinates(
253    context: &SteelCommandContext<CommandSource>,
254    name: &str,
255) -> Result<super::super::super::execution::Coordinates, CommandSyntaxError> {
256    context.coordinates(name)
257}