1#![cfg_attr(
2 not(test),
3 expect(
4 dead_code,
5 reason = "custom runtime variants are reserved for future keyed command integrations"
6 )
7)]
8
9use std::sync::Arc;
10
11use super::{
12 BiomeOrTag, BlockInput, BlockPredicate, ChainModifiers, CommandResultSuspension, CommandSource,
13 Coordinates, ExecutionCommandSource, ExecutionControl, GameProfileArgument, IntRange,
14 ItemPredicate, PermissionGroupName, ScoreHolderArgument, ScoreHolderWildcard,
15 SteelArgumentType, StructureOrTagKey, WorldArgument,
16 argument::{
17 ComponentValue, CoordinateAxes, DomainValue, EnchantmentValue, EntityTypeValue,
18 GameModeValue, IdentifierValue, ItemStackValue, NbtPathValue, ObjectiveValue,
19 SteelArgumentValue, TimeValue, TimelineValue, WorldClockValue,
20 },
21 selector::EntitySelector,
22};
23use crate::command::brigadier::{
24 ArgumentSuggestionContext, CommandContext, CommandNodeBuilder, CommandRedirectTarget,
25 CommandRuntime, CommandSyntaxError, ContextChain,
26};
27use crate::command::execution::argument::{AdvancementValue, DamageTypeValue};
28use crate::command::incorrectly_typed_argument;
29use crate::{
30 chunk::heightmap::HeightmapType,
31 entity::{EntityAnchor, SharedEntity},
32 permission::{PermissionMetadataExpression, PermissionRuleExpression},
33 player::Player,
34 scoreboard::ScoreHolder,
35};
36use steel_registry::advancement::registry::AdvancementRef;
37use steel_registry::damage_type::DamageTypeRef;
38use steel_registry::data_components::vanilla_components::SwingAnimationType;
39use steel_registry::{
40 enchantment::EnchantmentRef, entity_type::EntityTypeRef, item_stack::ItemStack,
41 timeline::TimelineRef, world_clock::WorldClockRef,
42};
43use steel_utils::{DowncastType, Identifier, nbt::NbtPath, translations, types::GameType};
44use text_components::TextComponent;
45
46pub(crate) struct SteelCommandRuntime;
48
49pub(crate) type SteelCommandContext<S> = CommandContext<S, SteelCommandRuntime>;
50pub(crate) type SteelContextChain<S> = ContextChain<S, SteelCommandRuntime>;
51pub(crate) type SteelSuggestionContext<'context, S> =
52 ArgumentSuggestionContext<'context, S, SteelArgumentValue>;
53
54type StandardExecutor<S> =
55 dyn Fn(&SteelCommandContext<S>) -> Result<i32, CommandSyntaxError> + Send + Sync;
56type SuspendedExecutor<S> = dyn Fn(&SteelCommandContext<S>) -> Result<Box<dyn CommandResultSuspension>, CommandSyntaxError>
57 + Send
58 + Sync;
59type StandardModifier<S> =
60 dyn Fn(&SteelCommandContext<S>) -> Result<Vec<S>, CommandSyntaxError> + Send + Sync;
61
62pub(crate) enum SteelExecutor<S>
64where
65 S: ExecutionCommandSource,
66{
67 Standard(Box<StandardExecutor<S>>),
68 Suspended(Box<SuspendedExecutor<S>>),
69 Custom(Arc<dyn CustomCommandExecutor<S>>),
70}
71
72pub(crate) enum SteelModifier<S>
74where
75 S: ExecutionCommandSource,
76{
77 Standard(Box<StandardModifier<S>>),
78 Custom(Arc<dyn CustomModifierExecutor<S>>),
79}
80
81pub(crate) trait CustomCommandExecutor<S>: Send + Sync
83where
84 S: ExecutionCommandSource,
85{
86 fn run(
87 &self,
88 source: Arc<S>,
89 chain: &SteelContextChain<S>,
90 modifiers: ChainModifiers,
91 control: &mut ExecutionControl<'_, S>,
92 );
93}
94
95pub(crate) trait CustomModifierExecutor<S>: Send + Sync
97where
98 S: ExecutionCommandSource,
99{
100 fn apply(
101 &self,
102 original_source: Arc<S>,
103 sources: Vec<Arc<S>>,
104 chain: &SteelContextChain<S>,
105 modifiers: ChainModifiers,
106 control: &mut ExecutionControl<'_, S>,
107 );
108}
109
110impl<S> CommandRuntime<S> for SteelCommandRuntime
111where
112 S: ExecutionCommandSource,
113{
114 type Argument = SteelArgumentType;
115 type ArgumentValue = SteelArgumentValue;
116 type Executor = SteelExecutor<S>;
117 type Modifier = SteelModifier<S>;
118}
119
120pub(crate) fn literal<S>(name: impl Into<Box<str>>) -> CommandNodeBuilder<S, SteelCommandRuntime>
122where
123 S: ExecutionCommandSource,
124{
125 CommandNodeBuilder::literal(name)
126}
127
128pub(crate) fn argument<S>(
130 name: impl Into<Box<str>>,
131 argument_type: impl Into<SteelArgumentType>,
132) -> CommandNodeBuilder<S, SteelCommandRuntime>
133where
134 S: ExecutionCommandSource,
135{
136 CommandNodeBuilder::argument(name, argument_type.into())
137}
138
139impl<S> CommandNodeBuilder<S, SteelCommandRuntime>
140where
141 S: ExecutionCommandSource,
142{
143 #[must_use]
145 pub(crate) fn executes(
146 self,
147 executor: impl Fn(&SteelCommandContext<S>) -> Result<i32, CommandSyntaxError>
148 + Send
149 + Sync
150 + 'static,
151 ) -> Self {
152 self.executes_with_executor(Arc::new(SteelExecutor::Standard(Box::new(executor))))
153 }
154
155 #[must_use]
157 pub(crate) fn executes_suspended<T>(
158 self,
159 executor: impl Fn(&SteelCommandContext<S>) -> Result<T, CommandSyntaxError>
160 + Send
161 + Sync
162 + 'static,
163 ) -> Self
164 where
165 T: CommandResultSuspension,
166 {
167 let executor = move |context: &SteelCommandContext<S>| {
168 executor(context)
169 .map(|suspension| Box::new(suspension) as Box<dyn CommandResultSuspension>)
170 };
171 self.executes_with_executor(Arc::new(SteelExecutor::Suspended(Box::new(executor))))
172 }
173
174 #[must_use]
176 pub(crate) fn executes_custom(self, executor: impl CustomCommandExecutor<S> + 'static) -> Self {
177 self.executes_with_executor(Arc::new(SteelExecutor::Custom(Arc::new(executor))))
178 }
179
180 #[must_use]
182 pub(crate) fn redirects_with(
183 self,
184 target: impl Into<CommandRedirectTarget>,
185 modifier: impl Fn(&SteelCommandContext<S>) -> Result<S, CommandSyntaxError>
186 + Send
187 + Sync
188 + 'static,
189 ) -> Self {
190 let modifier = SteelModifier::Standard(Box::new(move |context| {
191 modifier(context).map(|source| vec![source])
192 }));
193 self.redirects_with_modifier(target, Arc::new(modifier), false)
194 }
195
196 #[must_use]
198 pub(crate) fn forks(
199 self,
200 target: impl Into<CommandRedirectTarget>,
201 modifier: impl Fn(&SteelCommandContext<S>) -> Result<Vec<S>, CommandSyntaxError>
202 + Send
203 + Sync
204 + 'static,
205 ) -> Self {
206 self.redirects_with_modifier(
207 target,
208 Arc::new(SteelModifier::Standard(Box::new(modifier))),
209 true,
210 )
211 }
212
213 #[must_use]
215 pub(crate) fn redirects_custom(
216 self,
217 target: impl Into<CommandRedirectTarget>,
218 modifier: impl CustomModifierExecutor<S> + 'static,
219 forks: bool,
220 ) -> Self {
221 self.redirects_with_modifier(
222 target,
223 Arc::new(SteelModifier::Custom(Arc::new(modifier))),
224 forks,
225 )
226 }
227}
228
229impl<S> SteelCommandContext<S>
230where
231 S: ExecutionCommandSource,
232{
233 fn typed_argument<T: DowncastType>(&self, name: &str) -> Result<&T, CommandSyntaxError> {
234 self.argument(name)?
235 .downcast_ref::<T>()
236 .ok_or_else(|| incorrectly_typed_argument(name))
237 }
238
239 pub(crate) fn time(&self, name: &str) -> Result<i32, CommandSyntaxError> {
241 self.typed_argument::<TimeValue>(name).map(|value| value.0)
242 }
243
244 pub(crate) fn swing_animation(
245 &self,
246 name: &str,
247 ) -> Result<SwingAnimationType, CommandSyntaxError> {
248 self.typed_argument::<super::swing_animation::SwingAnimationValue>(name)
249 .map(|value| value.0)
250 }
251
252 pub(crate) fn coordinates(&self, name: &str) -> Result<Coordinates, CommandSyntaxError> {
254 self.typed_argument::<Coordinates>(name).copied()
255 }
256
257 pub(crate) fn entity_anchor(&self, name: &str) -> Result<EntityAnchor, CommandSyntaxError> {
259 self.typed_argument::<EntityAnchor>(name).copied()
260 }
261
262 pub(crate) fn swizzle(&self, name: &str) -> Result<CoordinateAxes, CommandSyntaxError> {
263 self.typed_argument::<CoordinateAxes>(name).copied()
264 }
265
266 pub(crate) fn heightmap(&self, name: &str) -> Result<HeightmapType, CommandSyntaxError> {
267 self.typed_argument::<HeightmapType>(name).copied()
268 }
269
270 pub(crate) fn score_holder_argument(
271 &self,
272 name: &str,
273 ) -> Result<&ScoreHolderArgument, CommandSyntaxError> {
274 self.typed_argument(name)
275 }
276
277 pub(crate) fn objective_name(&self, name: &str) -> Result<&str, CommandSyntaxError> {
278 self.typed_argument::<ObjectiveValue>(name)
279 .map(|value| value.0.as_ref())
280 }
281
282 pub(crate) fn int_range(&self, name: &str) -> Result<IntRange, CommandSyntaxError> {
283 self.typed_argument::<IntRange>(name).copied()
284 }
285
286 pub(crate) fn biome_or_tag(&self, name: &str) -> Result<&BiomeOrTag, CommandSyntaxError> {
287 self.typed_argument(name)
288 }
289
290 pub(crate) fn structure_or_tag_key(
291 &self,
292 name: &str,
293 ) -> Result<&StructureOrTagKey, CommandSyntaxError> {
294 self.typed_argument(name)
295 }
296
297 pub(crate) fn block_predicate(
298 &self,
299 name: &str,
300 ) -> Result<&BlockPredicate, CommandSyntaxError> {
301 self.typed_argument(name)
302 }
303
304 pub(crate) fn block_input(&self, name: &str) -> Result<&BlockInput, CommandSyntaxError> {
305 self.typed_argument(name)
306 }
307
308 pub(crate) fn domain(&self, name: &str) -> Result<&str, CommandSyntaxError> {
310 self.typed_argument::<DomainValue>(name)
311 .map(|value| value.0.as_ref())
312 }
313
314 pub(crate) fn world_argument(&self, name: &str) -> Result<&WorldArgument, CommandSyntaxError> {
315 self.typed_argument(name)
316 }
317
318 pub(crate) fn game_mode(&self, name: &str) -> Result<GameType, CommandSyntaxError> {
320 self.typed_argument::<GameModeValue>(name)
321 .map(|value| value.0)
322 }
323
324 pub(crate) fn entity_type(&self, name: &str) -> Result<EntityTypeRef, CommandSyntaxError> {
325 self.typed_argument::<EntityTypeValue>(name)
326 .map(|value| value.0)
327 }
328
329 pub(crate) fn enchantment(&self, name: &str) -> Result<EnchantmentRef, CommandSyntaxError> {
330 self.typed_argument::<EnchantmentValue>(name)
331 .map(|value| value.0)
332 }
333
334 pub(crate) fn damage_type(&self, name: &str) -> Result<DamageTypeRef, CommandSyntaxError> {
335 self.typed_argument::<DamageTypeValue>(name)
336 .map(|value| value.0)
337 }
338
339 pub(crate) fn item_stack(&self, name: &str) -> Result<&ItemStack, CommandSyntaxError> {
340 self.typed_argument::<ItemStackValue>(name)
341 .map(|value| &value.0)
342 }
343
344 pub(crate) fn item_predicate(&self, name: &str) -> Result<&ItemPredicate, CommandSyntaxError> {
345 self.typed_argument(name)
346 }
347
348 pub(crate) fn text_component(&self, name: &str) -> Result<&TextComponent, CommandSyntaxError> {
349 self.typed_argument::<ComponentValue>(name)
350 .map(|value| &value.0)
351 }
352
353 pub(crate) fn nbt_path(&self, name: &str) -> Result<&NbtPath, CommandSyntaxError> {
354 self.typed_argument::<NbtPathValue>(name)
355 .map(|value| &value.0)
356 }
357
358 pub(crate) fn identifier(&self, name: &str) -> Result<&Identifier, CommandSyntaxError> {
359 self.typed_argument::<IdentifierValue>(name)
360 .map(|value| &value.0)
361 }
362
363 pub(crate) fn world_clock(&self, name: &str) -> Result<WorldClockRef, CommandSyntaxError> {
364 self.typed_argument::<WorldClockValue>(name)
365 .map(|value| value.0)
366 }
367
368 pub(crate) fn timeline(&self, name: &str) -> Result<TimelineRef, CommandSyntaxError> {
369 self.typed_argument::<TimelineValue>(name)
370 .map(|value| value.0)
371 }
372
373 pub(crate) fn advancement(&self, name: &str) -> Result<AdvancementRef, CommandSyntaxError> {
374 self.typed_argument::<AdvancementValue>(name)
375 .map(|value| value.0)
376 }
377
378 pub(crate) fn entity_selector(
379 &self,
380 name: &str,
381 ) -> Result<&EntitySelector, CommandSyntaxError> {
382 self.typed_argument(name)
383 }
384
385 pub(crate) fn game_profile_argument(
386 &self,
387 name: &str,
388 ) -> Result<&GameProfileArgument, CommandSyntaxError> {
389 self.typed_argument(name)
390 }
391
392 pub(crate) fn permission_rule_expression(
393 &self,
394 name: &str,
395 ) -> Result<&PermissionRuleExpression, CommandSyntaxError> {
396 self.typed_argument(name)
397 }
398
399 pub(crate) fn permission_metadata_expression(
400 &self,
401 name: &str,
402 ) -> Result<&PermissionMetadataExpression, CommandSyntaxError> {
403 self.typed_argument(name)
404 }
405
406 pub(crate) fn permission_group(
407 &self,
408 name: &str,
409 ) -> Result<&PermissionGroupName, CommandSyntaxError> {
410 self.typed_argument(name)
411 }
412}
413
414impl<S> SteelSuggestionContext<'_, S> {
415 fn typed_argument<T: DowncastType>(&self, name: &str) -> Result<&T, CommandSyntaxError> {
416 self.argument(name)?
417 .downcast_ref::<T>()
418 .ok_or_else(|| incorrectly_typed_argument(name))
419 }
420
421 pub(crate) fn advancement(&self, name: &str) -> Result<AdvancementRef, CommandSyntaxError> {
423 self.typed_argument::<AdvancementValue>(name)
424 .map(|value| value.0)
425 }
426}
427
428impl SteelCommandContext<CommandSource> {
429 pub(crate) fn score_holders(
430 &self,
431 name: &str,
432 wildcard: ScoreHolderWildcard,
433 ) -> Result<Vec<ScoreHolder>, CommandSyntaxError> {
434 let holders = self
435 .score_holder_argument(name)?
436 .resolve(self.source(), wildcard)?;
437 if holders.is_empty() {
438 Err(CommandSyntaxError::dynamic(TextComponent::from(
439 &translations::ARGUMENT_SCORE_HOLDER_EMPTY,
440 )))
441 } else {
442 Ok(holders)
443 }
444 }
445
446 pub(crate) fn score_holder(&self, name: &str) -> Result<ScoreHolder, CommandSyntaxError> {
447 let mut holders = self.score_holders(name, ScoreHolderWildcard::Empty)?;
448 Ok(holders.remove(0))
449 }
450
451 pub(crate) fn optional_entities(
452 &self,
453 name: &str,
454 ) -> Result<Vec<SharedEntity>, CommandSyntaxError> {
455 self.entity_selector(name)?.find_entities(self.source())
456 }
457
458 pub(crate) fn entities(&self, name: &str) -> Result<Vec<SharedEntity>, CommandSyntaxError> {
459 let entities = self.optional_entities(name)?;
460 if entities.is_empty() {
461 Err(CommandSyntaxError::dynamic(TextComponent::from(
462 &translations::ARGUMENT_ENTITY_NOTFOUND_ENTITY,
463 )))
464 } else {
465 Ok(entities)
466 }
467 }
468
469 pub(crate) fn entity(&self, name: &str) -> Result<SharedEntity, CommandSyntaxError> {
470 let mut entities = self.entities(name)?;
471 if entities.len() != 1 {
472 return Err(CommandSyntaxError::dynamic(TextComponent::from(
473 &translations::ARGUMENT_ENTITY_TOOMANY,
474 )));
475 }
476 Ok(entities.remove(0))
477 }
478
479 pub(crate) fn optional_players(
480 &self,
481 name: &str,
482 ) -> Result<Vec<Arc<Player>>, CommandSyntaxError> {
483 self.entity_selector(name)?.find_players(self.source())
484 }
485
486 pub(crate) fn players(&self, name: &str) -> Result<Vec<Arc<Player>>, CommandSyntaxError> {
487 let players = self.optional_players(name)?;
488 if players.is_empty() {
489 Err(CommandSyntaxError::dynamic(TextComponent::from(
490 &translations::ARGUMENT_ENTITY_NOTFOUND_PLAYER,
491 )))
492 } else {
493 Ok(players)
494 }
495 }
496
497 pub(crate) fn player(&self, name: &str) -> Result<Arc<Player>, CommandSyntaxError> {
498 let mut players = self.players(name)?;
499 if players.len() != 1 {
500 return Err(CommandSyntaxError::dynamic(TextComponent::from(
501 &translations::ARGUMENT_PLAYER_TOOMANY,
502 )));
503 }
504 Ok(players.remove(0))
505 }
506}