1use super::{
2 Arc, CEntityEvent, CGameEvent, CSystemChat, CTabList, CTickingState, CTickingStep, Color,
3 CommandSender, CommandSource, DisplayResolutor, Entity, GameEventType, Modifier, Player,
4 Server, SprintReport, TabListTickStats, TextComponent, Uuid, client_permission_event,
5 command_tree_packet, translations,
6};
7
8impl Server {
9 fn broadcast_system_chat(&self, message: &TextComponent, excluded_player: Option<Uuid>) {
11 log::info!("{}", message.to_plain(&DisplayResolutor));
12 self.online_players.iter_players(|uuid, player| {
13 if Some(*uuid) != excluded_player {
14 player.send_packet(CSystemChat::new(message, false, player));
15 }
16 true
17 });
18 }
19
20 pub(super) fn tab_list_components(
22 tick_stats: TabListTickStats,
23 ) -> (TextComponent, TextComponent) {
24 let tps_color = if tick_stats.tps >= 19.5 {
26 Color::Green
27 } else if tick_stats.tps >= 15.0 {
28 Color::Yellow
29 } else {
30 Color::Red
31 };
32
33 let mspt_color = |mspt: f32| {
34 if mspt <= 50.0 {
35 Color::Aqua
36 } else {
37 Color::Red
38 }
39 };
40
41 let header = TextComponent::plain("\n").add_children(vec![
42 TextComponent::plain("Steel Dev Build").color(Color::Yellow),
43 TextComponent::plain("\n"),
44 ]);
45 let footer = TextComponent::plain("\n").add_children(vec![
46 TextComponent::plain("TPS: ").color(Color::Gray),
47 TextComponent::plain(format!("{:.1}", tick_stats.tps)).color(tps_color),
48 TextComponent::plain(" | ").color(Color::DarkGray),
49 TextComponent::plain("MSPT: ").color(Color::Gray),
50 TextComponent::plain(format!("{:.2}", tick_stats.recent_mspt))
51 .color(mspt_color(tick_stats.recent_mspt)),
52 TextComponent::plain(" recent | ").color(Color::Gray),
53 TextComponent::plain(format!("{:.2}", tick_stats.average_mspt))
54 .color(mspt_color(tick_stats.average_mspt)),
55 TextComponent::plain(" avg (5s) | ").color(Color::Gray),
56 TextComponent::plain(format!("{:.2}", tick_stats.p95_mspt))
57 .color(mspt_color(tick_stats.p95_mspt)),
58 TextComponent::plain(" p95").color(Color::Gray),
59 TextComponent::plain("\n"),
60 ]);
61
62 (header, footer)
63 }
64
65 pub(super) fn broadcast_tab_list(&self, tick_stats: TabListTickStats) {
67 let (header, footer) = Self::tab_list_components(tick_stats);
68
69 self.broadcast_to_online_with(|player| CTabList::new(&header, &footer, player));
70 }
71
72 pub(crate) fn broadcast_sprint_report(&self, report: &SprintReport) {
74 let message: TextComponent = translations::COMMANDS_TICK_SPRINT_REPORT
75 .message([
76 TextComponent::from(format!("{}", report.ticks_per_second)),
77 TextComponent::from(format!("{:.2}", report.ms_per_tick)),
78 ])
79 .into();
80
81 self.broadcast_system_chat(&message, None);
82 }
83
84 pub(super) fn broadcast_player_join_message(
85 &self,
86 player: &Player,
87 previous_name: Option<&str>,
88 ) {
89 let display_name = player.display_name();
90 let old_name = previous_name.unwrap_or(player.gameprofile.name.as_str());
92 let message: TextComponent = if player.gameprofile.name.eq_ignore_ascii_case(old_name) {
93 translations::MULTIPLAYER_PLAYER_JOINED
94 .message([display_name])
95 .into()
96 } else {
97 translations::MULTIPLAYER_PLAYER_JOINED_RENAMED
98 .message([display_name, TextComponent::plain(old_name.to_owned())])
99 .into()
100 };
101 let message = message.color(Color::Yellow);
102 self.broadcast_system_chat(&message, Some(player.gameprofile.id));
103 }
104
105 pub(super) fn broadcast_player_leave_message(&self, player: &Player) {
106 let message: TextComponent = translations::MULTIPLAYER_PLAYER_LEFT
107 .message([player.display_name()])
108 .into();
109 let message = message.color(Color::Yellow);
110 self.broadcast_system_chat(&message, None);
111 }
112
113 pub fn broadcast_ticking_state(&self) {
116 let tick_manager = self.tick_rate_manager.read();
117 let packet = CTickingState::new(tick_manager.tick_rate(), tick_manager.is_frozen());
118 drop(tick_manager);
119
120 self.broadcast_to_online(packet);
121 }
122
123 pub fn broadcast_ticking_step(&self) {
126 let tick_manager = self.tick_rate_manager.read();
127 let packet = CTickingStep::new(tick_manager.frozen_ticks_to_run());
128 drop(tick_manager);
129
130 self.broadcast_to_online(packet);
131 }
132
133 pub fn send_ticking_state_to_player(&self, player: &Player) {
136 let tick_manager = self.tick_rate_manager.read();
137 let state_packet = CTickingState::new(tick_manager.tick_rate(), tick_manager.is_frozen());
138 let step_packet = CTickingStep::new(tick_manager.frozen_ticks_to_run());
139 drop(tick_manager);
140
141 player.send_packet(state_packet);
142 player.send_packet(step_packet);
143 }
144
145 pub fn resend_player_context(self: &Arc<Self>, player: &Arc<Player>) {
147 player.send_difficulty();
148 player.send_inventory_to_remote();
149
150 self.resend_player_permission_context(player);
151
152 self.send_ticking_state_to_player(player);
153
154 player.send_packet(CGameEvent {
155 event: GameEventType::ChangeGameMode,
156 data: player.game_mode().into(),
157 });
158 }
159
160 pub fn resend_player_permission_context(self: &Arc<Self>, player: &Arc<Player>) {
162 let world = player.get_world();
163 player.send_packet(CEntityEvent {
164 entity_id: player.id(),
165 event: client_permission_event(player, &world),
166 });
167
168 let server = player.server();
169 if !Arc::ptr_eq(&server, self) {
170 tracing::error!(
171 player = %player.gameprofile.name,
172 "cannot project commands from a different server"
173 );
174 return;
175 }
176 let Some(shared_player) = self.online_players.get_by_uuid(&player.gameprofile.id) else {
177 tracing::error!(
178 player = %player.gameprofile.name,
179 "cannot project commands for a player outside the online player map"
180 );
181 return;
182 };
183 if !Arc::ptr_eq(&shared_player, player) {
184 tracing::error!(
185 player = %player.gameprofile.name,
186 "cannot project commands for a stale player handle"
187 );
188 return;
189 }
190 let source = CommandSource::new(CommandSender::Player(shared_player), server);
191 let commands = {
192 let dispatcher = self.command_dispatcher.read();
193 command_tree_packet(&dispatcher, &source)
194 };
195 match commands {
196 Ok(commands) => player.send_packet(commands),
197 Err(error) => tracing::error!(
198 player = %player.gameprofile.name,
199 %error,
200 "failed to project the player's command tree"
201 ),
202 }
203 }
204}