Pumpkin/crates/pumpkin/src/world/scoreboard.rs
Alexander Medvedev 09790fc093 fix: slime spawns
2026-08-29 15:58:32 +02:00

1270 lines
39 KiB
Rust

use rustc_hash::FxHashMap;
use pumpkin_data::scoreboard::ScoreboardDisplaySlot;
use pumpkin_protocol::{
BClientPacket, ClientPacket, NumberFormat,
bedrock::client::{
remove_objective::CRemoveObjective as BRemoveObjective,
set_display_objective::CSetDisplayObjective as BSetDisplayObjective,
set_score::{CSetScore as BSetScore, ScoreEntry as BScoreEntry},
},
codec::var_int::VarInt,
java::client::play::{
CDisplayObjective, CResetScore, CSetPlayerTeam, CUpdateObjectives, CUpdateScore, Mode,
RenderType, TeamMethod, TeamParameters,
},
};
use pumpkin_util::text::{TextComponent, color::NamedColor};
use tracing::warn;
use super::World;
use crate::entity::player::Player;
pub trait ScoreboardTarget: Send + Sync {
fn send_editioned<J: ClientPacket + Sync, B: BClientPacket + Sync>(
&self,
je_packet: &J,
be_packet: &B,
);
fn send_je<J: ClientPacket + Sync>(&self, je_packet: &J);
}
impl ScoreboardTarget for World {
fn send_editioned<J: ClientPacket + Sync, B: BClientPacket + Sync>(
&self,
je_packet: &J,
be_packet: &B,
) {
self.broadcast_editioned(je_packet, be_packet);
}
fn send_je<J: ClientPacket + Sync>(&self, je_packet: &J) {
self.broadcast_packet_all(je_packet);
}
}
impl<T: ScoreboardTarget + ?Sized> ScoreboardTarget for &T {
fn send_editioned<J: ClientPacket + Sync, B: BClientPacket + Sync>(
&self,
je_packet: &J,
be_packet: &B,
) {
(*self).send_editioned(je_packet, be_packet);
}
fn send_je<J: ClientPacket + Sync>(&self, je_packet: &J) {
(*self).send_je(je_packet);
}
}
impl ScoreboardTarget for std::sync::Arc<World> {
fn send_editioned<J: ClientPacket + Sync, B: BClientPacket + Sync>(
&self,
je_packet: &J,
be_packet: &B,
) {
self.broadcast_editioned(je_packet, be_packet);
}
fn send_je<J: ClientPacket + Sync>(&self, je_packet: &J) {
self.broadcast_packet_all(je_packet);
}
}
impl ScoreboardTarget for Player {
fn send_editioned<J: ClientPacket + Sync, B: BClientPacket + Sync>(
&self,
je_packet: &J,
be_packet: &B,
) {
self.try_enqueue_packet_editioned(je_packet, be_packet);
}
fn send_je<J: ClientPacket + Sync>(&self, je_packet: &J) {
self.try_send_client_packet(je_packet);
}
}
impl ScoreboardTarget for std::sync::Arc<Player> {
fn send_editioned<J: ClientPacket + Sync, B: BClientPacket + Sync>(
&self,
je_packet: &J,
be_packet: &B,
) {
self.try_enqueue_packet_editioned(je_packet, be_packet);
}
fn send_je<J: ClientPacket + Sync>(&self, je_packet: &J) {
self.try_send_client_packet(je_packet);
}
}
pub struct NoTarget;
impl ScoreboardTarget for NoTarget {
fn send_editioned<J: ClientPacket + Sync, B: BClientPacket + Sync>(
&self,
_je_packet: &J,
_be_packet: &B,
) {
}
fn send_je<J: ClientPacket + Sync>(&self, _je_packet: &J) {}
}
#[derive(Clone, Debug, Default)]
pub struct Scoreboard {
objectives: FxHashMap<String, ScoreboardObjective>,
display_slots: FxHashMap<ScoreboardDisplaySlot, String>,
scores: FxHashMap<String, FxHashMap<String, ScoreboardScore>>,
teams: FxHashMap<String, Team>,
}
impl Scoreboard {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub const fn get_objectives(&self) -> &FxHashMap<String, ScoreboardObjective> {
&self.objectives
}
#[must_use]
pub fn get_objective(&self, name: &str) -> Option<&ScoreboardObjective> {
self.objectives.get(name)
}
pub fn get_objective_mut(&mut self, name: &str) -> Option<&mut ScoreboardObjective> {
self.objectives.get_mut(name)
}
#[must_use]
pub const fn get_display_slots(&self) -> &FxHashMap<ScoreboardDisplaySlot, String> {
&self.display_slots
}
#[must_use]
pub fn get_display_objective(&self, slot: ScoreboardDisplaySlot) -> Option<&str> {
self.display_slots.get(&slot).map(String::as_str)
}
#[must_use]
pub const fn get_scores(&self) -> &FxHashMap<String, FxHashMap<String, ScoreboardScore>> {
&self.scores
}
#[must_use]
pub fn get_score(&self, entity_name: &str, objective_name: &str) -> Option<&ScoreboardScore> {
self.scores.get(objective_name)?.get(entity_name)
}
#[must_use]
pub fn get_score_value(&self, entity_name: &str, objective_name: &str) -> Option<i32> {
self.get_score(entity_name, objective_name)
.map(|s| s.value.0)
}
#[must_use]
pub fn get_scores_for_objective(
&self,
objective_name: &str,
) -> Option<&FxHashMap<String, ScoreboardScore>> {
self.scores.get(objective_name)
}
#[must_use]
pub fn get_scores_for_entity(&self, entity_name: &str) -> FxHashMap<String, &ScoreboardScore> {
let mut entity_scores = FxHashMap::default();
for (obj_name, obj_scores) in &self.scores {
if let Some(score) = obj_scores.get(entity_name) {
entity_scores.insert(obj_name.clone(), score);
}
}
entity_scores
}
#[must_use]
pub const fn get_teams(&self) -> &FxHashMap<String, Team> {
&self.teams
}
#[must_use]
pub fn get_team(&self, name: &str) -> Option<&Team> {
self.teams.get(name)
}
pub fn get_team_mut(&mut self, name: &str) -> Option<&mut Team> {
self.teams.get_mut(name)
}
#[must_use]
pub fn get_entity_team(&self, entity_name: &str) -> Option<&Team> {
self.teams
.values()
.find(|team| team.players.iter().any(|p| p == entity_name))
}
pub fn add_objective(
&mut self,
target: &impl ScoreboardTarget,
objective: ScoreboardObjective,
) {
if self.objectives.contains_key(&objective.name) {
warn!(
"Tried to create an objective which already exists: {}",
&objective.name
);
return;
}
let je_update = CUpdateObjectives::new(
objective.name.clone(),
Mode::Add,
objective.display_name.clone(),
objective.render_type,
objective.number_format.clone(),
);
let be_update = BSetDisplayObjective {
display_slot_name: "sidebar".to_string(), // Default to sidebar
objective_name: objective.name.clone(),
objective_display_name: objective.display_name.clone().get_text(),
criteria_name: "dummy".to_string(),
sort_order: VarInt(0),
};
target.send_editioned(&je_update, &be_update);
self.objectives.insert(objective.name.clone(), objective);
}
pub fn update_objective(
&mut self,
target: &impl ScoreboardTarget,
objective: ScoreboardObjective,
) {
if !self.objectives.contains_key(&objective.name) {
warn!(
"Tried to update an objective which does not exist: {}",
&objective.name
);
return;
}
let je_update = CUpdateObjectives::new(
objective.name.clone(),
Mode::Update,
objective.display_name.clone(),
objective.render_type,
objective.number_format.clone(),
);
let be_update = BSetDisplayObjective {
display_slot_name: "sidebar".to_string(),
objective_name: objective.name.clone(),
objective_display_name: objective.display_name.clone().get_text(),
criteria_name: "dummy".to_string(),
sort_order: VarInt(0),
};
target.send_editioned(&je_update, &be_update);
self.objectives.insert(objective.name.clone(), objective);
}
pub fn set_display_objective(
&mut self,
target: &impl ScoreboardTarget,
slot: ScoreboardDisplaySlot,
objective_name: Option<&str>,
) {
let slot_str = match slot {
ScoreboardDisplaySlot::List => "list",
ScoreboardDisplaySlot::BelowName => "belowname",
_ => "sidebar",
};
let obj_name_str = objective_name.unwrap_or("");
let display_name = objective_name
.and_then(|name| self.objectives.get(name))
.map_or_else(
|| obj_name_str.to_string(),
|o| o.display_name.clone().get_text(),
);
let je_display = CDisplayObjective::new(slot, obj_name_str.to_string());
let be_display = BSetDisplayObjective {
display_slot_name: slot_str.to_string(),
objective_name: obj_name_str.to_string(),
objective_display_name: display_name,
criteria_name: "dummy".to_string(),
sort_order: VarInt(0),
};
target.send_editioned(&je_display, &be_display);
if let Some(name) = objective_name {
self.display_slots.insert(slot, name.to_string());
} else {
self.display_slots.remove(&slot);
}
}
pub fn clear_display_objective(
&mut self,
target: &impl ScoreboardTarget,
slot: ScoreboardDisplaySlot,
) {
self.set_display_objective(target, slot, None);
}
pub fn remove_objective(&mut self, target: &impl ScoreboardTarget, name: &str) {
if !self.objectives.contains_key(name) {
warn!(
"Tried to remove an objective which does not exist: {}",
name
);
return;
}
let je_packet = CUpdateObjectives::new(
name.to_string(),
Mode::Remove,
TextComponent::empty(),
RenderType::Integer,
None,
);
let be_packet = BRemoveObjective {
objective_name: name.to_string(),
};
target.send_editioned(&je_packet, &be_packet);
self.objectives.remove(name);
self.scores.remove(name);
self.display_slots.retain(|_, obj| obj != name);
}
pub fn update_score(&mut self, target: &impl ScoreboardTarget, score: ScoreboardScore) {
if !self.objectives.contains_key(&score.objective_name) {
warn!(
"Tried to place a score into an objective which does not exist: {}",
&score.objective_name
);
return;
}
let je_packet = CUpdateScore::new(
score.entity_name.clone(),
score.objective_name.clone(),
score.value,
score.display_name.clone(),
score.number_format.clone(),
);
let be_packet = BSetScore {
action: VarInt(0), // Change
entries: vec![BScoreEntry {
scoreboard_id: score.entity_name.as_ptr() as i64, // Internal ID
objective_name: score.objective_name.clone(),
score: score.value,
entry_type: VarInt(3), // Fake player / Literal
entity_unique_id: 0,
custom_name: score.entity_name.clone(),
}],
};
target.send_editioned(&je_packet, &be_packet);
self.scores
.entry(score.objective_name.clone())
.or_default()
.insert(score.entity_name.clone(), score);
}
pub fn set_score_value(
&mut self,
target: &impl ScoreboardTarget,
entity_name: impl Into<String>,
objective_name: impl Into<String>,
value: i32,
) {
let entity_s = entity_name.into();
let obj_s = objective_name.into();
let existing = self.get_score(&entity_s, &obj_s).cloned();
let score = ScoreboardScore {
entity_name: entity_s,
objective_name: obj_s,
value: VarInt(value),
display_name: existing.as_ref().and_then(|s| s.display_name.clone()),
number_format: existing.as_ref().and_then(|s| s.number_format.clone()),
locked: existing.as_ref().is_none_or(|s| s.locked),
};
self.update_score(target, score);
}
pub fn add_score(
&mut self,
target: &impl ScoreboardTarget,
entity_name: impl Into<String>,
objective_name: impl Into<String>,
delta: i32,
) -> i32 {
let entity_s = entity_name.into();
let obj_s = objective_name.into();
let current_val = self.get_score_value(&entity_s, &obj_s).unwrap_or(0);
let new_val = current_val + delta;
self.set_score_value(target, entity_s, obj_s, new_val);
new_val
}
pub fn remove_score(
&mut self,
target: &impl ScoreboardTarget,
entity_name: &str,
objective_name: &str,
) {
let je_packet = CResetScore::new(entity_name.to_string(), Some(objective_name.to_string()));
let be_packet = BSetScore {
action: VarInt(1), // Remove
entries: vec![BScoreEntry {
scoreboard_id: entity_name.as_ptr() as i64,
objective_name: objective_name.to_string(),
score: VarInt(0),
entry_type: VarInt(3),
entity_unique_id: 0,
custom_name: entity_name.to_string(),
}],
};
target.send_editioned(&je_packet, &be_packet);
if let Some(objective_scores) = self.scores.get_mut(objective_name) {
objective_scores.remove(entity_name);
}
}
pub fn reset_scores_for_entity(&mut self, target: &impl ScoreboardTarget, entity_name: &str) {
let je_packet = CResetScore::new(entity_name.to_string(), None);
let mut be_entries = Vec::new();
for (obj_name, obj_scores) in &self.scores {
if obj_scores.contains_key(entity_name) {
be_entries.push(BScoreEntry {
scoreboard_id: entity_name.as_ptr() as i64,
objective_name: obj_name.clone(),
score: VarInt(0),
entry_type: VarInt(3),
entity_unique_id: 0,
custom_name: entity_name.to_string(),
});
}
}
let be_packet = BSetScore {
action: VarInt(1), // Remove
entries: be_entries,
};
target.send_editioned(&je_packet, &be_packet);
for obj_scores in self.scores.values_mut() {
obj_scores.remove(entity_name);
}
}
pub fn add_team(&mut self, target: &impl ScoreboardTarget, team: Team) {
if self.teams.contains_key(&team.name) {
warn!("Tried to create Team which already exists, {}", team.name);
return;
}
let parameters = TeamParameters {
display_name: &team.display_name,
options: team.options,
nametag_visibility: team.nametag_visibility.to_str(),
collision_rule: team.collision_rule.to_str(),
color: team.color as i32,
player_prefix: &team.player_prefix,
player_suffix: &team.player_suffix,
};
target.send_je(&CSetPlayerTeam {
team_name: team.name.clone(),
method: TeamMethod::Create,
parameters: Some(parameters),
players: team.players.clone().into(),
});
self.teams.insert(team.name.clone(), team);
}
pub fn create_team(&mut self, target: &impl ScoreboardTarget, team: Team) {
self.add_team(target, team);
}
pub fn update_team(&mut self, target: &impl ScoreboardTarget, team: Team) {
if !self.teams.contains_key(&team.name) {
warn!("Tried to update Team which does not exist, {}", team.name);
return;
}
let parameters = TeamParameters {
display_name: &team.display_name,
options: team.options,
nametag_visibility: team.nametag_visibility.to_str(),
collision_rule: team.collision_rule.to_str(),
color: team.color as i32,
player_prefix: &team.player_prefix,
player_suffix: &team.player_suffix,
};
target.send_je(&CSetPlayerTeam {
team_name: team.name.clone(),
method: TeamMethod::Update,
parameters: Some(parameters),
players: Box::new([]),
});
self.teams.insert(team.name.clone(), team);
}
pub fn remove_team(&mut self, target: &impl ScoreboardTarget, name: &str) {
if !self.teams.contains_key(name) {
warn!("Tried to remove Team which does not exist, {}", name);
return;
}
target.send_je(&CSetPlayerTeam {
team_name: name.to_string(),
method: TeamMethod::Remove,
parameters: None,
players: Box::new([]),
});
self.teams.remove(name);
}
pub fn add_player_to_team(
&mut self,
target: &impl ScoreboardTarget,
team_name: &str,
player: String,
) {
let Some(team) = self.teams.get_mut(team_name) else {
warn!(
"Tried to add player to Team which does not exist, {}",
team_name
);
return;
};
if team.players.contains(&player) {
return;
}
target.send_je(&CSetPlayerTeam {
team_name: team_name.to_string(),
method: TeamMethod::AddPlayers,
parameters: None,
players: vec![player.clone()].into(),
});
team.players.push(player);
}
pub fn remove_player_from_team(
&mut self,
target: &impl ScoreboardTarget,
team_name: &str,
player: &str,
) {
let Some(team) = self.teams.get_mut(team_name) else {
warn!(
"Tried to remove player from Team which does not exist, {}",
team_name
);
return;
};
if !team.players.contains(&player.to_string()) {
return;
}
target.send_je(&CSetPlayerTeam {
team_name: team_name.to_string(),
method: TeamMethod::RemovePlayers,
parameters: None,
players: vec![player.to_string()].into(),
});
team.players.retain(|p| p != player);
}
pub fn clear_team_players(&mut self, target: &impl ScoreboardTarget, team_name: &str) {
let Some(team) = self.teams.get_mut(team_name) else {
warn!(
"Tried to clear players from Team which does not exist, {}",
team_name
);
return;
};
if team.players.is_empty() {
return;
}
let players_to_remove = team.players.clone();
target.send_je(&CSetPlayerTeam {
team_name: team_name.to_string(),
method: TeamMethod::RemovePlayers,
parameters: None,
players: players_to_remove.into(),
});
team.players.clear();
}
pub fn send_to_player(&self, player: &Player) {
for objective in self.objectives.values() {
let je_update = CUpdateObjectives::new(
objective.name.clone(),
Mode::Add,
objective.display_name.clone(),
objective.render_type,
objective.number_format.clone(),
);
let be_update = BSetDisplayObjective {
display_slot_name: "sidebar".to_string(),
objective_name: objective.name.clone(),
objective_display_name: objective.display_name.clone().get_text(),
criteria_name: "dummy".to_string(),
sort_order: VarInt(0),
};
player.try_enqueue_packet_editioned(&je_update, &be_update);
}
for (slot, objective_name) in &self.display_slots {
let slot_str = match slot {
ScoreboardDisplaySlot::List => "list",
ScoreboardDisplaySlot::BelowName => "belowname",
_ => "sidebar",
};
let display_name = self.objectives.get(objective_name).map_or_else(
|| objective_name.clone(),
|o| o.display_name.clone().get_text(),
);
let je_display = CDisplayObjective::new(*slot, objective_name.clone());
let be_display = BSetDisplayObjective {
display_slot_name: slot_str.to_string(),
objective_name: objective_name.clone(),
objective_display_name: display_name,
criteria_name: "dummy".to_string(),
sort_order: VarInt(0),
};
player.try_enqueue_packet_editioned(&je_display, &be_display);
}
for objective_scores in self.scores.values() {
for score in objective_scores.values() {
let je_packet = CUpdateScore::new(
score.entity_name.clone(),
score.objective_name.clone(),
score.value,
score.display_name.clone(),
score.number_format.clone(),
);
let be_packet = BSetScore {
action: VarInt(0),
entries: vec![BScoreEntry {
scoreboard_id: score.entity_name.as_ptr() as i64,
objective_name: score.objective_name.clone(),
score: score.value,
entry_type: VarInt(3),
entity_unique_id: 0,
custom_name: score.entity_name.clone(),
}],
};
player.try_enqueue_packet_editioned(&je_packet, &be_packet);
}
}
for team in self.teams.values() {
let parameters = TeamParameters {
display_name: &team.display_name,
options: team.options,
nametag_visibility: team.nametag_visibility.to_str(),
collision_rule: team.collision_rule.to_str(),
color: team.color as i32,
player_prefix: &team.player_prefix,
player_suffix: &team.player_suffix,
};
let je_packet = CSetPlayerTeam {
team_name: team.name.clone(),
method: TeamMethod::Create,
parameters: Some(parameters),
players: team.players.clone().into(),
};
player.try_send_client_packet(&je_packet);
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ScoreboardObjective {
pub name: String,
pub display_name: TextComponent,
pub render_type: RenderType,
pub number_format: Option<NumberFormat>,
pub criterion: String,
}
impl ScoreboardObjective {
#[must_use]
pub fn new(
name: impl Into<String>,
display_name: TextComponent,
render_type: RenderType,
number_format: Option<NumberFormat>,
criterion: impl Into<String>,
) -> Self {
Self {
name: name.into(),
display_name,
render_type,
number_format,
criterion: criterion.into(),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ScoreboardScore {
pub entity_name: String,
pub objective_name: String,
pub value: VarInt,
pub display_name: Option<TextComponent>,
pub number_format: Option<NumberFormat>,
pub locked: bool,
}
impl ScoreboardScore {
#[must_use]
pub fn new(
entity_name: impl Into<String>,
objective_name: impl Into<String>,
value: VarInt,
display_name: Option<TextComponent>,
number_format: Option<NumberFormat>,
) -> Self {
Self {
entity_name: entity_name.into(),
objective_name: objective_name.into(),
value,
display_name,
number_format,
locked: true,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum NameTagVisibility {
Always,
Never,
HideForOtherTeams,
HideForOwnTeam,
}
impl NameTagVisibility {
#[must_use]
pub const fn to_str(&self) -> &'static str {
match self {
Self::Always => "always",
Self::Never => "never",
Self::HideForOtherTeams => "hideForOtherTeams",
Self::HideForOwnTeam => "hideForOwnTeam",
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CollisionRule {
Always,
Never,
PushOtherTeams,
PushOwnTeam,
}
impl CollisionRule {
#[must_use]
pub const fn to_str(&self) -> &'static str {
match self {
Self::Always => "always",
Self::Never => "never",
Self::PushOtherTeams => "pushOtherTeams",
Self::PushOwnTeam => "pushOwnTeam",
}
}
}
#[derive(Clone, Debug)]
pub struct Team {
pub name: String,
pub display_name: TextComponent,
pub options: i8,
pub nametag_visibility: NameTagVisibility,
pub collision_rule: CollisionRule,
pub color: NamedColor,
pub player_prefix: TextComponent,
pub player_suffix: TextComponent,
pub players: Vec<String>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum BedrockSortOrder {
Ascending,
Descending,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum BedrockDisplaySlot {
PlayerList,
Sidebar,
BelowName,
}
impl BedrockDisplaySlot {
#[must_use]
pub const fn to_str(&self) -> &'static str {
match self {
Self::PlayerList => "list",
Self::Sidebar => "sidebar",
Self::BelowName => "belowname",
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BedrockObjective {
pub name: String,
pub display_name: String,
pub sort_order: BedrockSortOrder,
}
#[derive(Clone, Debug, Default)]
pub struct BedrockScoreboard {
pub objectives: FxHashMap<String, BedrockObjective>,
pub display_slots: FxHashMap<BedrockDisplaySlot, String>,
pub scores: FxHashMap<(String, String), i32>,
}
impl BedrockScoreboard {
pub fn add_objective(&mut self, player: &Player, objective: BedrockObjective) {
let be_update = BSetDisplayObjective {
display_slot_name: "sidebar".to_string(),
objective_name: objective.name.clone(),
objective_display_name: objective.display_name.clone(),
criteria_name: "dummy".to_string(),
sort_order: VarInt(match objective.sort_order {
BedrockSortOrder::Ascending => 0,
BedrockSortOrder::Descending => 1,
}),
};
player.try_enqueue_packet_editioned(
&pumpkin_protocol::java::client::play::CUpdateObjectives::new(
objective.name.clone(),
pumpkin_protocol::java::client::play::Mode::Add,
TextComponent::text(objective.display_name.clone()),
pumpkin_protocol::java::client::play::RenderType::Integer,
None,
),
&be_update,
);
self.objectives.insert(objective.name.clone(), objective);
}
pub fn update_objective(&mut self, player: &Player, objective: BedrockObjective) {
let be_update = BSetDisplayObjective {
display_slot_name: "sidebar".to_string(),
objective_name: objective.name.clone(),
objective_display_name: objective.display_name.clone(),
criteria_name: "dummy".to_string(),
sort_order: VarInt(match objective.sort_order {
BedrockSortOrder::Ascending => 0,
BedrockSortOrder::Descending => 1,
}),
};
player.try_enqueue_packet_editioned(
&pumpkin_protocol::java::client::play::CUpdateObjectives::new(
objective.name.clone(),
pumpkin_protocol::java::client::play::Mode::Update,
TextComponent::text(objective.display_name.clone()),
pumpkin_protocol::java::client::play::RenderType::Integer,
None,
),
&be_update,
);
self.objectives.insert(objective.name.clone(), objective);
}
pub fn remove_objective(&mut self, player: &Player, name: &str) {
let be_remove = BRemoveObjective {
objective_name: name.to_string(),
};
player.try_enqueue_packet_editioned(
&pumpkin_protocol::java::client::play::CUpdateObjectives::new(
name.to_string(),
pumpkin_protocol::java::client::play::Mode::Remove,
TextComponent::text(""),
pumpkin_protocol::java::client::play::RenderType::Integer,
None,
),
&be_remove,
);
self.objectives.remove(name);
self.display_slots.retain(|_, v| v != name);
self.scores.retain(|(_, obj), _| obj != name);
}
pub fn set_display_objective(
&mut self,
player: &Player,
slot: BedrockDisplaySlot,
objective_name: Option<&str>,
) {
let obj_name_str = objective_name.unwrap_or("");
let display_name = objective_name
.and_then(|name| self.objectives.get(name))
.map_or_else(|| obj_name_str.to_string(), |o| o.display_name.clone());
let be_display = BSetDisplayObjective {
display_slot_name: slot.to_str().to_string(),
objective_name: obj_name_str.to_string(),
objective_display_name: display_name,
criteria_name: "dummy".to_string(),
sort_order: VarInt(0),
};
player.try_enqueue_packet_editioned(
&pumpkin_protocol::java::client::play::CDisplayObjective::new(
match slot {
BedrockDisplaySlot::PlayerList => ScoreboardDisplaySlot::List,
BedrockDisplaySlot::Sidebar => ScoreboardDisplaySlot::Sidebar,
BedrockDisplaySlot::BelowName => ScoreboardDisplaySlot::BelowName,
},
obj_name_str.to_string(),
),
&be_display,
);
if let Some(name) = objective_name {
self.display_slots.insert(slot, name.to_string());
} else {
self.display_slots.remove(&slot);
}
}
pub fn clear_display_objective(&mut self, player: &Player, slot: BedrockDisplaySlot) {
self.set_display_objective(player, slot, None);
}
pub fn update_score(
&mut self,
player: &Player,
entity_name: &str,
objective_name: &str,
value: i32,
) {
let score = ScoreboardScore::new(
entity_name.to_string(),
objective_name.to_string(),
VarInt(value),
None,
None,
);
let be_score = BSetScore {
action: VarInt(0),
entries: vec![],
};
player.try_enqueue_packet_editioned(
&pumpkin_protocol::java::client::play::CUpdateScore::new(
score.entity_name.clone(),
score.objective_name.clone(),
score.value,
score.display_name.clone(),
score.number_format,
),
&be_score,
);
self.scores
.insert((entity_name.to_string(), objective_name.to_string()), value);
}
pub fn add_score(
&mut self,
player: &Player,
entity_name: impl Into<String>,
objective_name: impl Into<String>,
delta: i32,
) -> i32 {
let entity_s = entity_name.into();
let obj_s = objective_name.into();
let current = self
.scores
.get(&(entity_s.clone(), obj_s.clone()))
.copied()
.unwrap_or(0);
let new_val = current + delta;
self.update_score(player, &entity_s, &obj_s, new_val);
new_val
}
pub fn remove_score(&mut self, player: &Player, entity_name: &str, objective_name: &str) {
let be_score = BSetScore {
action: VarInt(1),
entries: vec![],
};
player.try_enqueue_packet_editioned(
&pumpkin_protocol::java::client::play::CResetScore::new(
entity_name.to_string(),
Some(objective_name.to_string()),
),
&be_score,
);
self.scores
.remove(&(entity_name.to_string(), objective_name.to_string()));
}
pub fn reset_scores_for_entity(&mut self, player: &Player, entity_name: &str) {
let be_score = BSetScore {
action: VarInt(1),
entries: vec![],
};
player.try_enqueue_packet_editioned(
&pumpkin_protocol::java::client::play::CResetScore::new(entity_name.to_string(), None),
&be_score,
);
self.scores.retain(|(entity, _), _| entity != entity_name);
}
pub fn send_to_player(&self, player: &Player) {
for objective in self.objectives.values() {
let be_update = BSetDisplayObjective {
display_slot_name: "sidebar".to_string(),
objective_name: objective.name.clone(),
objective_display_name: objective.display_name.clone(),
criteria_name: "dummy".to_string(),
sort_order: VarInt(match objective.sort_order {
BedrockSortOrder::Ascending => 0,
BedrockSortOrder::Descending => 1,
}),
};
player.try_enqueue_packet_editioned(
&pumpkin_protocol::java::client::play::CUpdateObjectives::new(
objective.name.clone(),
pumpkin_protocol::java::client::play::Mode::Add,
TextComponent::text(objective.display_name.clone()),
pumpkin_protocol::java::client::play::RenderType::Integer,
None,
),
&be_update,
);
}
for (slot, objective_name) in &self.display_slots {
let display_name = self
.objectives
.get(objective_name)
.map_or_else(|| objective_name.clone(), |o| o.display_name.clone());
let be_display = BSetDisplayObjective {
display_slot_name: slot.to_str().to_string(),
objective_name: objective_name.clone(),
objective_display_name: display_name,
criteria_name: "dummy".to_string(),
sort_order: VarInt(0),
};
player.try_enqueue_packet_editioned(
&pumpkin_protocol::java::client::play::CDisplayObjective::new(
match slot {
BedrockDisplaySlot::PlayerList => ScoreboardDisplaySlot::List,
BedrockDisplaySlot::Sidebar => ScoreboardDisplaySlot::Sidebar,
BedrockDisplaySlot::BelowName => ScoreboardDisplaySlot::BelowName,
},
objective_name.clone(),
),
&be_display,
);
}
for ((entity_name, objective_name), value) in &self.scores {
let score = ScoreboardScore::new(
entity_name.clone(),
objective_name.clone(),
VarInt(*value),
None,
None,
);
let be_score = BSetScore {
action: VarInt(0),
entries: vec![],
};
player.try_enqueue_packet_editioned(
&pumpkin_protocol::java::client::play::CUpdateScore::new(
score.entity_name,
score.objective_name,
score.value,
score.display_name,
score.number_format,
),
&be_score,
);
}
}
}
#[derive(Default, Debug)]
pub struct ScoreboardBuilder {
scoreboard: Scoreboard,
}
impl ScoreboardBuilder {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn objective(mut self, name: impl Into<String>, display_name: TextComponent) -> Self {
let obj = ScoreboardObjective::new(
name.into(),
display_name,
RenderType::Integer,
None,
"dummy",
);
self.scoreboard.objectives.insert(obj.name.clone(), obj);
self
}
#[must_use]
pub fn objective_with_render(
mut self,
name: impl Into<String>,
display_name: TextComponent,
render_type: RenderType,
) -> Self {
let obj = ScoreboardObjective::new(name.into(), display_name, render_type, None, "dummy");
self.scoreboard.objectives.insert(obj.name.clone(), obj);
self
}
#[must_use]
pub fn display_slot(
mut self,
slot: ScoreboardDisplaySlot,
objective_name: impl Into<String>,
) -> Self {
self.scoreboard
.display_slots
.insert(slot, objective_name.into());
self
}
#[must_use]
pub fn score(
mut self,
entity_name: impl Into<String>,
objective_name: impl Into<String>,
value: i32,
) -> Self {
let entity_s = entity_name.into();
let obj_s = objective_name.into();
let score =
ScoreboardScore::new(entity_s.clone(), obj_s.clone(), VarInt(value), None, None);
self.scoreboard
.scores
.entry(entity_s)
.or_default()
.insert(obj_s, score);
self
}
#[must_use]
pub fn team(mut self, team: Team) -> Self {
self.scoreboard.teams.insert(team.name.clone(), team);
self
}
#[must_use]
pub fn build(self) -> Scoreboard {
self.scoreboard
}
}
#[derive(Default, Debug)]
pub struct BedrockScoreboardBuilder {
scoreboard: BedrockScoreboard,
}
impl BedrockScoreboardBuilder {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn objective(
mut self,
name: impl Into<String>,
display_name: impl Into<String>,
sort_order: BedrockSortOrder,
) -> Self {
let name = name.into();
let obj = BedrockObjective {
name: name.clone(),
display_name: display_name.into(),
sort_order,
};
self.scoreboard.objectives.insert(name, obj);
self
}
#[must_use]
pub fn display_slot(
mut self,
slot: BedrockDisplaySlot,
objective_name: impl Into<String>,
) -> Self {
self.scoreboard
.display_slots
.insert(slot, objective_name.into());
self
}
#[must_use]
pub fn score(
mut self,
entity_name: impl Into<String>,
objective_name: impl Into<String>,
value: i32,
) -> Self {
self.scoreboard
.scores
.insert((entity_name.into(), objective_name.into()), value);
self
}
#[must_use]
pub fn build(self) -> BedrockScoreboard {
self.scoreboard
}
}