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