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