steel_core/command/builtins/
summon.rs1use std::sync::Arc;
4
5use glam::DVec3;
6use steel_registry::entity_type::EntityTypeRef;
7use steel_utils::{BlockPos, Identifier, translations, types::Difficulty};
8use text_components::{TextComponent, translation::Translation};
9
10use super::super::{
11 brigadier::{CommandNodeBuilder, CommandSyntaxError},
12 execution::{
13 CommandSource, SteelArgumentType, SteelCommandContext, SteelCommandRuntime, argument,
14 literal,
15 },
16 registration::CommandRegistration,
17};
18use crate::{
19 entity::{AddEntityError, ENTITIES, EntitySpawnReason, SharedEntity, next_entity_id},
20 world::World,
21};
22
23pub(super) fn registration() -> CommandRegistration<CommandSource> {
24 CommandRegistration::new(Identifier::vanilla_static("summon"), |_| command())
25}
26
27fn command() -> CommandNodeBuilder<CommandSource, SteelCommandRuntime> {
28 literal("summon").then(
29 argument("entity", SteelArgumentType::summonable_entity())
30 .executes(|context| summon_entity(context, context.source().position()))
31 .then(
32 argument("pos", SteelArgumentType::vec3(true)).executes(|context| {
33 let position = context.coordinates("pos")?;
34 summon_entity(context, position.position(context.source()))
35 }),
36 ),
37 )
38 }
41
42fn summon_entity(
43 context: &SteelCommandContext<CommandSource>,
44 position: DVec3,
45) -> Result<i32, CommandSyntaxError> {
46 let entity_type = context.entity_type("entity")?;
47 let entity = create_entity(context, entity_type, position)?;
48 let message = translations::COMMANDS_SUMMON_SUCCESS
49 .message([entity.display_name()])
50 .component();
51 context.source().send_success(&message, true);
52 Ok(1)
53}
54
55pub(super) fn create_entity(
56 context: &SteelCommandContext<CommandSource>,
57 entity_type: EntityTypeRef,
58 position: DVec3,
59) -> Result<SharedEntity, CommandSyntaxError> {
60 if !World::is_in_spawnable_bounds(BlockPos::from(position)) {
61 return Err(command_failed(
62 &translations::COMMANDS_SUMMON_INVALID_POSITION,
63 ));
64 }
65
66 let world = context.source().world();
67 if world.difficulty() == Difficulty::Peaceful && !entity_type.allowed_in_peaceful {
68 return Err(command_failed(
69 &translations::COMMANDS_SUMMON_FAILED_PEACEFUL,
70 ));
71 }
72
73 let Some(entity) = ENTITIES.create(
74 entity_type,
75 next_entity_id(),
76 position,
77 Arc::downgrade(world),
78 ) else {
79 return Err(command_failed(&translations::COMMANDS_SUMMON_FAILED));
80 };
81
82 if let Some(mob) = entity.as_mob() {
83 let _ = mob.finalize_spawn(world, EntitySpawnReason::Command, None);
84 }
85
86 match world.try_add_entity(Arc::clone(&entity)) {
87 Ok(()) => Ok(entity),
88 Err(AddEntityError::DuplicateUuid { .. }) => {
89 Err(command_failed(&translations::COMMANDS_SUMMON_FAILED_UUID))
90 }
91 Err(_) => Err(command_failed(&translations::COMMANDS_SUMMON_FAILED)),
92 }
93}
94
95fn command_failed(translation: &'static Translation<0>) -> CommandSyntaxError {
96 CommandSyntaxError::dynamic(TextComponent::from(translation))
97}
98
99#[cfg(test)]
100mod tests {
101 use super::super::create_dispatcher;
102 use crate::bootstrap::init_globals;
103 use crate::command::{
104 brigadier::{CommandDispatcher, NodeId},
105 execution::{CommandSource, SteelArgumentType, SteelCommandRuntime},
106 };
107
108 type Dispatcher = CommandDispatcher<CommandSource, SteelCommandRuntime>;
109
110 fn child(dispatcher: &Dispatcher, parent: NodeId, name: &str) -> NodeId {
111 let Some(children) = dispatcher.children(parent) else {
112 panic!("parent node should exist");
113 };
114 let Some(child) = children.iter().copied().find(|child| {
115 dispatcher
116 .node(*child)
117 .is_some_and(|node| node.name() == name)
118 }) else {
119 panic!("child {name} should exist");
120 };
121 child
122 }
123
124 #[test]
125 fn summon_graph_uses_typed_entity_and_deferred_position_arguments() {
126 init_globals();
127 let Ok(dispatcher) = create_dispatcher() else {
128 panic!("built-in commands should register");
129 };
130 let root = child(&dispatcher, dispatcher.root(), "summon");
131 let entity = child(&dispatcher, root, "entity");
132 assert_eq!(
133 dispatcher
134 .node(entity)
135 .and_then(|node| node.argument_type()),
136 Some(&SteelArgumentType::summonable_entity())
137 );
138 assert!(matches!(
139 dispatcher.node(entity),
140 Some(node) if node.is_executable()
141 ));
142
143 let position = child(&dispatcher, entity, "pos");
144 assert_eq!(
145 dispatcher
146 .node(position)
147 .and_then(|node| node.argument_type()),
148 Some(&SteelArgumentType::vec3(true))
149 );
150 assert!(matches!(
151 dispatcher.node(position),
152 Some(node) if node.is_executable()
153 ));
154 assert!(dispatcher.children(position).is_some_and(<[_]>::is_empty));
155 }
156}