1#[cfg(not(feature = "worldgen"))]
2use crate::test_world::World;
3#[cfg(feature = "worldgen")]
4use world::{IndexOwned, World};
5
6use crate::{
7 EditableSettings,
8 automod::AutoMod,
9 character_creator,
10 client::Client,
11 persistence::{character_loader::CharacterLoader, character_updater::CharacterUpdater},
12};
13#[cfg(feature = "worldgen")]
14use common::terrain::TerrainChunkSize;
15use common::{
16 comp::{Admin, AdminRole, ChatType, Content, Player, Presence, Waypoint},
17 event::{
18 ChatEvent, ClientDisconnectEvent, DeleteCharacterEvent, EmitExt, InitializeCharacterEvent,
19 InitializeSpectatorEvent,
20 },
21 event_emitters,
22 resources::Time,
23 uid::Uid,
24};
25use common_ecs::{Job, Origin, Phase, System};
26use common_net::msg::{ClientGeneral, ServerGeneral};
27use specs::{
28 Entities, Join, ReadExpect, ReadStorage, SystemData, WriteExpect, WriteStorage, shred,
29};
30use std::sync::{Arc, atomic::Ordering};
31use tracing::debug;
32
33event_emitters! {
34 struct Events[Emitters] {
35 init_spectator: InitializeSpectatorEvent,
36 init_character_data: InitializeCharacterEvent,
37 delete_character: DeleteCharacterEvent,
38 client_disconnect: ClientDisconnectEvent,
39 chat: ChatEvent,
40 }
41}
42
43impl Sys {
44 #[cfg_attr(feature = "worldgen", expect(clippy::too_many_arguments))] fn handle_client_character_screen_msg(
46 emitters: &mut Emitters,
47 entity: specs::Entity,
48 client: &Client,
49 character_loader: &ReadExpect<'_, CharacterLoader>,
50 character_updater: &mut WriteExpect<'_, CharacterUpdater>,
51 uids: &ReadStorage<'_, Uid>,
52 players: &ReadStorage<'_, Player>,
53 admins: &ReadStorage<'_, Admin>,
54 presences: &ReadStorage<'_, Presence>,
55 editable_settings: &ReadExpect<'_, EditableSettings>,
56 censor: &ReadExpect<'_, Arc<censor::Censor>>,
57 automod: &AutoMod,
58 msg: ClientGeneral,
59 time: Time,
60 #[cfg(feature = "worldgen")] index: &ReadExpect<'_, IndexOwned>,
61 world: &ReadExpect<'_, Arc<World>>,
62 ) -> Result<(), crate::error::Error> {
63 let mut send_join_messages = || -> Result<(), crate::error::Error> {
64 let localized_description = editable_settings
66 .server_description
67 .get(client.locale.as_deref());
68 if !localized_description.is_none_or(|d| d.motd.is_empty()) {
69 client.send(ServerGeneral::server_msg(
70 ChatType::CommandInfo,
71 localized_description.map_or(Content::Plain("".to_string()), |d| {
72 Content::Plain(d.motd.to_owned())
73 }),
74 ))?;
75 }
76
77 if automod.enabled() {
79 client.send(ServerGeneral::server_msg(
80 ChatType::CommandInfo,
81 Content::Plain(
82 "Automatic moderation is enabled: play nice and have fun!".to_string(),
83 ),
84 ))?;
85 }
86
87 if client.client_type.emit_login_events()
88 && !client.login_msg_sent.load(Ordering::Relaxed)
89 {
90 if let Some(player_uid) = uids.get(entity) {
91 emitters.emit(ChatEvent(ChatType::Online(*player_uid).into_plain_msg("")));
92
93 client.login_msg_sent.store(true, Ordering::Relaxed);
94 }
95 }
96 Ok(())
97 };
98 match msg {
99 ClientGeneral::Spectate(requested_view_distances) => {
101 if let Some(admin) = admins.get(entity)
102 && admin.0 >= AdminRole::Moderator
103 {
104 send_join_messages()?;
105
106 emitters.emit(InitializeSpectatorEvent(entity, requested_view_distances));
107 } else {
108 debug!("dropped Spectate msg from unprivileged client")
109 }
110 },
111 ClientGeneral::Character(character_id, requested_view_distances) => {
112 if let Some(player) = players.get(entity) {
113 if presences.contains(entity) {
120 debug!("player already ingame, aborting");
121 } else if character_updater.has_pending_database_action(character_id) {
122 debug!("player recently logged out pending persistence, aborting");
123 client.send(ServerGeneral::CharacterDataLoadResult(Err(
124 "You have recently logged out, please wait a few seconds and try again"
125 .to_string(),
126 )))?;
127 } else if character_updater.disconnect_all_clients_requested() {
128 debug!(
132 "Rejecting player login while pending disconnection of all players is \
133 in progress"
134 );
135 client.send(ServerGeneral::CharacterDataLoadResult(Err(
136 "The server is currently recovering from an error, please wait a few \
137 seconds and try again"
138 .to_string(),
139 )))?;
140 } else {
141 character_loader.load_character_data(
145 entity,
146 player.uuid().to_string(),
147 character_id,
148 );
149
150 send_join_messages()?;
151
152 emitters.emit(InitializeCharacterEvent {
155 entity,
156 character_id,
157 requested_view_distances,
158 });
159 }
160 } else {
161 debug!("Client is not yet registered");
162 client.send(ServerGeneral::CharacterDataLoadResult(Err(String::from(
163 "Failed to fetch player entity",
164 ))))?
165 }
166 },
167 ClientGeneral::RequestCharacterList => {
168 if let Some(player) = players.get(entity) {
169 character_loader.load_character_list(entity, player.uuid().to_string())
170 }
171 },
172 ClientGeneral::CreateCharacter {
173 alias,
174 mainhand,
175 offhand,
176 body,
177 hardcore,
178 #[cfg(feature = "worldgen")]
179 start_site,
180 #[cfg(not(feature = "worldgen"))]
181 start_site: _,
182 } => {
183 if censor.check(&alias) {
184 debug!(?alias, "denied alias as it contained a banned word");
185 client.send(ServerGeneral::CharacterActionError(format!(
186 "Alias '{}' contains a banned word",
187 alias
188 )))?;
189 } else if let Some(player) = players.get(entity) {
190 #[cfg(feature = "worldgen")]
191 let waypoint = start_site.and_then(|site_idx| {
192 world
198 .civs()
199 .sites
200 .iter()
201 .find(|(_, site)| site.site_tmp.map(|i| i.id()) == Some(site_idx))
202 .map(Some)
203 .unwrap_or_else(|| {
204 tracing::error!(
205 "Tried to create character with starting site index {}, but \
206 such a site does not exist",
207 site_idx
208 );
209 None
210 })
211 .map(|(_, site)| {
212 let wpos2d = TerrainChunkSize::center_wpos(site.center);
213 Waypoint::new(
214 world.find_accessible_pos(index.as_index_ref(), wpos2d, false),
215 time,
216 )
217 })
218 });
219 #[cfg(not(feature = "worldgen"))]
220 let waypoint = Some(Waypoint::new(world.get_center().with_z(10).as_(), time));
221 if let Err(error) = character_creator::create_character(
222 entity,
223 player.uuid().to_string(),
224 alias,
225 mainhand.clone(),
226 offhand.clone(),
227 body,
228 hardcore,
229 character_updater,
230 waypoint,
231 ) {
232 debug!(
233 ?error,
234 ?mainhand,
235 ?offhand,
236 ?body,
237 "Denied creating character because of invalid input."
238 );
239 client.send(ServerGeneral::CharacterActionError(error.to_string()))?;
240 }
241 }
242 },
243 ClientGeneral::EditCharacter { id, alias, body } => {
244 if censor.check(&alias) {
245 debug!(?alias, "denied alias as it contained a banned word");
246 client.send(ServerGeneral::CharacterActionError(format!(
247 "Alias '{}' contains a banned word",
248 alias
249 )))?;
250 } else if let Some(player) = players.get(entity) {
251 if let Err(error) = character_creator::edit_character(
252 entity,
253 player.uuid().to_string(),
254 id,
255 alias,
256 body,
257 character_updater,
258 ) {
259 debug!(
260 ?error,
261 ?body,
262 "Denied editing character because of invalid input."
263 );
264 client.send(ServerGeneral::CharacterActionError(error.to_string()))?;
265 }
266 }
267 },
268 ClientGeneral::DeleteCharacter(character_id) => {
269 if let Some(player) = players.get(entity) {
270 emitters.emit(DeleteCharacterEvent {
271 entity,
272 requesting_player_uuid: player.uuid().to_string(),
273 character_id,
274 });
275 }
276 },
277 _ => {
278 debug!("Kicking possibly misbehaving client due to invalid character request");
279 emitters.emit(ClientDisconnectEvent(
280 entity,
281 common::comp::DisconnectReason::NetworkError,
282 ));
283 },
284 }
285 Ok(())
286 }
287}
288
289#[derive(SystemData)]
290pub struct Data<'a> {
291 entities: Entities<'a>,
292 events: Events<'a>,
293 character_loader: ReadExpect<'a, CharacterLoader>,
294 character_updater: WriteExpect<'a, CharacterUpdater>,
295 uids: ReadStorage<'a, Uid>,
296 clients: WriteStorage<'a, Client>,
297 players: ReadStorage<'a, Player>,
298 admins: ReadStorage<'a, Admin>,
299 presences: ReadStorage<'a, Presence>,
300 editable_settings: ReadExpect<'a, EditableSettings>,
301 censor: ReadExpect<'a, Arc<censor::Censor>>,
302 automod: ReadExpect<'a, AutoMod>,
303 time: ReadExpect<'a, Time>,
304 #[cfg(feature = "worldgen")]
305 index: ReadExpect<'a, IndexOwned>,
306 world: ReadExpect<'a, Arc<World>>,
307}
308
309#[derive(Default)]
311pub struct Sys;
312impl<'a> System<'a> for Sys {
313 type SystemData = Data<'a>;
314
315 const NAME: &'static str = "msg::character_screen";
316 const ORIGIN: Origin = Origin::Server;
317 const PHASE: Phase = Phase::Create;
318
319 fn run(_job: &mut Job<Self>, mut data: Self::SystemData) {
320 let mut emitters = data.events.get_emitters();
321
322 for (entity, client) in (&data.entities, &mut data.clients).join() {
323 let _ = super::try_recv_all(client, 1, |client, msg| {
324 Self::handle_client_character_screen_msg(
325 &mut emitters,
326 entity,
327 client,
328 &data.character_loader,
329 &mut data.character_updater,
330 &data.uids,
331 &data.players,
332 &data.admins,
333 &data.presences,
334 &data.editable_settings,
335 &data.censor,
336 &data.automod,
337 msg,
338 *data.time,
339 #[cfg(feature = "worldgen")]
340 &data.index,
341 &data.world,
342 )
343 });
344 }
345 }
346}