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