feat(connection_cache): implement player samples in server status

- Add `Sample` import from pumpkin_protocol for player sample representation
- Add `Uuid` import for player identification
- Add `MAX_SAMPLE_PLAYERS` constant set to 12 to limit sample size
- Add `player_samples` field to `CachedStatus` struct to track online players
- Implement player sample collection in `add_player` method with deduplication
- Implement player sample removal in `remove_player` method
- Update status response JSON with player samples on each change
- Replace TODO comments with functional implementation for player tracking
- Ensures server status response includes up to 12 player samples for clients
This commit is contained in:
denys-shatin
2026-02-10 01:07:25 +02:00
parent 62c40dcaf9
commit 2d403557ea

View File

@@ -4,7 +4,7 @@ use core::error;
use pumpkin_config::BasicConfiguration;
use pumpkin_data::packet::CURRENT_MC_PROTOCOL;
use pumpkin_protocol::{
Players, StatusResponse, Version,
Players, Sample, StatusResponse, Version,
codec::var_int::VarInt,
java::client::{config::CPluginMessage, status::CStatusResponse},
};
@@ -13,8 +13,10 @@ use std::{
fs::{self},
path::Path,
};
use uuid::Uuid;
const DEFAULT_ICON: &[u8] = include_bytes!("../../../assets/default_icon.png");
const MAX_SAMPLE_PLAYERS: usize = 12;
fn load_icon_from_file<P: AsRef<Path>>(path: P) -> Result<String, Box<dyn error::Error>> {
let buf = fs::read(path)?;
@@ -41,6 +43,7 @@ pub struct CachedStatus {
// We cache the json response here so we don't parse it every time someone makes a status request.
// Keep in mind that we must parse this again when the StatusResponse changes, which usually happen when a player joins or leaves.
status_response_json: String,
player_samples: Vec<(Uuid, String)>,
}
pub struct CachedBranding {
@@ -79,6 +82,7 @@ impl CachedStatus {
Self {
status_response,
status_response_json,
player_samples: Vec::new(),
}
}
@@ -86,37 +90,48 @@ impl CachedStatus {
CStatusResponse::new(self.status_response_json.clone())
}
// TODO: Player samples
pub fn add_player(&mut self, _player: &Player) {
pub fn add_player(&mut self, player: &Player) {
let status_response = &mut self.status_response;
if let Some(players) = &mut status_response.players {
// TODO
// if player
// .client
// .added_to_server_listing
// .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
// .is_ok()
// {
players.online = players.online.saturating_add(1);
// }
let player_id = player.gameprofile.id;
let player_name = player.gameprofile.name.clone();
if !self.player_samples.iter().any(|(id, _)| *id == player_id) {
self.player_samples.push((player_id, player_name.clone()));
players.sample = self.player_samples
.iter()
.take(MAX_SAMPLE_PLAYERS)
.map(|(id, name)| Sample {
name: name.clone(),
id: id.to_string(),
})
.collect();
}
}
self.status_response_json = serde_json::to_string(&status_response)
.expect("Failed to parse status response into JSON");
}
pub fn remove_player(&mut self, _player: &Player) {
pub fn remove_player(&mut self, player: &Player) {
let status_response = &mut self.status_response;
if let Some(players) = &mut status_response.players {
// TODO
// if player
// .client
// .added_to_server_listing
// .compare_exchange(true, false, Ordering::Acquire, Ordering::Relaxed)
// .is_ok()
// {
players.online = players.online.saturating_sub(1);
// }
let player_id = player.gameprofile.id;
self.player_samples.retain(|(id, _)| *id != player_id);
players.sample = self.player_samples
.iter()
.take(MAX_SAMPLE_PLAYERS)
.map(|(id, name)| Sample {
name: name.clone(),
id: id.to_string(),
})
.collect();
}
self.status_response_json = serde_json::to_string(&status_response)