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
use crate::Entity;
use super::{Meter, Progress, Skill, Vitals, World, WorldError, stats};
impl World {
/// `entity`'s health, stamina and mana; `None` when it has no health,
/// off the game thread, or on a build without stat reads.
///
/// # Examples
///
/// ```
/// use enshwick::events::PlayerJoined;
///
/// fn init(load: &mut enshwick::Load<'_>) -> Result<(), enshwick::Off> {
/// let world = load.world()?;
/// load.on::<PlayerJoined>(move |joined| {
/// if let Some(vitals) = world.vitals(joined.entity) {
/// enshwick::info!("joined with {} of {} health", vitals.health.current, vitals.health.max);
/// }
/// })
/// }
/// ```
#[must_use]
pub fn vitals(self, entity: Entity) -> Option<Vitals> {
self.0.stats?.vitals(self, entity)
}
/// Sets `entity`'s health to `value`, kept between 1 and its maximum, so
/// a write never kills; returns the health it now has. The game sends it
/// to clients and regenerates from it as from any other value. On the
/// game thread only.
///
/// # Errors
///
/// [`WorldError::Refused`] for an entity already dead: death and revival
/// stay the game's; otherwise as for [`World::update`].
///
/// # Examples
///
/// ```
/// use enshwick::actions::{Action, Outcome};
/// fn init(load: &mut enshwick::Load<'_>) -> Result<(), enshwick::Off> {
/// let world = load.world()?;
/// let actions = load.actions(enshwick::i18n::Texts::default())?;
/// actions.add(Action::new("full", "Restore your health"), move |request| {
/// let me = request.player.entity;
/// let max = world.vitals(me).map_or(0, |v| v.health.max);
/// match world.set_health(me, max) {
/// Ok(health) => Outcome::done(format!("{} of {} health", health.current, health.max)),
/// Err(why) => Outcome::refused(format!("{why}")),
/// }
/// })
/// }
/// ```
pub fn set_health(self, entity: Entity, value: i32) -> Result<Meter, WorldError> {
self.write_stat(entity, stats::Pool::Health, |_| value)
}
/// Adds `amount` to `entity`'s health, kept between 1 and its maximum: a
/// negative amount takes health but never kills. Returns the health it
/// now has. On the game thread only.
///
/// # Errors
///
/// As for [`World::set_health`].
///
/// # Examples
///
/// ```
/// use enshwick::game_events::LevelUpEvent;
///
/// fn init(load: &mut enshwick::Load<'_>) -> Result<(), enshwick::Off> {
/// let world = load.world()?;
/// load.on::<LevelUpEvent>(move |gained| {
/// let _healed = world.heal(gained.player_entity_id, 50);
/// })
/// }
/// ```
pub fn heal(self, entity: Entity, amount: i32) -> Result<Meter, WorldError> {
self.write_stat(entity, stats::Pool::Health, |current| {
current.saturating_add(amount)
})
}
/// Sets `entity`'s stamina to `value`, kept between 0 and its maximum;
/// returns the stamina it now has. On the game thread only.
///
/// # Errors
///
/// [`WorldError::Lacks`] for an entity without stamina; otherwise as for
/// [`World::update`].
///
/// # Examples
///
/// ```
/// fn refresh(world: &enshwick::world::World, player: &enshwick::world::Player) {
/// let max = world.vitals(player.entity).and_then(|v| v.stamina).map_or(0, |s| s.max);
/// let _refreshed = world.set_stamina(player.entity, max);
/// }
/// ```
pub fn set_stamina(self, entity: Entity, value: i32) -> Result<Meter, WorldError> {
self.write_stat(entity, stats::Pool::Stamina, |_| value)
}
/// Sets `entity`'s mana to `value`, kept between 0 and its maximum;
/// returns the mana it now has. On the game thread only.
///
/// # Errors
///
/// As for [`World::set_stamina`], for mana.
///
/// # Examples
///
/// ```
/// fn drain(world: &enshwick::world::World, player: &enshwick::world::Player) {
/// let _drained = world.set_mana(player.entity, 0);
/// }
/// ```
pub fn set_mana(self, entity: Entity, value: i32) -> Result<Meter, WorldError> {
self.write_stat(entity, stats::Pool::Mana, |_| value)
}
/// The meter of an item's own entity, its PIDE (see
/// [`FuelSetup`](crate::content::items::FuelSetup)): the count it holds and
/// its maximum. `None` for an entity without one, off the game thread,
/// or on a build without the reads.
///
/// # Examples
///
/// ```
/// fn charges(world: &enshwick::world::World, slot: &enshwick::inventory::Slot) -> i32 {
/// slot.pide.and_then(|pide| world.item_meter(pide)).map_or(0, |m| m.current)
/// }
/// ```
#[must_use]
pub fn item_meter(self, pide: Entity) -> Option<Meter> {
self.0.stats?.meter(self, pide)
}
/// Sets the meter of an item's own entity to what `change` makes of it,
/// kept between 0 and its maximum, and returns it. The game saves it
/// with the world and shows it on the item's slot. On the game thread
/// only.
///
/// # Errors
///
/// [`WorldError::Lacks`] for an entity without a meter;
/// [`WorldError::Missing`] on a build without the writes; otherwise as
/// for [`World::update`].
///
/// # Examples
///
/// ```
/// fn spend(world: &enshwick::world::World, pide: enshwick::Entity) -> bool {
/// world.change_item_meter(pide, |charges| charges - 1).is_ok()
/// }
/// ```
pub fn change_item_meter(
self,
pide: Entity,
change: impl FnOnce(i32) -> i32,
) -> Result<Meter, WorldError> {
self.write_stat(pide, stats::Pool::Meter, change)
}
/// How long `entity`, a character, holds out in the shroud before it
/// drains health, in seconds, and the most it holds; `None` for an
/// entity without it, off the game thread, or on a build without it.
/// It runs down in the shroud and refills outside.
///
/// # Examples
///
/// ```
/// fn left(world: &enshwick::world::World, player: &enshwick::world::Player) -> i32 {
/// world.shroud_time(player.entity).map_or(0, |time| time.current)
/// }
/// ```
#[must_use]
pub fn shroud_time(self, entity: Entity) -> Option<Meter> {
self.0.stats?.shroud(self, entity)
}
/// Sets how long `entity` holds out in the shroud to what `change` makes
/// of it, in seconds, kept between 0 and its most, and returns it;
/// clients see it from the next step. Not saved: the game refills it
/// outside the shroud. On the game thread only.
///
/// # Errors
///
/// [`WorldError::Lacks`] for an entity without it;
/// [`WorldError::Missing`] on a build without it; otherwise as for
/// [`World::update`].
///
/// # Examples
///
/// ```
/// fn refill(world: &enshwick::world::World, player: &enshwick::world::Player) {
/// let _full = world.change_shroud_time(player.entity, |_| i32::MAX);
/// }
/// ```
pub fn change_shroud_time(
self,
entity: Entity,
change: impl FnOnce(i32) -> i32,
) -> Result<Meter, WorldError> {
self.write_stat(entity, stats::Pool::Shroud, change)
}
/// Whether `entity`, a character, stands in the shroud where it drains
/// them, outside the altars' reach; `None` for an entity that is no
/// actor, off the game thread, or on a build without it.
///
/// # Examples
///
/// ```
/// fn exposed(world: &enshwick::world::World, player: &enshwick::world::Player) -> bool {
/// world.in_shroud(player.entity) == Some(true)
/// }
/// ```
#[must_use]
pub fn in_shroud(self, entity: Entity) -> Option<bool> {
self.0.stats?.in_shroud(self, entity)
}
/// The skill nodes `entity`, a character, unlocked, with their levels;
/// `None` for an entity without skills, off the game thread, or on a
/// build without them. The game saves them with the character.
///
/// # Examples
///
/// ```
/// fn skilled(world: &enshwick::world::World, player: &enshwick::world::Player) -> usize {
/// world.skills(player.entity).map_or(0, |skills| skills.len())
/// }
/// ```
#[must_use]
pub fn skills(self, entity: Entity) -> Option<Vec<Skill>> {
self.0.stats?;
stats::Stats::skills(self, entity)
}
pub(super) fn write_stat(
self,
entity: Entity,
pool: stats::Pool,
change: impl FnOnce(i32) -> i32,
) -> Result<Meter, WorldError> {
self.0
.stats
.ok_or(WorldError::Missing)?
.write(self, entity, pool, change)
}
/// `entity`'s level and experience; `None` for an entity without them,
/// off the game thread, or on a build without stat reads.
///
/// # Examples
///
/// ```
/// fn level(world: &enshwick::world::World, player: &enshwick::world::Player) -> u32 {
/// world.progress(player.entity).map_or(0, |progress| progress.level)
/// }
/// ```
#[must_use]
pub fn progress(self, entity: Entity) -> Option<Progress> {
self.0.stats?.progress(self, entity)
}
}