Pumpkin/pumpkin/src/world/custom_bossbar.rs
2024-12-30 13:10:02 +01:00

419 lines
13 KiB
Rust

use crate::command::args::GetCloned;
use crate::entity::player::Player;
use crate::server::Server;
use crate::world::bossbar::{Bossbar, BossbarColor, BossbarDivisions};
use std::collections::HashMap;
use std::sync::Arc;
use thiserror::Error;
use uuid::Uuid;
#[derive(Debug, Error)]
pub enum BossbarUpdateError {
#[error("Invalid resource location")]
InvalidResourceLocation(String),
#[error("No changes")]
NoChanges(String),
}
/// Representing the stored custom boss bars from level.dat
#[derive(Clone)]
pub struct CustomBossbar {
pub namespace: String,
pub bossbar_data: Bossbar,
pub max: u32,
pub value: u32,
pub visible: bool,
pub player: Vec<Uuid>,
}
impl CustomBossbar {
#[deny(clippy::new_without_default)]
#[must_use]
pub fn new(namespace: String, bossbar_data: Bossbar) -> Self {
Self {
namespace,
bossbar_data,
max: 100,
value: 0,
visible: true,
player: vec![],
}
}
}
pub struct CustomBossbars {
pub custom_bossbars: HashMap<String, CustomBossbar>,
}
impl Default for CustomBossbars {
fn default() -> Self {
Self::new()
}
}
impl CustomBossbars {
#[must_use]
pub fn new() -> Self {
Self {
custom_bossbars: HashMap::new(),
}
}
#[must_use]
pub fn get_player_bars(&self, uuid: &Uuid) -> Option<Vec<&Bossbar>> {
let mut player_bars: Vec<&Bossbar> = Vec::new();
for bossbar in &self.custom_bossbars {
if bossbar.1.player.contains(uuid) {
player_bars.push(&bossbar.1.bossbar_data);
}
}
if !player_bars.is_empty() {
return Some(player_bars);
}
None
}
pub fn create_bossbar(&mut self, namespace: String, bossbar_data: Bossbar) {
self.custom_bossbars.insert(
namespace.clone(),
CustomBossbar::new(namespace, bossbar_data),
);
}
pub fn replace_bossbar(&mut self, resource_location: &str, bossbar_data: CustomBossbar) {
self.custom_bossbars
.insert(resource_location.to_string(), bossbar_data);
}
#[must_use]
pub fn get_all_bossbars(&self) -> Option<Vec<CustomBossbar>> {
let mut bossbars: Vec<CustomBossbar> = Vec::new();
for bossbar in self.custom_bossbars.clone() {
bossbars.push(bossbar.1);
}
Some(bossbars)
}
#[must_use]
pub fn get_bossbar(&self, resource_location: &str) -> Option<CustomBossbar> {
let bossbar = self.custom_bossbars.get(resource_location);
if let Some(bossbar) = bossbar {
return Some(bossbar.clone());
}
None
}
pub async fn remove_bossbar(
&mut self,
server: &Server,
resource_location: String,
) -> Result<(), BossbarUpdateError> {
let bossbar = self.custom_bossbars.get_cloned(&resource_location);
if let Some(bossbar) = bossbar {
self.custom_bossbars.remove(&resource_location);
let players: Vec<Arc<Player>> = server.get_all_players().await;
let online_players: Vec<&Arc<Player>> = players
.iter()
.filter(|player| bossbar.player.contains(&player.gameprofile.id))
.collect();
if bossbar.visible {
for player in online_players {
player.remove_bossbar(bossbar.bossbar_data.uuid).await;
}
}
return Ok(());
}
Err(BossbarUpdateError::InvalidResourceLocation(
resource_location,
))
}
#[must_use]
pub fn has_bossbar(&self, resource_location: &str) -> bool {
self.custom_bossbars.contains_key(resource_location)
}
pub async fn update_health(
&mut self,
server: &Server,
resource_location: String,
max_value: u32,
value: u32,
) -> Result<(), BossbarUpdateError> {
let bossbar = self.custom_bossbars.get_mut(&resource_location);
if let Some(bossbar) = bossbar {
if bossbar.value == value && bossbar.max == max_value {
return Err(BossbarUpdateError::NoChanges(String::from(
"That's already the value of this bossbar",
)));
}
let ratio = f64::from(value) / f64::from(max_value);
let health: f32;
if ratio >= 1.0 {
health = 1.0;
} else if ratio <= 0.0 {
health = 0.0;
} else {
health = ratio as f32;
}
bossbar.value = value;
bossbar.max = max_value;
bossbar.bossbar_data.health = health;
if !bossbar.visible {
return Ok(());
}
let players: Vec<Arc<Player>> = server.get_all_players().await;
let matching_players: Vec<&Arc<Player>> = players
.iter()
.filter(|player| bossbar.player.contains(&player.gameprofile.id))
.collect();
for player in matching_players {
player
.update_bossbar_health(&bossbar.bossbar_data.uuid, bossbar.bossbar_data.health)
.await;
}
return Ok(());
}
Err(BossbarUpdateError::InvalidResourceLocation(
resource_location,
))
}
pub async fn update_visibility(
&mut self,
server: &Server,
resource_location: String,
new_visibility: bool,
) -> Result<(), BossbarUpdateError> {
let bossbar = self.custom_bossbars.get_mut(&resource_location);
if let Some(bossbar) = bossbar {
if bossbar.visible == new_visibility && new_visibility {
return Err(BossbarUpdateError::NoChanges(String::from(
"The bossbar is already visible",
)));
}
if bossbar.visible == new_visibility && !new_visibility {
return Err(BossbarUpdateError::NoChanges(String::from(
"The bossbar is already hidden",
)));
}
bossbar.visible = new_visibility;
let players: Vec<Arc<Player>> = server.get_all_players().await;
let online_players: Vec<&Arc<Player>> = players
.iter()
.filter(|player| bossbar.player.contains(&player.gameprofile.id))
.collect();
for player in online_players {
if bossbar.visible {
player.send_bossbar(&bossbar.bossbar_data).await;
} else {
player.remove_bossbar(bossbar.bossbar_data.uuid).await;
}
}
return Ok(());
}
Err(BossbarUpdateError::InvalidResourceLocation(
resource_location,
))
}
pub async fn update_name(
&mut self,
server: &Server,
resource_location: &str,
new_title: &str,
) -> Result<(), BossbarUpdateError> {
let bossbar = self.custom_bossbars.get_mut(resource_location);
if let Some(bossbar) = bossbar {
if bossbar.bossbar_data.title == new_title {
return Err(BossbarUpdateError::NoChanges(String::from(
"That's already the name of this bossbar",
)));
}
bossbar.bossbar_data.title = new_title.to_string();
if !bossbar.visible {
return Ok(());
}
let players: Vec<Arc<Player>> = server.get_all_players().await;
let matching_players: Vec<&Arc<Player>> = players
.iter()
.filter(|player| bossbar.player.contains(&player.gameprofile.id))
.collect();
for player in matching_players {
player
.update_bossbar_title(
&bossbar.bossbar_data.uuid,
bossbar.bossbar_data.title.clone(),
)
.await;
}
return Ok(());
}
Err(BossbarUpdateError::InvalidResourceLocation(
resource_location.to_string(),
))
}
pub async fn update_color(
&mut self,
server: &Server,
resource_location: String,
new_color: BossbarColor,
) -> Result<(), BossbarUpdateError> {
let bossbar = self.custom_bossbars.get_mut(&resource_location);
if let Some(bossbar) = bossbar {
if bossbar.bossbar_data.color == new_color {
return Err(BossbarUpdateError::NoChanges(String::from(
"That's already the color of this bossbar",
)));
}
bossbar.bossbar_data.color = new_color;
if !bossbar.visible {
return Ok(());
}
let players: Vec<Arc<Player>> = server.get_all_players().await;
let matching_players: Vec<&Arc<Player>> = players
.iter()
.filter(|player| bossbar.player.contains(&player.gameprofile.id))
.collect();
for player in matching_players {
player
.update_bossbar_style(
&bossbar.bossbar_data.uuid,
bossbar.bossbar_data.color.clone(),
bossbar.bossbar_data.division.clone(),
)
.await;
}
return Ok(());
}
Err(BossbarUpdateError::InvalidResourceLocation(
resource_location,
))
}
pub async fn update_division(
&mut self,
server: &Server,
resource_location: String,
new_division: BossbarDivisions,
) -> Result<(), BossbarUpdateError> {
let bossbar = self.custom_bossbars.get_mut(&resource_location);
if let Some(bossbar) = bossbar {
if bossbar.bossbar_data.division == new_division {
return Err(BossbarUpdateError::NoChanges(String::from(
"That's already the style of this bossbar",
)));
}
bossbar.bossbar_data.division = new_division;
if !bossbar.visible {
return Ok(());
}
let players: Vec<Arc<Player>> = server.get_all_players().await;
let matching_players: Vec<&Arc<Player>> = players
.iter()
.filter(|player| bossbar.player.contains(&player.gameprofile.id))
.collect();
for player in matching_players {
player
.update_bossbar_style(
&bossbar.bossbar_data.uuid,
bossbar.bossbar_data.color.clone(),
bossbar.bossbar_data.division.clone(),
)
.await;
}
return Ok(());
}
Err(BossbarUpdateError::InvalidResourceLocation(
resource_location,
))
}
pub async fn update_players(
&mut self,
server: &Server,
resource_location: String,
new_players: Vec<Uuid>,
) -> Result<(), BossbarUpdateError> {
let bossbar = self.custom_bossbars.get_mut(&resource_location);
if let Some(bossbar) = bossbar {
// Get difference between old and new player list and remove bossbars from old players
let removed_players: Vec<Uuid> = bossbar
.player
.iter()
.filter(|item| !new_players.contains(item))
.copied()
.collect();
let added_players: Vec<Uuid> = new_players
.iter()
.filter(|item| !bossbar.player.contains(item))
.copied()
.collect();
if removed_players.is_empty() && added_players.is_empty() {
return Err(BossbarUpdateError::NoChanges(String::from(
"Those players are already on the bossbar with nobody to add or remove",
)));
}
if bossbar.visible {
for uuid in removed_players {
let Some(player) = server.get_player_by_uuid(uuid).await else {
continue;
};
player.remove_bossbar(bossbar.bossbar_data.uuid).await;
}
}
bossbar.player = new_players;
if !bossbar.visible {
return Ok(());
}
for uuid in added_players {
let Some(player) = server.get_player_by_uuid(uuid).await else {
continue;
};
player.send_bossbar(&bossbar.bossbar_data).await;
}
return Ok(());
}
Err(BossbarUpdateError::InvalidResourceLocation(
resource_location,
))
}
}