Skip to main content

steel_core/command/builtins/
weather.rs

1//! Per-world weather command.
2
3use steel_utils::{Identifier, translations};
4use text_components::TextComponent;
5
6use super::super::{
7    brigadier::{CommandNodeBuilder, CommandSyntaxError},
8    execution::{
9        CommandSource, SteelArgumentType, SteelCommandContext, SteelCommandRuntime, argument,
10        literal,
11    },
12    registration::CommandRegistration,
13};
14
15const DEFAULT_DURATION: i32 = -1;
16
17pub(super) fn registration() -> CommandRegistration<CommandSource> {
18    CommandRegistration::new(Identifier::vanilla_static("weather"), |_| command())
19}
20
21fn command() -> CommandNodeBuilder<CommandSource, SteelCommandRuntime> {
22    literal("weather")
23        .then(weather_literal("clear", WeatherKind::Clear))
24        .then(weather_literal("rain", WeatherKind::Rain))
25        .then(weather_literal("thunder", WeatherKind::Thunder))
26}
27
28fn weather_literal(
29    name: &'static str,
30    weather: WeatherKind,
31) -> CommandNodeBuilder<CommandSource, SteelCommandRuntime> {
32    literal(name)
33        .executes(move |context| set_weather(context, weather, DEFAULT_DURATION))
34        .then(
35            argument("duration", SteelArgumentType::time(1)).executes(move |context| {
36                let duration = context.time("duration")?;
37                set_weather(context, weather, duration)
38            }),
39        )
40}
41
42#[derive(Clone, Copy)]
43enum WeatherKind {
44    Clear,
45    Rain,
46    Thunder,
47}
48
49impl WeatherKind {
50    fn random_duration(self) -> i32 {
51        match self {
52            Self::Clear => rand::random_range(12_000..=180_000),
53            Self::Rain => rand::random_range(12_000..=24_000),
54            Self::Thunder => rand::random_range(3_600..=15_600),
55        }
56    }
57
58    const fn parameters(self, duration: i32) -> (i32, i32, bool, bool) {
59        match self {
60            Self::Clear => (duration, 0, false, false),
61            Self::Rain => (0, duration, true, false),
62            Self::Thunder => (0, duration, true, true),
63        }
64    }
65
66    fn success_message(self) -> TextComponent {
67        match self {
68            Self::Clear => TextComponent::from(&translations::COMMANDS_WEATHER_SET_CLEAR),
69            Self::Rain => TextComponent::from(&translations::COMMANDS_WEATHER_SET_RAIN),
70            Self::Thunder => TextComponent::from(&translations::COMMANDS_WEATHER_SET_THUNDER),
71        }
72    }
73}
74
75#[expect(
76    clippy::unnecessary_wraps,
77    reason = "Command executors use a shared fallible callback signature."
78)]
79fn set_weather(
80    context: &SteelCommandContext<CommandSource>,
81    weather: WeatherKind,
82    requested_duration: i32,
83) -> Result<i32, CommandSyntaxError> {
84    let duration = if requested_duration == DEFAULT_DURATION {
85        weather.random_duration()
86    } else {
87        requested_duration
88    };
89    let (clear_time, rain_time, raining, thundering) = weather.parameters(duration);
90    context
91        .source()
92        .world()
93        .set_weather_parameters(clear_time, rain_time, raining, thundering);
94    context
95        .source()
96        .send_success(&weather.success_message(), true);
97    Ok(requested_duration)
98}
99
100#[cfg(test)]
101mod tests {
102    use super::WeatherKind;
103
104    #[test]
105    fn weather_kinds_map_to_vanilla_parameter_sets() {
106        assert_eq!(WeatherKind::Clear.parameters(40), (40, 0, false, false));
107        assert_eq!(WeatherKind::Rain.parameters(40), (0, 40, true, false));
108        assert_eq!(WeatherKind::Thunder.parameters(40), (0, 40, true, true));
109    }
110}