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 {
92 msg: ChatType::Online(*player_uid).into_plain_msg(""),
93 from_client: false,
94 });
95
96 client.login_msg_sent.store(true, Ordering::Relaxed);
97 }
98 }
99 Ok(())
100 };
101 match msg {
102 ClientGeneral::Spectate(requested_view_distances) => {
104 if let Some(admin) = admins.get(entity)
105 && admin.0 >= AdminRole::Moderator
106 {
107 send_join_messages()?;
108
109 emitters.emit(InitializeSpectatorEvent(entity, requested_view_distances));
110 } else {
111 debug!("dropped Spectate msg from unprivileged client")
112 }
113 },
114 ClientGeneral::Character(character_id, requested_view_distances) => {
115 if let Some(player) = players.get(entity) {
116 if presences.contains(entity) {
123 debug!("player already ingame, aborting");
124 } else if character_updater.has_pending_database_action(character_id) {
125 debug!("player recently logged out pending persistence, aborting");
126 client.send(ServerGeneral::CharacterDataLoadResult(Err(
127 "You have recently logged out, please wait a few seconds and try again"
128 .to_string(),
129 )))?;
130 } else if character_updater.disconnect_all_clients_requested() {
131 debug!(
135 "Rejecting player login while pending disconnection of all players is \
136 in progress"
137 );
138 client.send(ServerGeneral::CharacterDataLoadResult(Err(
139 "The server is currently recovering from an error, please wait a few \
140 seconds and try again"
141 .to_string(),
142 )))?;
143 } else {
144 character_loader.load_character_data(
148 entity,
149 player.uuid().to_string(),
150 character_id,
151 );
152
153 send_join_messages()?;
154
155 emitters.emit(InitializeCharacterEvent {
158 entity,
159 character_id,
160 requested_view_distances,
161 });
162 }
163 } else {
164 debug!("Client is not yet registered");
165 client.send(ServerGeneral::CharacterDataLoadResult(Err(String::from(
166 "Failed to fetch player entity",
167 ))))?
168 }
169 },
170 ClientGeneral::RequestCharacterList => {
171 if let Some(player) = players.get(entity) {
172 character_loader.load_character_list(entity, player.uuid().to_string())
173 }
174 },
175 ClientGeneral::CreateCharacter {
176 alias,
177 mainhand,
178 offhand,
179 body,
180 hardcore,
181 #[cfg(feature = "worldgen")]
182 start_site,
183 #[cfg(not(feature = "worldgen"))]
184 start_site: _,
185 } => {
186 if censor.check(&alias) {
187 debug!(?alias, "denied alias as it contained a banned word");
188 client.send(ServerGeneral::CharacterActionError(format!(
189 "Alias '{}' contains a banned word",
190 alias
191 )))?;
192 } else if let Some(player) = players.get(entity) {
193 #[cfg(feature = "worldgen")]
194 let waypoint = start_site.and_then(|site_idx| {
195 world
201 .civs()
202 .sites
203 .iter()
204 .find(|(_, site)| site.site_tmp.map(|i| i.id()) == Some(site_idx))
205 .map(Some)
206 .unwrap_or_else(|| {
207 tracing::error!(
208 "Tried to create character with starting site index {}, but \
209 such a site does not exist",
210 site_idx
211 );
212 None
213 })
214 .map(|(_, site)| {
215 let wpos2d = TerrainChunkSize::center_wpos(site.center);
216 Waypoint::new(
217 world.find_accessible_pos(index.as_index_ref(), wpos2d, false),
218 time,
219 )
220 })
221 });
222 #[cfg(not(feature = "worldgen"))]
223 let waypoint = Some(Waypoint::new(world.get_center().with_z(10).as_(), time));
224 if let Err(error) = character_creator::create_character(
225 entity,
226 player.uuid().to_string(),
227 alias,
228 mainhand.clone(),
229 offhand.clone(),
230 body,
231 hardcore,
232 character_updater,
233 waypoint,
234 ) {
235 debug!(
236 ?error,
237 ?mainhand,
238 ?offhand,
239 ?body,
240 "Denied creating character because of invalid input."
241 );
242 client.send(ServerGeneral::CharacterActionError(error.to_string()))?;
243 }
244 }
245 },
246 ClientGeneral::EditCharacter { id, alias, body } => {
247 if censor.check(&alias) {
248 debug!(?alias, "denied alias as it contained a banned word");
249 client.send(ServerGeneral::CharacterActionError(format!(
250 "Alias '{}' contains a banned word",
251 alias
252 )))?;
253 } else if let Some(player) = players.get(entity) {
254 if let Err(error) = character_creator::edit_character(
255 entity,
256 player.uuid().to_string(),
257 id,
258 alias,
259 body,
260 character_updater,
261 ) {
262 debug!(
263 ?error,
264 ?body,
265 "Denied editing character because of invalid input."
266 );
267 client.send(ServerGeneral::CharacterActionError(error.to_string()))?;
268 }
269 }
270 },
271 ClientGeneral::DeleteCharacter(character_id) => {
272 if let Some(player) = players.get(entity) {
273 emitters.emit(DeleteCharacterEvent {
274 entity,
275 requesting_player_uuid: player.uuid().to_string(),
276 character_id,
277 });
278 }
279 },
280 _ => {
281 debug!("Kicking possibly misbehaving client due to invalid character request");
282 emitters.emit(ClientDisconnectEvent(
283 entity,
284 common::comp::DisconnectReason::NetworkError,
285 ));
286 },
287 }
288 Ok(())
289 }
290}
291
292#[derive(SystemData)]
293pub struct Data<'a> {
294 entities: Entities<'a>,
295 events: Events<'a>,
296 character_loader: ReadExpect<'a, CharacterLoader>,
297 character_updater: WriteExpect<'a, CharacterUpdater>,
298 uids: ReadStorage<'a, Uid>,
299 clients: WriteStorage<'a, Client>,
300 players: ReadStorage<'a, Player>,
301 admins: ReadStorage<'a, Admin>,
302 presences: ReadStorage<'a, Presence>,
303 editable_settings: ReadExpect<'a, EditableSettings>,
304 censor: ReadExpect<'a, Arc<censor::Censor>>,
305 automod: ReadExpect<'a, AutoMod>,
306 time: ReadExpect<'a, Time>,
307 #[cfg(feature = "worldgen")]
308 index: ReadExpect<'a, IndexOwned>,
309 world: ReadExpect<'a, Arc<World>>,
310}
311
312#[derive(Default)]
314pub struct Sys;
315impl<'a> System<'a> for Sys {
316 type SystemData = Data<'a>;
317
318 const NAME: &'static str = "msg::character_screen";
319 const ORIGIN: Origin = Origin::Server;
320 const PHASE: Phase = Phase::Create;
321
322 fn run(_job: &mut Job<Self>, mut data: Self::SystemData) {
323 let mut emitters = data.events.get_emitters();
324
325 for (entity, client) in (&data.entities, &mut data.clients).join() {
326 let _ = super::try_recv_all(client, 1, |client, msg| {
327 Self::handle_client_character_screen_msg(
328 &mut emitters,
329 entity,
330 client,
331 &data.character_loader,
332 &mut data.character_updater,
333 &data.uids,
334 &data.players,
335 &data.admins,
336 &data.presences,
337 &data.editable_settings,
338 &data.censor,
339 &data.automod,
340 msg,
341 *data.time,
342 #[cfg(feature = "worldgen")]
343 &data.index,
344 &data.world,
345 )
346 });
347 }
348 }
349}