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