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
use crate::abi::Str;
use crate::{Entity, PlayerKey};
use super::{Notice, Player, World, blank_info, online};
#[crate::script(players)]
impl World {
/// The connected player whose character is `entity`.
///
/// # Examples
///
/// See [`World`].
#[must_use]
#[script]
pub fn player(&self, entity: Entity) -> Option<Player> {
let mut info = blank_info();
let status = (self.0.host.api().player)(self.0.host.handle(), entity, &raw mut info);
status.is_ok().then(|| Player::from_info(&info))
}
/// The connected player with the account `key`, the same in every
/// session.
///
/// # Examples
///
/// ```
/// fn owner_name(world: &enshwick::world::World, owner: enshwick::PlayerKey) -> Option<String> {
/// world.player_by_key(owner).map(|player| player.name)
/// }
/// ```
#[must_use]
#[script]
pub fn player_by_key(self, key: PlayerKey) -> Option<Player> {
self.players().into_iter().find(|player| player.key == key)
}
/// The connected players named `name`, ignoring case; names are
/// untrusted and may repeat, so a command that wants one player refuses
/// more than one.
///
/// # Examples
///
/// ```
/// fn find(world: &enshwick::world::World, name: &str) -> Result<enshwick::world::Player, String> {
/// match <[_; 1]>::try_from(world.players_named(name)) {
/// Ok([player]) => Ok(player),
/// Err(found) if found.is_empty() => Err(format!("no player is named {name}")),
/// Err(_) => Err(format!("several players are named {name}")),
/// }
/// }
/// ```
#[must_use]
#[script]
pub fn players_named(self, name: &str) -> Vec<Player> {
let mut players = self.players();
players.retain(|player| player.is_named(name));
players
}
/// Gives `entity` the name `text`, as a player's rename does: the game
/// shows it on hover, sends it to every client and saves it with the
/// world. The game keeps 255 bytes; an empty text removes the name. On
/// the game thread only; `false` elsewhere or when the entity is gone.
///
/// # Examples
///
/// ```
/// use enshwick::events::BuildingPlaced;
///
/// fn init(load: &mut enshwick::Load<'_>) -> Result<(), enshwick::Off> {
/// let world = load.world()?;
/// load.on::<BuildingPlaced>(move |placed| {
/// if let Some(player) = world.player(placed.player) {
/// let label = format!("Built by {}", player.name);
/// let _labelled = world.set_label(placed.entity, &label);
/// }
/// })
/// }
/// ```
#[must_use]
pub fn set_label(self, entity: Entity, text: &str) -> bool {
(self.0.host.api().set_label)(self.0.host.handle(), entity, Str::new(text)).is_ok()
}
/// Sends `text` to every player's chat, after the next step; callable
/// from any thread. Each line becomes a message of its own.
///
/// # Examples
///
/// ```
/// fn announce(world: &enshwick::world::World) {
/// world.broadcast("The server restarts in five minutes");
/// }
/// ```
#[script]
pub fn broadcast(self, text: &str) {
self.chat(PlayerKey::new(0), text);
}
/// Sends `text` to `player`'s chat alone, after the next step; callable
/// from any thread.
///
/// # Examples
///
/// ```
/// fn welcome(world: &enshwick::world::World, player: &enshwick::world::Player) {
/// world.tell(player, "Welcome back");
/// }
/// ```
#[script]
pub fn tell(self, player: &Player, text: &str) {
if player.key.get() != 0 {
self.chat(player.key, text);
}
}
/// Shows `text` on `player`'s screen, as the game shows its own "can't
/// do that" messages, or tells it in chat when the player's client lacks
/// Enshwick to show it; says which. The game shows 255 bytes; keep it to
/// a line. On the game thread; elsewhere it goes to chat.
///
/// # Examples
///
/// ```
/// use enshwick::world::Notice;
///
/// fn warn_low_fuel(world: &enshwick::world::World, player: &enshwick::world::Player) {
/// if world.notify(player, "The forge runs out of fuel") == Notice::Chat {
/// enshwick::debug!("{} has no Enshwick client", player.name);
/// }
/// }
/// ```
#[expect(
clippy::must_use_candidate,
reason = "shown is the point; how is for the curious"
)]
#[script]
pub fn notify(self, player: &Player, text: &str) -> Notice {
if (self.0.host.api().notify)(self.0.host.handle(), player.key, Str::new(text)).is_ok() {
return Notice::OnScreen;
}
self.tell(player, text);
Notice::Chat
}
/// Sends a notice to every connected player, as [`notify`](World::notify)
/// does to one.
///
/// # Examples
///
/// ```
/// fn storm(world: &enshwick::world::World) {
/// world.notify_all("A storm is coming");
/// }
/// ```
#[script]
pub fn notify_all(self, text: &str) {
for player in self.players() {
self.notify(&player, text);
}
}
pub(super) fn chat(self, to: PlayerKey, text: &str) {
let _queued = (self.0.host.api().chat)(self.0.host.handle(), to, Str::new(text));
}
/// Every connected player with a character in the world.
///
/// # Examples
///
/// ```
/// fn count(world: &enshwick::world::World) -> usize {
/// world.players().len()
/// }
/// ```
#[must_use]
#[script]
pub fn players(self) -> Vec<Player> {
online(self.0.host)
}
}