steel_core/command/builtins/
setblock.rs1use super::super::{
4 brigadier::{CommandNodeBuilder, CommandSyntaxError},
5 execution::{
6 CommandSource, SteelArgumentType, SteelCommandContext, SteelCommandRuntime, argument,
7 literal,
8 },
9 registration::CommandRegistration,
10};
11use crate::command::execution::BlockPredicate;
12use crate::world::tick_scheduler::TickPriority;
13use simdnbt::borrow::read_compound;
14use std::io::Cursor;
15use steel_registry::REGISTRY;
16use steel_registry::blocks::block_state_ext::BlockStateExt;
17use steel_utils::Identifier;
18use steel_utils::translations::{COMMANDS_SETBLOCK_FAILED, COMMANDS_SETBLOCK_SUCCESS};
19use steel_utils::types::UpdateFlags;
20use text_components::TextComponent;
21
22enum SetBlockMode {
24 Destroy,
26 Keep,
28 Replace,
30 Strict,
32}
33
34pub(super) fn registration() -> CommandRegistration<CommandSource> {
35 CommandRegistration::new(Identifier::vanilla_static("setblock"), |_| command())
36}
37
38fn command() -> CommandNodeBuilder<CommandSource, SteelCommandRuntime> {
39 literal("setblock").then(
40 argument("pos", SteelArgumentType::block_pos()).then(
41 argument("block", SteelArgumentType::block_state())
42 .executes(|c| set_block(c, SetBlockMode::Replace))
43 .then(literal("destroy").executes(|c| set_block(c, SetBlockMode::Destroy)))
44 .then(literal("keep").executes(|c| set_block(c, SetBlockMode::Keep)))
45 .then(literal("replace").executes(|c| set_block(c, SetBlockMode::Replace)))
46 .then(literal("strict").executes(|c| set_block(c, SetBlockMode::Strict))),
47 ),
48 )
49}
50
51fn set_block(
53 context: &SteelCommandContext<CommandSource>,
54 mode: SetBlockMode,
55) -> Result<i32, CommandSyntaxError> {
56 let Some(coordinates) = context.coordinates("pos") else {
58 return Err(missing_argument("pos"));
59 };
60 let block_pos = coordinates.block_pos(context.source());
61
62 let Some(block_predicate) = context.block_predicate("block") else {
64 return Err(missing_argument("block"));
65 };
66
67 let (block_state, nbt) = match block_predicate {
68 BlockPredicate::Block {
69 block,
70 properties,
71 nbt,
72 } => {
73 let properties_vec: Vec<(&str, &str)> = properties
75 .iter()
76 .map(|(name, value)| (name.as_ref(), value.as_ref()))
77 .collect();
78
79 let Some(block_state_id) = REGISTRY
81 .blocks
82 .state_id_from_block_properties(block, &properties_vec)
83 else {
84 return Err(CommandSyntaxError::dynamic(
85 "This Block is not registered or a property name/value is invalid.",
86 ));
87 };
88
89 (block_state_id, nbt)
90 }
91 BlockPredicate::Tag { .. } => unreachable!(),
92 };
93
94 let level = context.source().world();
96
97 if matches!(mode, SetBlockMode::Keep) && level.get_block_state(block_pos).is_air() {
99 return Ok(set_block_failed(context.source()));
100 }
101
102 let place_needed = if matches!(mode, SetBlockMode::Destroy) {
103 level.destroy_block(block_pos, true);
104
105 !block_state.is_air() || level.get_block_state(block_pos).is_air()
106 } else {
107 true
108 };
109
110 let update_bits = if matches!(mode, SetBlockMode::Strict) {
111 816
112 } else {
113 256
114 };
115
116 let old_state = level.get_block_state(block_pos);
118
119 if place_needed
121 && !level.set_block(
122 block_pos,
123 block_state,
124 UpdateFlags::from_bits_truncate(2 | update_bits),
125 )
126 {
127 return Ok(set_block_failed(context.source()));
128 }
129
130 if let Some(block_entity) = level.get_block_entity(block_pos)
132 && let Some(nbt) = nbt
133 {
134 let mut bytes = Vec::new();
135 nbt.write(&mut bytes);
136 let borrowed = read_compound(&mut Cursor::new(bytes.as_slice())).map_err(|_| {
137 CommandSyntaxError::dynamic("Failed to transpose owned compound into borrowed ")
138 })?;
139 block_entity.load_additional(&borrowed);
140 block_entity.set_changed();
141 }
142
143 if !matches!(mode, SetBlockMode::Strict) {
144 level.update_neighbour_on_block_set(block_pos, old_state);
145 if block_state.has_fluid() {
146 level.schedule_fluid_tick(
147 block_pos,
148 block_state.get_fluid_state().fluid_id,
149 0,
150 TickPriority::ExtremelyHigh,
151 );
152 }
153 }
154
155 context.source().send_success(
156 &COMMANDS_SETBLOCK_SUCCESS
157 .message([
158 TextComponent::plain(format!("{}", block_pos.x())),
159 TextComponent::plain(format!("{}", block_pos.y())),
160 TextComponent::plain(format!("{}", block_pos.z())),
161 ])
162 .component(),
163 true,
164 );
165
166 Ok(1)
167}
168
169fn missing_argument(name: &str) -> CommandSyntaxError {
170 CommandSyntaxError::dynamic(format!(
171 "Parsed value for {name} is missing from the command context"
172 ))
173}
174
175fn set_block_failed(source: &CommandSource) -> i32 {
176 source.send_failure(COMMANDS_SETBLOCK_FAILED.msg().component());
177
178 0
180}