1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
//! Weather in the server's world: how it is at a place, the fronts that
//! make it, and starting new weather.
//!
//! The game's weather comes in fronts: a few weather layers, each with a
//! current phenomenon at a place with a radius (rain, a blizzard, or clouds
//! without either) that lasts its time, after which the game picks the
//! next. Rain falls as snow where it is cold. [`Weather::at`] weighs a
//! place as the game's own systems do; [`Weather::set`] starts new fronts
//! the way the game's state configurations do, and every client follows.
//! How often rain and storms come is the server's `weatherFrequency` game
//! setting.
//!
//! Every call belongs on the server's game thread, from a game event, a
//! [`Scheduler`](crate::schedule::Scheduler) task or an action; on a client,
//! while it hosts a game.
//!
//! # Examples
//!
//! ```
//! use core::time::Duration;
//! use enshwick::weather::{Forecast, Weather};
//!
//! fn sun(weather: &Weather) -> Result<(), enshwick::WorldError> {
//! weather.set(Forecast::Clear, Some(Duration::from_mins(10)))
//! }
//!
//! fn init(load: &mut enshwick::Load<'_>) -> Result<Weather, enshwick::Off> {
//! Ok(load.weather())
//! }
//! ```
use core::time::Duration;
use crate::WorldError;
use crate::abi::{Header, Status, WeatherFront};
use crate::{Host, Load};
/// What the weather is at a place, as the game names it.
///
/// # Examples
///
/// See [`WeatherAt`].
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum WeatherState {
/// Clear.
Clear,
/// Rain.
Rain,
/// Snow.
Snow,
/// A blizzard.
Blizzard,
}
/// How the weather is at a place, from [`Weather::at`]: weights that add
/// up to 1, as the game's systems weigh it.
///
/// The name is enshwick's, not the game's.
///
/// # Examples
///
/// ```
/// use enshwick::weather::WeatherState;
///
/// fn wet(weather: &enshwick::weather::Weather, at: [f64; 3]) -> bool {
/// weather.at(at).is_some_and(|now| now.state() == WeatherState::Rain)
/// }
/// ```
#[derive(Clone, Copy, Debug, PartialEq)]
#[non_exhaustive]
pub struct WeatherAt {
/// How clear it is.
pub clear: f32,
/// How rainy.
pub rain: f32,
/// How snowy.
pub snow: f32,
/// How stormy.
pub blizzard: f32,
}
impl WeatherAt {
/// The strongest of the four.
///
/// # Examples
///
/// See [`WeatherAt`].
#[must_use]
pub fn state(&self) -> WeatherState {
[
(self.clear, WeatherState::Clear),
(self.rain, WeatherState::Rain),
(self.snow, WeatherState::Snow),
(self.blizzard, WeatherState::Blizzard),
]
.into_iter()
.fold((f32::MIN, WeatherState::Clear), |best, next| {
if next.0 > best.0 { next } else { best }
})
.1
}
}
/// What a front brings.
///
/// # Examples
///
/// See [`Front`].
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum WeatherPhenomenonEffect {
/// Clouds without rain.
None,
/// Rain, snow where it is cold.
Rain,
/// A blizzard.
Blizzard,
/// A kind this API does not name.
Other(u32),
}
/// A front of weather, from [`Weather::fronts`].
///
/// # Examples
///
/// ```
/// use enshwick::weather::WeatherPhenomenonEffect;
///
/// fn storms(weather: &enshwick::weather::Weather) -> usize {
/// weather.fronts().iter().filter(|front| front.kind == WeatherPhenomenonEffect::Blizzard).count()
/// }
/// ```
#[derive(Clone, Copy, Debug, PartialEq)]
#[non_exhaustive]
pub struct Front {
/// The weather layer it belongs to.
pub layer: u32,
/// What it brings.
pub kind: WeatherPhenomenonEffect,
/// Where it is, in metres.
pub position: [f64; 3],
/// How far it reaches, in metres.
pub radius: f32,
/// How long it has left, in game time.
pub remaining: Duration,
}
/// The weather to start, for [`Weather::set`].
///
/// The name is enshwick's, not the game's.
///
/// # Examples
///
/// See the [module](self).
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Forecast {
/// Clear skies everywhere.
Clear,
/// Rain fronts, snow where it is cold.
Rain,
/// Blizzards where the game has them.
Blizzard,
/// What the game picks itself, at once.
Any,
}
/// The weather of the server's world; get it from [`Load::weather`].
///
/// # Examples
///
/// See the [module](self).
#[derive(Clone, Copy, Debug)]
pub struct Weather {
host: Host,
}
impl Weather {
/// How the weather is at `position`, in metres; `None` off the game
/// thread or while no world runs.
///
/// # Examples
///
/// See [`WeatherAt`].
#[must_use]
pub fn at(&self, position: [f64; 3]) -> Option<WeatherAt> {
let fixed = crate::ecs::fixed(position);
let mut out = [0.0f32; 4];
let status =
(self.host.api().weather_at)(self.host.handle(), &raw const fixed, &raw mut out);
(status == Status::OK).then_some(WeatherAt {
clear: out[0],
rain: out[1],
snow: out[2],
blizzard: out[3],
})
}
/// The fronts now, one per weather layer that has one; empty off the
/// game thread or while no world runs.
///
/// # Examples
///
/// See [`Front`].
#[must_use]
pub fn fronts(&self) -> Vec<Front> {
let blank = WeatherFront {
header: Header::of::<WeatherFront>(),
layer: 0,
effect: 0,
position: [0; 3],
radius: 0.0,
reserved: 0,
remaining: 0,
};
let mut fronts = vec![blank; 16];
loop {
let mut count = 0u32;
let capacity = u32::try_from(fronts.len()).unwrap_or(u32::MAX);
let status = (self.host.api().weather_fronts)(
self.host.handle(),
fronts.as_mut_ptr(),
capacity,
&raw mut count,
);
if status != Status::OK {
return Vec::new();
}
let count = count as usize;
if count <= fronts.len() {
fronts.truncate(count);
break;
}
fronts = vec![blank; count];
}
fronts
.iter()
.map(|front| Front {
layer: front.layer,
kind: match front.effect {
WeatherFront::NONE => WeatherPhenomenonEffect::None,
WeatherFront::RAIN => WeatherPhenomenonEffect::Rain,
WeatherFront::BLIZZARD => WeatherPhenomenonEffect::Blizzard,
other => WeatherPhenomenonEffect::Other(other),
},
position: crate::ecs::metres(front.position),
radius: front.radius,
remaining: Duration::from_nanos(u64::try_from(front.remaining).unwrap_or(0)),
})
.collect()
}
/// Starts new weather in every layer at once: fronts of the forecast
/// where a layer has them and clear skies elsewhere, for `lasting` of
/// game time or the game's own for `None`; [`Forecast::Any`] lets the
/// game pick anew. The game places the fronts itself, as for its own.
/// Clients follow, and the next save keeps it. Moving the clock forward
/// with [`Clock::set_hours`](crate::daytime::Clock::set_hours) uses up
/// the time the fronts have, and the game picks new ones.
///
/// # Errors
///
/// [`WorldError::WrongThread`] off the game thread and
/// [`WorldError::Missing`] while no world runs or on a build without
/// weather.
///
/// # Examples
///
/// See the [module](self).
pub fn set(&self, forecast: Forecast, lasting: Option<Duration>) -> Result<(), WorldError> {
let effect = match forecast {
Forecast::Clear => WeatherFront::NONE,
Forecast::Rain => WeatherFront::RAIN,
Forecast::Blizzard => WeatherFront::BLIZZARD,
Forecast::Any => u32::MAX,
};
let duration = lasting.map_or(0, |d| {
i64::try_from(d.as_nanos()).unwrap_or(i64::MAX).max(1)
});
WorldError::from_status((self.host.api().set_weather)(
self.host.handle(),
effect,
duration,
))
}
}
impl Load<'_> {
/// The [`Weather`] of the server's world; on a client, the built-in
/// server's while it hosts a game.
///
/// # Examples
///
/// See the [module](self).
#[must_use]
pub fn weather(&mut self) -> Weather {
Weather { host: self.host() }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_strongest_weight_names_the_weather() {
let now = WeatherAt {
clear: 0.2,
rain: 0.1,
snow: 0.6,
blizzard: 0.1,
};
assert_eq!(now.state(), WeatherState::Snow);
}
}