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