Skip to main content

steel_core/command/builtins/
setblock.rs

1//! Vanilla set block command.
2
3use super::super::{
4    brigadier::{CommandNodeBuilder, CommandSyntaxError},
5    execution::{
6        CommandSource, SteelArgumentType, SteelCommandContext, SteelCommandRuntime, argument,
7        literal, placement_flags,
8    },
9    registration::CommandRegistration,
10};
11use super::execute::loaded_block_position;
12use steel_registry::blocks::block_state_ext::BlockStateExt;
13use steel_utils::Identifier;
14use steel_utils::translations::{COMMANDS_SETBLOCK_FAILED, COMMANDS_SETBLOCK_SUCCESS};
15use text_components::TextComponent;
16
17/// How the block should be placed
18enum SetBlockMode {
19    /// Destroy the previous block and drop the loot according to the loot table
20    Destroy,
21    /// Can only place a block if the previous block was air
22    Keep,
23    /// Base case
24    Replace,
25    /// Replace the block without updating the world
26    Strict,
27}
28
29pub(super) fn registration() -> CommandRegistration<CommandSource> {
30    CommandRegistration::new(Identifier::vanilla_static("setblock"), |_| command())
31}
32
33fn command() -> CommandNodeBuilder<CommandSource, SteelCommandRuntime> {
34    literal("setblock").then(
35        argument("pos", SteelArgumentType::block_pos()).then(
36            argument("block", SteelArgumentType::block_state())
37                .executes(|c| set_block(c, SetBlockMode::Replace))
38                .then(literal("destroy").executes(|c| set_block(c, SetBlockMode::Destroy)))
39                .then(literal("keep").executes(|c| set_block(c, SetBlockMode::Keep)))
40                .then(literal("replace").executes(|c| set_block(c, SetBlockMode::Replace)))
41                .then(literal("strict").executes(|c| set_block(c, SetBlockMode::Strict))),
42        ),
43    )
44}
45
46/// Set a block in the desired position with a mode (destroy, keep, replace, strict), and return 1 if the block is placed, 0 else.
47fn set_block(
48    context: &SteelCommandContext<CommandSource>,
49    mode: SetBlockMode,
50) -> Result<i32, CommandSyntaxError> {
51    let block_pos = loaded_block_position(context, "pos")?;
52    let block = context.block_input("block")?;
53    let level = context.source().world();
54
55    if matches!(mode, SetBlockMode::Keep) && !level.get_block_state(block_pos).is_air() {
56        return Ok(set_block_failed(context.source()));
57    }
58
59    let place_needed = if matches!(mode, SetBlockMode::Destroy) {
60        level.destroy_block(block_pos, true);
61
62        !block.state().is_air() || !level.get_block_state(block_pos).is_air()
63    } else {
64        true
65    };
66
67    let strict = matches!(mode, SetBlockMode::Strict);
68    let old_state = level.get_block_state(block_pos);
69
70    if place_needed && !block.place(level, block_pos, placement_flags(strict))? {
71        return Ok(set_block_failed(context.source()));
72    }
73
74    if !strict {
75        level.update_neighbors_on_block_set(block_pos, old_state);
76    }
77
78    context.source().send_success(
79        &COMMANDS_SETBLOCK_SUCCESS
80            .message([
81                TextComponent::plain(format!("{}", block_pos.x())),
82                TextComponent::plain(format!("{}", block_pos.y())),
83                TextComponent::plain(format!("{}", block_pos.z())),
84            ])
85            .component(),
86        true,
87    );
88
89    Ok(1)
90}
91
92fn set_block_failed(source: &CommandSource) -> i32 {
93    source.send_failure(COMMANDS_SETBLOCK_FAILED.msg().component());
94
95    0
96}