feat(plugin): native bedrock features

This commit is contained in:
Alexander Medvedev
2026-08-13 21:23:23 +02:00
parent e0d16c74f2
commit 00cb2f9240
15 changed files with 2177 additions and 299 deletions

View File

@@ -1,4 +1,4 @@
/* This file is generated. Do not edit manually. */
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum ScoreboardDisplaySlot {
List,
Sidebar,

View File

@@ -75,7 +75,7 @@ pub enum Mode {
Update,
}
#[derive(Clone, Copy)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RenderType {
Integer,
Hearts,

View File

@@ -385,7 +385,7 @@ impl KnownPack<'_> {
}
}
#[derive(Clone)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum NumberFormat {
/// Show nothing.
Blank,

View File

@@ -60,15 +60,12 @@ impl CommandExecutor for ObjectivesAddExecutor {
return Err(DUPLICATE_OBJECTIVE_ERROR.create_without_context());
}
let obj_name_static = Box::leak(objective_name.to_string().into_boxed_str());
let criterion_static = Box::leak(criterion.to_string().into_boxed_str());
let new_objective = ScoreboardObjective::new(
obj_name_static,
objective_name,
display_name.clone(),
RenderType::Integer,
None,
criterion_static,
criterion,
);
scoreboard.add_objective(world, new_objective).await;
@@ -128,8 +125,8 @@ impl CommandExecutor for PlayersEnableExecutor {
let number_format = current_score.and_then(|s| s.number_format.clone());
let updated_score = ScoreboardScore {
entity_name: Box::leak(player_name.clone().into_boxed_str()),
objective_name: Box::leak(objective_name.to_string().into_boxed_str()),
entity_name: player_name.clone(),
objective_name: objective_name.to_string(),
value: VarInt(value),
display_name,
number_format,

View File

@@ -122,7 +122,7 @@ impl CommandExecutor for TeamAddExecutor {
players: vec![],
};
scoreboard.add_team(world, new_team);
scoreboard.add_team(world, new_team).await;
context
.source
@@ -158,7 +158,7 @@ impl CommandExecutor for TeamRemoveExecutor {
let team_display_name = team.display_name.clone();
scoreboard.remove_team(world, team_name);
scoreboard.remove_team(world, team_name).await;
context
.source
@@ -200,7 +200,9 @@ impl CommandExecutor for TeamEmptyExecutor {
}
for player in &players_to_remove {
scoreboard.remove_player_from_team(world, team_name, player);
scoreboard
.remove_player_from_team(world, team_name, player)
.await;
}
context
@@ -262,7 +264,9 @@ impl CommandExecutor for TeamJoinExecutor {
let count = entity_names.len();
for name in &entity_names {
scoreboard.add_player_to_team(world, team_name, name.clone());
scoreboard
.add_player_to_team(world, team_name, name.clone())
.await;
}
let msg = if count == 1 {
@@ -327,7 +331,9 @@ impl CommandExecutor for TeamLeaveExecutor {
}
}
if let Some(team_name) = found_team {
scoreboard.remove_player_from_team(world, &team_name, name);
scoreboard
.remove_player_from_team(world, &team_name, name)
.await;
removed_count += 1;
}
}
@@ -474,7 +480,7 @@ impl CommandExecutor for TeamModifyColorResetExecutor {
team.color = NamedColor::White;
let team_display_name = team.display_name.clone();
scoreboard.update_team(world, team);
scoreboard.update_team(world, team).await;
context
.source
@@ -519,7 +525,7 @@ impl CommandExecutor for TeamModifyColorExecutor {
team.color = new_color;
let team_display_name = team.display_name.clone();
scoreboard.update_team(world, team);
scoreboard.update_team(world, team).await;
context
.source
@@ -565,7 +571,7 @@ impl CommandExecutor for TeamModifyDisplayNameExecutor {
team.display_name = new_display_name.clone();
let team_display_name = team.display_name.clone();
scoreboard.update_team(world, team);
scoreboard.update_team(world, team).await;
context
.source
@@ -607,7 +613,7 @@ impl CommandExecutor for TeamModifyPrefixExecutor {
let new_prefix = TextComponent::text(new_prefix_str.to_string());
team.player_prefix = new_prefix.clone();
let team_display_name = team.display_name.clone();
scoreboard.update_team(world, team);
scoreboard.update_team(world, team).await;
context
.source
@@ -649,7 +655,7 @@ impl CommandExecutor for TeamModifySuffixExecutor {
let new_suffix = TextComponent::text(new_suffix_str.to_string());
team.player_suffix = new_suffix.clone();
let team_display_name = team.display_name.clone();
scoreboard.update_team(world, team);
scoreboard.update_team(world, team).await;
context
.source
@@ -703,7 +709,7 @@ impl CommandExecutor for TeamModifyFriendlyFireExecutor {
}
let team_display_name = team.display_name.clone();
scoreboard.update_team(world, team);
scoreboard.update_team(world, team).await;
let key = if value {
translation::java::COMMANDS_TEAM_OPTION_FRIENDLYFIRE_ENABLED
@@ -761,7 +767,7 @@ impl CommandExecutor for TeamModifySeeFriendlyInvisiblesExecutor {
}
let team_display_name = team.display_name.clone();
scoreboard.update_team(world, team);
scoreboard.update_team(world, team).await;
let key = if value {
translation::java::COMMANDS_TEAM_OPTION_SEEFRIENDLYINVISIBLES_ENABLED
@@ -829,7 +835,7 @@ impl CommandExecutor for TeamModifyNametagVisibilityExecutor {
};
let team_display_name = team.display_name.clone();
scoreboard.update_team(world, team);
scoreboard.update_team(world, team).await;
context
.source
@@ -931,7 +937,7 @@ impl CommandExecutor for TeamModifyCollisionRuleExecutor {
};
let team_display_name = team.display_name.clone();
scoreboard.update_team(world, team);
scoreboard.update_team(world, team).await;
context
.source

View File

@@ -69,8 +69,8 @@ impl CommandExecutor for SimpleTriggerExecutor {
let new_value = current_value + 1;
let updated_score = ScoreboardScore {
entity_name: Box::leak(player_name.clone().into_boxed_str()),
objective_name: Box::leak(objective_name.to_string().into_boxed_str()),
entity_name: player_name.clone(),
objective_name: objective_name.to_string(),
value: VarInt(new_value),
display_name: None,
number_format: None,
@@ -139,8 +139,8 @@ impl CommandExecutor for AddTriggerExecutor {
let new_value = current_value + value;
let updated_score = ScoreboardScore {
entity_name: Box::leak(player_name.clone().into_boxed_str()),
objective_name: Box::leak(objective_name.to_string().into_boxed_str()),
entity_name: player_name.clone(),
objective_name: objective_name.to_string(),
value: VarInt(new_value),
display_name: None,
number_format: None,
@@ -204,8 +204,8 @@ impl CommandExecutor for SetTriggerExecutor {
}
let updated_score = ScoreboardScore {
entity_name: Box::leak(player_name.clone().into_boxed_str()),
objective_name: Box::leak(objective_name.to_string().into_boxed_str()),
entity_name: player_name.clone(),
objective_name: objective_name.to_string(),
value: VarInt(value),
display_name: None,
number_format: None,

View File

@@ -10,6 +10,8 @@ use std::sync::atomic::{AtomicBool, AtomicI8, AtomicI32, AtomicU8, AtomicU32, Or
use std::sync::{Arc, Weak};
use std::time::{Duration, Instant};
use crate::world::scoreboard::{BedrockScoreboard, Scoreboard};
use advancement::PlayerAdvancement;
use arc_swap::ArcSwap;
use crossbeam::atomic::AtomicCell;
use crossbeam::channel::Receiver;
@@ -37,7 +39,162 @@ use tokio::task::JoinHandle;
use tracing::{debug, warn};
use uuid::Uuid;
use advancement::PlayerAdvancement;
#[derive(Clone, Debug)]
pub enum CustomScoreboard {
Java(Scoreboard),
Bedrock(BedrockScoreboard),
}
impl From<Scoreboard> for CustomScoreboard {
fn from(sb: Scoreboard) -> Self {
Self::Java(sb)
}
}
impl From<BedrockScoreboard> for CustomScoreboard {
fn from(sb: BedrockScoreboard) -> Self {
Self::Bedrock(sb)
}
}
#[derive(Copy, Clone)]
pub struct JavaPlayer<'a>(pub &'a Player);
impl JavaPlayer<'_> {
pub async fn send_packet<C: pumpkin_protocol::ClientPacket + Sync>(&self, packet: &C) {
if let ClientPlatform::Java(client) = self.0.client.as_ref() {
client.enqueue_packet(packet).await;
}
}
pub async fn send_custom_payload(&self, channel: &str, data: &[u8]) {
if let ClientPlatform::Java(java) = self.0.client.as_ref() {
java.enqueue_packet(&CCustomPayload::new(channel, data))
.await;
}
}
pub async fn send_stats(&self) {
self.0.send_stats().await;
}
pub async fn set_scoreboard(&self, scoreboard: Option<Scoreboard>) {
*self.0.custom_scoreboard.lock().await = scoreboard.map(CustomScoreboard::Java);
self.0.send_scoreboard().await;
}
pub async fn reset_scoreboard(&self) {
self.set_scoreboard(None).await;
}
pub async fn get_scoreboard(&self) -> Option<Scoreboard> {
let guard = self.0.custom_scoreboard.lock().await;
if let Some(CustomScoreboard::Java(sb)) = guard.as_ref() {
Some(sb.clone())
} else {
None
}
}
}
#[derive(Copy, Clone)]
pub struct BedrockPlayer<'a>(pub &'a Player);
impl BedrockPlayer<'_> {
pub async fn send_packet<P: pumpkin_protocol::BClientPacket + Sync>(&self, packet: &P) {
if let ClientPlatform::Bedrock(client) = self.0.client.as_ref() {
client.send_game_packet(packet).await;
}
}
pub async fn set_scoreboard(&self, scoreboard: Option<BedrockScoreboard>) {
*self.0.custom_scoreboard.lock().await = scoreboard.map(CustomScoreboard::Bedrock);
self.0.send_scoreboard().await;
}
pub async fn reset_scoreboard(&self) {
self.set_scoreboard(None).await;
}
pub async fn get_scoreboard(&self) -> Option<BedrockScoreboard> {
let guard = self.0.custom_scoreboard.lock().await;
if let Some(CustomScoreboard::Bedrock(sb)) = guard.as_ref() {
Some(sb.clone())
} else {
None
}
}
#[must_use]
pub fn client_data(&self) -> Option<Arc<pumpkin_protocol::bedrock::server::login::ClientData>> {
if let ClientPlatform::Bedrock(client) = self.0.client.as_ref() {
let data = client.client_data.load();
(**data).clone()
} else {
None
}
}
#[must_use]
pub fn device_os(&self) -> Option<i32> {
self.client_data().map(|d| d.device_os)
}
#[must_use]
pub fn device_id(&self) -> Option<String> {
self.client_data().map(|d| d.device_id.clone())
}
#[must_use]
pub fn device_model(&self) -> Option<String> {
self.client_data().map(|d| d.device_model.clone())
}
#[must_use]
pub fn game_version(&self) -> Option<String> {
self.client_data().map(|d| d.game_version.clone())
}
#[must_use]
pub fn language_code(&self) -> Option<String> {
self.client_data().map(|d| d.language_code.clone())
}
#[must_use]
pub fn current_input_mode(&self) -> Option<i32> {
self.client_data().map(|d| d.current_input_mode)
}
#[must_use]
pub fn default_input_mode(&self) -> Option<i32> {
self.client_data().map(|d| d.default_input_mode)
}
#[must_use]
pub fn ui_profile(&self) -> Option<i32> {
self.client_data().map(|d| d.ui_profile)
}
#[must_use]
pub fn gui_scale(&self) -> Option<i32> {
self.client_data().map(|d| d.gui_scale)
}
#[must_use]
pub fn max_view_distance(&self) -> Option<i32> {
self.client_data().map(|d| d.max_view_distance)
}
#[must_use]
pub fn memory_tier(&self) -> Option<i32> {
self.client_data().map(|d| d.memory_tier)
}
#[must_use]
pub fn graphics_mode(&self) -> Option<i32> {
self.client_data().map(|d| d.graphics_mode)
}
}
use pumpkin_data::attributes::Attributes;
use pumpkin_data::block_properties::{BlockProperties, HorizontalFacing};
use pumpkin_data::damage::DamageType;
@@ -594,6 +751,7 @@ pub struct Player {
pub tab_list_listed: AtomicBool,
pub per_player_time: AtomicCell<Option<(u64, bool)>>,
pub per_player_weather: AtomicCell<Option<PlayerWeather>>,
pub custom_scoreboard: Mutex<Option<CustomScoreboard>>,
pub compass_target: AtomicCell<Option<pumpkin_util::math::position::BlockPos>>,
pub respawn_location: AtomicCell<Option<pumpkin_util::math::position::BlockPos>>,
pub hidden_players: Mutex<std::collections::HashSet<uuid::Uuid>>,
@@ -853,6 +1011,7 @@ impl Player {
tab_list_listed: AtomicBool::new(true),
per_player_time: AtomicCell::new(None),
per_player_weather: AtomicCell::new(None),
custom_scoreboard: Mutex::new(None),
compass_target: AtomicCell::new(None),
respawn_location: AtomicCell::new(None),
hidden_players: Mutex::new(std::collections::HashSet::new()),
@@ -2760,14 +2919,16 @@ impl Player {
.await;
}
pub async fn set_player_time(&self, world: &World, time: u64, relative: bool) {
pub async fn set_player_time(&self, time: u64, relative: bool) {
let world = self.world();
self.per_player_time.store(Some((time, relative)));
self.send_time(world).await;
self.send_time(&world).await;
}
pub async fn reset_player_time(&self, world: &World) {
pub async fn reset_player_time(&self) {
let world = self.world();
self.per_player_time.store(None);
self.send_time(world).await;
self.send_time(&world).await;
}
pub fn get_player_time(&self) -> Option<u64> {
@@ -2790,6 +2951,66 @@ impl Player {
self.per_player_weather.load()
}
pub async fn send_editioned<
J: pumpkin_protocol::ClientPacket + Sync,
B: pumpkin_protocol::BClientPacket + Sync,
>(
&self,
je_packet: &J,
be_packet: &B,
) {
match self.client.as_ref() {
ClientPlatform::Java(client) => client.enqueue_packet(je_packet).await,
ClientPlatform::Bedrock(client) => client.enqueue_packet(be_packet).await,
}
}
pub async fn send_client_packet<C: pumpkin_protocol::ClientPacket + Sync>(&self, packet: &C) {
if let ClientPlatform::Java(client) = self.client.as_ref() {
client.enqueue_packet(packet).await;
}
}
#[must_use]
pub fn as_java(&self) -> Option<JavaPlayer<'_>> {
matches!(self.client.as_ref(), ClientPlatform::Java(_)).then(|| JavaPlayer(self))
}
#[must_use]
pub fn as_bedrock(&self) -> Option<BedrockPlayer<'_>> {
matches!(self.client.as_ref(), ClientPlatform::Bedrock(_)).then(|| BedrockPlayer(self))
}
pub async fn reset_scoreboard(&self) {
*self.custom_scoreboard.lock().await = None;
self.send_scoreboard().await;
}
pub async fn send_scoreboard(&self) {
let guard = self.custom_scoreboard.lock().await;
match guard.as_ref() {
Some(CustomScoreboard::Java(custom))
if matches!(self.client.as_ref(), ClientPlatform::Java(_)) =>
{
custom.send_to_player(self).await;
}
Some(CustomScoreboard::Bedrock(custom))
if matches!(self.client.as_ref(), ClientPlatform::Bedrock(_)) =>
{
custom.send_to_player(self).await;
}
_ => {
drop(guard);
self.world()
.scoreboard
.lock()
.await
.send_to_player(self)
.await;
}
}
}
pub async fn set_compass_target(&self, pos: pumpkin_util::math::position::BlockPos) {
use pumpkin_protocol::java::client::play::CPlayerSpawnPosition;
self.compass_target.store(Some(pos));
@@ -2822,6 +3043,85 @@ impl Player {
!self.hidden_players.lock().await.contains(other_id)
}
pub async fn can_see_player(&self, other_id: &uuid::Uuid) -> bool {
self.can_see(other_id).await
}
// --- Experience & Leveling API ---
pub async fn add_experience(self: &Arc<Self>, points: i32) {
self.add_experience_points(points).await;
}
pub async fn add_levels(&self, levels: i32) {
self.add_experience_levels(levels).await;
}
pub fn get_experience_level(&self) -> i32 {
self.experience_level.load(Ordering::Relaxed)
}
pub fn get_experience_progress(&self) -> f32 {
self.experience_progress.load()
}
pub fn get_total_experience(&self) -> i32 {
self.experience_points.load(Ordering::Relaxed)
}
pub async fn set_experience_progress(&self, progress: f32) {
let level = self.get_experience_level();
let max_points = experience::points_in_level(level);
let points = (progress.clamp(0.0, 1.0) * max_points as f32) as i32;
self.set_experience(level, progress, points).await;
}
pub async fn set_total_experience(&self, points: i32) {
self.set_experience_points(points).await;
}
// --- Item Cooldown System ---
pub async fn set_item_cooldown(&self, item_id: &str, ticks: i32) {
self.start_cooldown(item_id.to_string(), ticks).await;
}
pub async fn get_item_cooldown(&self, item_id: &str) -> Option<i32> {
let cooldowns = self.item_cooldowns.lock().await;
if let Some(cooldown) = cooldowns.get(item_id) {
let current_tick = self.tick_counter.load(Ordering::Relaxed);
let elapsed = current_tick - cooldown.start_tick;
if elapsed < cooldown.duration {
return Some(cooldown.duration - elapsed);
}
}
None
}
pub async fn has_item_cooldown(&self, item_id: &str) -> bool {
self.is_on_cooldown(item_id).await
}
// --- Tab List & Display Names ---
pub fn set_tab_list_ping(&self, latency_ms: i32) {
self.set_tab_list_latency(latency_ms);
}
// --- Hunger & Saturation Aliases ---
pub fn get_food_saturation(&self) -> f32 {
self.get_saturation()
}
pub async fn set_food_saturation(&self, saturation: f32) {
self.set_saturation(saturation).await;
}
pub fn get_food_exhaustion(&self) -> f32 {
self.get_exhaustion()
}
pub async fn set_food_exhaustion(&self, exhaustion: f32) {
self.set_exhaustion(exhaustion).await;
}
pub async fn get_target_block(
&self,
world: &Arc<World>,
@@ -3653,14 +3953,6 @@ impl Player {
.await;
}
/// Sends a custom payload packet to this player (Java edition only).
pub async fn send_custom_payload(&self, channel: &str, data: &[u8]) {
if let ClientPlatform::Java(java) = self.client.as_ref() {
java.enqueue_packet(&CCustomPayload::new(channel, data))
.await;
}
}
pub async fn drop_item(&self, item_stack: ItemStack) {
self.increment_stat(
statistics::StatisticCategory::Dropped,

View File

@@ -45,7 +45,15 @@ pub type EntityResource = WasmResource<Arc<dyn EntityBase>>;
pub type WorldResource = WasmResource<Arc<World>>;
pub type ChunkResource = WasmResource<(Arc<World>, Weak<pumpkin_world::chunk::ChunkData>)>;
pub type WorldBorderResource = WasmResource<Arc<World>>;
pub type ScoreboardResource = WasmResource<Arc<World>>;
#[derive(Clone)]
pub enum ScoreboardProvider {
World(Arc<World>),
Player(Arc<Player>),
}
pub type ScoreboardResource = WasmResource<ScoreboardProvider>;
pub type BedrockScoreboardResource = WasmResource<Arc<Player>>;
pub type GuiResource = WasmResource<Arc<Mutex<PluginGui>>>;
pub type BossBarResource = WasmResource<
Arc<Mutex<crate::plugin::loader::wasm::wasm_host::wit::v0_1::boss_bar::PluginBossBar>>,
@@ -176,12 +184,22 @@ impl PluginHostState {
pub fn add_scoreboard<T>(
&mut self,
provider: Arc<World>,
provider: ScoreboardProvider,
) -> wasmtime::Result<wasmtime::component::Resource<T>> {
let resource = self.resource_table.push(ScoreboardResource { provider })?;
Ok(wasmtime::component::Resource::new_own(resource.rep()))
}
pub fn add_bedrock_scoreboard<T>(
&mut self,
provider: Arc<Player>,
) -> wasmtime::Result<wasmtime::component::Resource<T>> {
let resource = self
.resource_table
.push(BedrockScoreboardResource { provider })?;
Ok(wasmtime::component::Resource::new_own(resource.rep()))
}
pub fn add_gui<T>(
&mut self,
provider: Arc<Mutex<PluginGui>>,

View File

@@ -1059,21 +1059,13 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
relative: bool,
) -> wasmtime::Result<()> {
let player = player_from_resource(self, &player)?;
player
.set_player_time(
&player.living_entity.entity.world.load_full(),
time,
relative,
)
.await;
player.set_player_time(time, relative).await;
Ok(())
}
async fn reset_player_time(&mut self, player: Resource<Player>) -> wasmtime::Result<()> {
let player = player_from_resource(self, &player)?;
player
.reset_player_time(&player.living_entity.entity.world.load_full())
.await;
player.reset_player_time().await;
Ok(())
}
@@ -1222,6 +1214,55 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
Ok(player.can_see(&other.gameprofile.id).await)
}
async fn can_see_player(
&mut self,
player: Resource<Player>,
other: Resource<Player>,
) -> wasmtime::Result<bool> {
let player = player_from_resource(self, &player)?;
let other = player_from_resource(self, &other)?;
Ok(player.can_see(&other.gameprofile.id).await)
}
async fn set_tab_list_ping(
&mut self,
player: Resource<Player>,
latency_ms: i32,
) -> wasmtime::Result<()> {
let player = player_from_resource(self, &player)?;
player.set_tab_list_ping(latency_ms);
Ok(())
}
async fn set_item_cooldown(
&mut self,
player: Resource<Player>,
item_id: String,
ticks: i32,
) -> wasmtime::Result<()> {
let player = player_from_resource(self, &player)?;
player.set_item_cooldown(&item_id, ticks).await;
Ok(())
}
async fn get_item_cooldown(
&mut self,
player: Resource<Player>,
item_id: String,
) -> wasmtime::Result<Option<i32>> {
let player = player_from_resource(self, &player)?;
Ok(player.get_item_cooldown(&item_id).await)
}
async fn has_item_cooldown(
&mut self,
player: Resource<Player>,
item_id: String,
) -> wasmtime::Result<bool> {
let player = player_from_resource(self, &player)?;
Ok(player.has_item_cooldown(&item_id).await)
}
async fn get_target_block(
&mut self,
player: Resource<Player>,
@@ -2198,6 +2239,39 @@ impl pumpkin::plugin::player::HostJavaPlayer for PluginHostState {
Ok(())
}
async fn get_scoreboard(
&mut self,
player_res: Resource<pumpkin::plugin::player::JavaPlayer>,
) -> wasmtime::Result<Resource<pumpkin::plugin::scoreboard::Scoreboard>> {
let player = self
.resource_table
.get::<crate::plugin::loader::wasm::wasm_host::state::JavaPlayerResource>(
&Resource::new_own(player_res.rep()),
)
.map_err(|_| wasmtime::Error::msg("invalid java-player resource handle"))?
.provider
.clone();
self.add_scoreboard(
crate::plugin::loader::wasm::wasm_host::state::ScoreboardProvider::Player(player),
)
}
async fn reset_scoreboard(
&mut self,
player_res: Resource<pumpkin::plugin::player::JavaPlayer>,
) -> wasmtime::Result<()> {
let player = self
.resource_table
.get::<crate::plugin::loader::wasm::wasm_host::state::JavaPlayerResource>(
&Resource::new_own(player_res.rep()),
)
.map_err(|_| wasmtime::Error::msg("invalid java-player resource handle"))?
.provider
.clone();
player.reset_scoreboard().await;
Ok(())
}
async fn drop(
&mut self,
rep: Resource<pumpkin::plugin::player::JavaPlayer>,
@@ -2543,6 +2617,37 @@ impl pumpkin::plugin::player::HostBedrockPlayer for PluginHostState {
}
}
async fn get_scoreboard(
&mut self,
player_res: Resource<pumpkin::plugin::player::BedrockPlayer>,
) -> wasmtime::Result<Resource<pumpkin::plugin::scoreboard::BedrockScoreboard>> {
let player = self
.resource_table
.get::<crate::plugin::loader::wasm::wasm_host::state::BedrockPlayerResource>(
&Resource::new_own(player_res.rep()),
)
.map_err(|_| wasmtime::Error::msg("invalid bedrock-player resource handle"))?
.provider
.clone();
self.add_bedrock_scoreboard(player)
}
async fn reset_scoreboard(
&mut self,
player_res: Resource<pumpkin::plugin::player::BedrockPlayer>,
) -> wasmtime::Result<()> {
let player = self
.resource_table
.get::<crate::plugin::loader::wasm::wasm_host::state::BedrockPlayerResource>(
&Resource::new_own(player_res.rep()),
)
.map_err(|_| wasmtime::Error::msg("invalid bedrock-player resource handle"))?
.provider
.clone();
player.reset_scoreboard().await;
Ok(())
}
async fn drop(
&mut self,
rep: Resource<pumpkin::plugin::player::BedrockPlayer>,

View File

@@ -1,11 +1,12 @@
use wasmtime::component::Resource;
use crate::plugin::loader::wasm::wasm_host::{
state::{PluginHostState, ScoreboardResource},
state::{BedrockScoreboardResource, PluginHostState, ScoreboardProvider, ScoreboardResource},
wit::v0_1::pumpkin::{
self,
plugin::scoreboard::{
self, CollisionRule, DisplaySlot, NametagVisibility, RenderType, TeamSettings,
self, CollisionRule, DisplaySlot, HostBedrockScoreboard, NametagVisibility, RenderType,
TeamSettings,
},
},
};
@@ -21,6 +22,15 @@ impl PluginHostState {
.get::<ScoreboardResource>(&Resource::new_own(res.rep()))
.map_err(wasmtime::Error::from)
}
fn get_bedrock_scoreboard_res(
&self,
res: &Resource<scoreboard::BedrockScoreboard>,
) -> wasmtime::Result<&BedrockScoreboardResource> {
self.resource_table
.get::<BedrockScoreboardResource>(&Resource::new_own(res.rep()))
.map_err(wasmtime::Error::from)
}
}
impl scoreboard::Host for PluginHostState {}
@@ -33,7 +43,7 @@ impl scoreboard::HostScoreboard for PluginHostState {
display_name: Resource<pumpkin::plugin::text::TextComponent>,
render_type: RenderType,
) -> wasmtime::Result<()> {
let world = self.get_scoreboard_res(&res)?.provider.clone();
let provider = self.get_scoreboard_res(&res)?.provider.clone();
let display_name = self.get_text_provider(&display_name)?;
let rt = match render_type {
@@ -41,19 +51,80 @@ impl scoreboard::HostScoreboard for PluginHostState {
RenderType::Hearts => pumpkin_protocol::java::client::play::RenderType::Hearts,
};
let objective = ScoreboardObjective::new(
Box::leak(name.into_boxed_str()),
display_name,
rt,
None,
"dummy",
);
world
.scoreboard
.lock()
.await
.add_objective(&world, objective)
.await;
let objective = ScoreboardObjective::new(name, display_name, rt, None, "dummy");
match provider {
ScoreboardProvider::World(world) => {
world
.scoreboard
.lock()
.await
.add_objective(world.as_ref(), objective)
.await;
}
ScoreboardProvider::Player(player) => {
let mut custom_guard = player.custom_scoreboard.lock().await;
if !matches!(
*custom_guard,
Some(crate::entity::player::CustomScoreboard::Java(_))
) {
*custom_guard = Some(crate::entity::player::CustomScoreboard::Java(
crate::world::scoreboard::Scoreboard::default(),
));
}
if let Some(crate::entity::player::CustomScoreboard::Java(sb)) =
custom_guard.as_mut()
{
sb.add_objective(player.as_ref(), objective).await;
}
}
}
Ok(())
}
async fn update_objective(
&mut self,
res: Resource<scoreboard::Scoreboard>,
name: String,
display_name: Resource<pumpkin::plugin::text::TextComponent>,
render_type: RenderType,
) -> wasmtime::Result<()> {
let provider = self.get_scoreboard_res(&res)?.provider.clone();
let display_name = self.get_text_provider(&display_name)?;
let rt = match render_type {
RenderType::Integer => pumpkin_protocol::java::client::play::RenderType::Integer,
RenderType::Hearts => pumpkin_protocol::java::client::play::RenderType::Hearts,
};
let objective = ScoreboardObjective::new(name, display_name, rt, None, "dummy");
match provider {
ScoreboardProvider::World(world) => {
world
.scoreboard
.lock()
.await
.update_objective(world.as_ref(), objective)
.await;
}
ScoreboardProvider::Player(player) => {
let mut custom_guard = player.custom_scoreboard.lock().await;
if !matches!(
*custom_guard,
Some(crate::entity::player::CustomScoreboard::Java(_))
) {
*custom_guard = Some(crate::entity::player::CustomScoreboard::Java(
crate::world::scoreboard::Scoreboard::default(),
));
}
if let Some(crate::entity::player::CustomScoreboard::Java(sb)) =
custom_guard.as_mut()
{
sb.update_objective(player.as_ref(), objective).await;
}
}
}
Ok(())
}
@@ -62,13 +133,25 @@ impl scoreboard::HostScoreboard for PluginHostState {
res: Resource<scoreboard::Scoreboard>,
name: String,
) -> wasmtime::Result<()> {
let world = self.get_scoreboard_res(&res)?.provider.clone();
world
.scoreboard
.lock()
.await
.remove_objective(&world, &name)
.await;
let provider = self.get_scoreboard_res(&res)?.provider.clone();
match provider {
ScoreboardProvider::World(world) => {
world
.scoreboard
.lock()
.await
.remove_objective(world.as_ref(), &name)
.await;
}
ScoreboardProvider::Player(player) => {
let mut custom_guard = player.custom_scoreboard.lock().await;
if let Some(crate::entity::player::CustomScoreboard::Java(sb)) =
custom_guard.as_mut()
{
sb.remove_objective(player.as_ref(), &name).await;
}
}
}
Ok(())
}
@@ -78,65 +161,65 @@ impl scoreboard::HostScoreboard for PluginHostState {
slot: DisplaySlot,
objective_name: String,
) -> wasmtime::Result<()> {
let world = self.get_scoreboard_res(&res)?.provider.clone();
let slot = match slot {
DisplaySlot::PlayerList => pumpkin_data::scoreboard::ScoreboardDisplaySlot::List,
DisplaySlot::Sidebar => pumpkin_data::scoreboard::ScoreboardDisplaySlot::Sidebar,
DisplaySlot::BelowName => pumpkin_data::scoreboard::ScoreboardDisplaySlot::BelowName,
DisplaySlot::SidebarTeamBlack => {
pumpkin_data::scoreboard::ScoreboardDisplaySlot::TeamBlack
}
DisplaySlot::SidebarTeamDarkBlue => {
pumpkin_data::scoreboard::ScoreboardDisplaySlot::TeamDarkBlue
}
DisplaySlot::SidebarTeamDarkGreen => {
pumpkin_data::scoreboard::ScoreboardDisplaySlot::TeamDarkGreen
}
DisplaySlot::SidebarTeamDarkAqua => {
pumpkin_data::scoreboard::ScoreboardDisplaySlot::TeamDarkAqua
}
DisplaySlot::SidebarTeamDarkRed => {
pumpkin_data::scoreboard::ScoreboardDisplaySlot::TeamDarkRed
}
DisplaySlot::SidebarTeamDarkPurple => {
pumpkin_data::scoreboard::ScoreboardDisplaySlot::TeamDarkPurple
}
DisplaySlot::SidebarTeamGold => {
pumpkin_data::scoreboard::ScoreboardDisplaySlot::TeamGold
}
DisplaySlot::SidebarTeamGray => {
pumpkin_data::scoreboard::ScoreboardDisplaySlot::TeamGray
}
DisplaySlot::SidebarTeamDarkGray => {
pumpkin_data::scoreboard::ScoreboardDisplaySlot::TeamDarkGray
}
DisplaySlot::SidebarTeamBlue => {
pumpkin_data::scoreboard::ScoreboardDisplaySlot::TeamBlue
}
DisplaySlot::SidebarTeamGreen => {
pumpkin_data::scoreboard::ScoreboardDisplaySlot::TeamGreen
}
DisplaySlot::SidebarTeamAqua => {
pumpkin_data::scoreboard::ScoreboardDisplaySlot::TeamAqua
}
DisplaySlot::SidebarTeamRed => pumpkin_data::scoreboard::ScoreboardDisplaySlot::TeamRed,
DisplaySlot::SidebarTeamLightPurple => {
pumpkin_data::scoreboard::ScoreboardDisplaySlot::TeamLightPurple
}
DisplaySlot::SidebarTeamYellow => {
pumpkin_data::scoreboard::ScoreboardDisplaySlot::TeamYellow
}
DisplaySlot::SidebarTeamWhite => {
pumpkin_data::scoreboard::ScoreboardDisplaySlot::TeamWhite
}
};
let provider = self.get_scoreboard_res(&res)?.provider.clone();
let slot = map_display_slot(slot);
world
.scoreboard
.lock()
.await
.set_display_objective(&world, slot, &objective_name)
.await;
match provider {
ScoreboardProvider::World(world) => {
world
.scoreboard
.lock()
.await
.set_display_objective(world.as_ref(), slot, Some(&objective_name))
.await;
}
ScoreboardProvider::Player(player) => {
let mut custom_guard = player.custom_scoreboard.lock().await;
if !matches!(
*custom_guard,
Some(crate::entity::player::CustomScoreboard::Java(_))
) {
*custom_guard = Some(crate::entity::player::CustomScoreboard::Java(
crate::world::scoreboard::Scoreboard::default(),
));
}
if let Some(crate::entity::player::CustomScoreboard::Java(sb)) =
custom_guard.as_mut()
{
sb.set_display_objective(player.as_ref(), slot, Some(&objective_name))
.await;
}
}
}
Ok(())
}
async fn clear_display_slot(
&mut self,
res: Resource<scoreboard::Scoreboard>,
slot: DisplaySlot,
) -> wasmtime::Result<()> {
let provider = self.get_scoreboard_res(&res)?.provider.clone();
let slot = map_display_slot(slot);
match provider {
ScoreboardProvider::World(world) => {
world
.scoreboard
.lock()
.await
.clear_display_objective(world.as_ref(), slot)
.await;
}
ScoreboardProvider::Player(player) => {
let mut custom_guard = player.custom_scoreboard.lock().await;
if let Some(crate::entity::player::CustomScoreboard::Java(sb)) =
custom_guard.as_mut()
{
sb.clear_display_objective(player.as_ref(), slot).await;
}
}
}
Ok(())
}
@@ -147,36 +230,129 @@ impl scoreboard::HostScoreboard for PluginHostState {
objective_name: String,
value: i32,
) -> wasmtime::Result<()> {
let world = self.get_scoreboard_res(&res)?.provider.clone();
let score = ScoreboardScore::new(
Box::leak(entity_name.into_boxed_str()),
Box::leak(objective_name.into_boxed_str()),
VarInt(value),
None,
None,
);
world
.scoreboard
.lock()
.await
.update_score(&world, score)
.await;
let provider = self.get_scoreboard_res(&res)?.provider.clone();
let score = ScoreboardScore::new(entity_name, objective_name, VarInt(value), None, None);
match provider {
ScoreboardProvider::World(world) => {
world
.scoreboard
.lock()
.await
.update_score(world.as_ref(), score)
.await;
}
ScoreboardProvider::Player(player) => {
let mut custom_guard = player.custom_scoreboard.lock().await;
if !matches!(
*custom_guard,
Some(crate::entity::player::CustomScoreboard::Java(_))
) {
*custom_guard = Some(crate::entity::player::CustomScoreboard::Java(
crate::world::scoreboard::Scoreboard::default(),
));
}
if let Some(crate::entity::player::CustomScoreboard::Java(sb)) =
custom_guard.as_mut()
{
sb.update_score(player.as_ref(), score).await;
}
}
}
Ok(())
}
async fn add_score(
&mut self,
res: Resource<scoreboard::Scoreboard>,
entity_name: String,
objective_name: String,
delta: i32,
) -> wasmtime::Result<i32> {
let provider = self.get_scoreboard_res(&res)?.provider.clone();
let new_val = match provider {
ScoreboardProvider::World(world) => {
world
.scoreboard
.lock()
.await
.add_score(world.as_ref(), entity_name, objective_name, delta)
.await
}
ScoreboardProvider::Player(player) => {
let mut custom_guard = player.custom_scoreboard.lock().await;
if !matches!(
*custom_guard,
Some(crate::entity::player::CustomScoreboard::Java(_))
) {
*custom_guard = Some(crate::entity::player::CustomScoreboard::Java(
crate::world::scoreboard::Scoreboard::default(),
));
}
let Some(crate::entity::player::CustomScoreboard::Java(sb)) = custom_guard.as_mut()
else {
return Err(wasmtime::Error::msg("Invalid scoreboard state"));
};
sb.add_score(player.as_ref(), entity_name, objective_name, delta)
.await
}
};
Ok(new_val)
}
async fn remove_score(
&mut self,
res: Resource<scoreboard::Scoreboard>,
entity_name: String,
objective_name: String,
) -> wasmtime::Result<()> {
let world = self.get_scoreboard_res(&res)?.provider.clone();
world
.scoreboard
.lock()
.await
.remove_score(&world, &entity_name, &objective_name)
.await;
let provider = self.get_scoreboard_res(&res)?.provider.clone();
match provider {
ScoreboardProvider::World(world) => {
world
.scoreboard
.lock()
.await
.remove_score(world.as_ref(), &entity_name, &objective_name)
.await;
}
ScoreboardProvider::Player(player) => {
let mut custom_guard = player.custom_scoreboard.lock().await;
if let Some(crate::entity::player::CustomScoreboard::Java(sb)) =
custom_guard.as_mut()
{
sb.remove_score(player.as_ref(), &entity_name, &objective_name)
.await;
}
}
}
Ok(())
}
async fn reset_entity_scores(
&mut self,
res: Resource<scoreboard::Scoreboard>,
entity_name: String,
) -> wasmtime::Result<()> {
let provider = self.get_scoreboard_res(&res)?.provider.clone();
match provider {
ScoreboardProvider::World(world) => {
world
.scoreboard
.lock()
.await
.reset_scores_for_entity(world.as_ref(), &entity_name)
.await;
}
ScoreboardProvider::Player(player) => {
let mut custom_guard = player.custom_scoreboard.lock().await;
if let Some(crate::entity::player::CustomScoreboard::Java(sb)) =
custom_guard.as_mut()
{
sb.reset_scores_for_entity(player.as_ref(), &entity_name)
.await;
}
}
}
Ok(())
}
@@ -186,9 +362,34 @@ impl scoreboard::HostScoreboard for PluginHostState {
name: String,
settings: TeamSettings,
) -> wasmtime::Result<()> {
let world = self.get_scoreboard_res(&res)?.provider.clone();
let provider = self.get_scoreboard_res(&res)?.provider.clone();
let team = map_team_settings(name, &settings, self)?;
world.scoreboard.lock().await.add_team(&world, team);
match provider {
ScoreboardProvider::World(world) => {
world
.scoreboard
.lock()
.await
.add_team(world.as_ref(), team)
.await;
}
ScoreboardProvider::Player(player) => {
let mut custom_guard = player.custom_scoreboard.lock().await;
if !matches!(
*custom_guard,
Some(crate::entity::player::CustomScoreboard::Java(_))
) {
*custom_guard = Some(crate::entity::player::CustomScoreboard::Java(
crate::world::scoreboard::Scoreboard::default(),
));
}
if let Some(crate::entity::player::CustomScoreboard::Java(sb)) =
custom_guard.as_mut()
{
sb.add_team(player.as_ref(), team).await;
}
}
}
Ok(())
}
@@ -197,8 +398,25 @@ impl scoreboard::HostScoreboard for PluginHostState {
res: Resource<scoreboard::Scoreboard>,
name: String,
) -> wasmtime::Result<()> {
let world = self.get_scoreboard_res(&res)?.provider.clone();
world.scoreboard.lock().await.remove_team(&world, &name);
let provider = self.get_scoreboard_res(&res)?.provider.clone();
match provider {
ScoreboardProvider::World(world) => {
world
.scoreboard
.lock()
.await
.remove_team(world.as_ref(), &name)
.await;
}
ScoreboardProvider::Player(player) => {
let mut custom_guard = player.custom_scoreboard.lock().await;
if let Some(crate::entity::player::CustomScoreboard::Java(sb)) =
custom_guard.as_mut()
{
sb.remove_team(player.as_ref(), &name).await;
}
}
}
Ok(())
}
@@ -208,9 +426,26 @@ impl scoreboard::HostScoreboard for PluginHostState {
name: String,
settings: TeamSettings,
) -> wasmtime::Result<()> {
let world = self.get_scoreboard_res(&res)?.provider.clone();
let provider = self.get_scoreboard_res(&res)?.provider.clone();
let team = map_team_settings(name, &settings, self)?;
world.scoreboard.lock().await.update_team(&world, team);
match provider {
ScoreboardProvider::World(world) => {
world
.scoreboard
.lock()
.await
.update_team(world.as_ref(), team)
.await;
}
ScoreboardProvider::Player(player) => {
let mut custom_guard = player.custom_scoreboard.lock().await;
if let Some(crate::entity::player::CustomScoreboard::Java(sb)) =
custom_guard.as_mut()
{
sb.update_team(player.as_ref(), team).await;
}
}
}
Ok(())
}
@@ -220,12 +455,26 @@ impl scoreboard::HostScoreboard for PluginHostState {
team_name: String,
player_name: String,
) -> wasmtime::Result<()> {
let world = self.get_scoreboard_res(&res)?.provider.clone();
world
.scoreboard
.lock()
.await
.add_player_to_team(&world, &team_name, player_name);
let provider = self.get_scoreboard_res(&res)?.provider.clone();
match provider {
ScoreboardProvider::World(world) => {
world
.scoreboard
.lock()
.await
.add_player_to_team(world.as_ref(), &team_name, player_name)
.await;
}
ScoreboardProvider::Player(player) => {
let mut custom_guard = player.custom_scoreboard.lock().await;
if let Some(crate::entity::player::CustomScoreboard::Java(sb)) =
custom_guard.as_mut()
{
sb.add_player_to_team(player.as_ref(), &team_name, player_name)
.await;
}
}
}
Ok(())
}
@@ -235,12 +484,53 @@ impl scoreboard::HostScoreboard for PluginHostState {
team_name: String,
player_name: String,
) -> wasmtime::Result<()> {
let world = self.get_scoreboard_res(&res)?.provider.clone();
world
.scoreboard
.lock()
.await
.remove_player_from_team(&world, &team_name, &player_name);
let provider = self.get_scoreboard_res(&res)?.provider.clone();
match provider {
ScoreboardProvider::World(world) => {
world
.scoreboard
.lock()
.await
.remove_player_from_team(world.as_ref(), &team_name, &player_name)
.await;
}
ScoreboardProvider::Player(player) => {
let mut custom_guard = player.custom_scoreboard.lock().await;
if let Some(crate::entity::player::CustomScoreboard::Java(sb)) =
custom_guard.as_mut()
{
sb.remove_player_from_team(player.as_ref(), &team_name, &player_name)
.await;
}
}
}
Ok(())
}
async fn clear_team_players(
&mut self,
res: Resource<scoreboard::Scoreboard>,
team_name: String,
) -> wasmtime::Result<()> {
let provider = self.get_scoreboard_res(&res)?.provider.clone();
match provider {
ScoreboardProvider::World(world) => {
world
.scoreboard
.lock()
.await
.clear_team_players(world.as_ref(), &team_name)
.await;
}
ScoreboardProvider::Player(player) => {
let mut custom_guard = player.custom_scoreboard.lock().await;
if let Some(crate::entity::player::CustomScoreboard::Java(sb)) =
custom_guard.as_mut()
{
sb.clear_team_players(player.as_ref(), &team_name).await;
}
}
}
Ok(())
}
@@ -252,6 +542,46 @@ impl scoreboard::HostScoreboard for PluginHostState {
}
}
const fn map_display_slot(slot: DisplaySlot) -> pumpkin_data::scoreboard::ScoreboardDisplaySlot {
match slot {
DisplaySlot::PlayerList => pumpkin_data::scoreboard::ScoreboardDisplaySlot::List,
DisplaySlot::Sidebar => pumpkin_data::scoreboard::ScoreboardDisplaySlot::Sidebar,
DisplaySlot::BelowName => pumpkin_data::scoreboard::ScoreboardDisplaySlot::BelowName,
DisplaySlot::SidebarTeamBlack => pumpkin_data::scoreboard::ScoreboardDisplaySlot::TeamBlack,
DisplaySlot::SidebarTeamDarkBlue => {
pumpkin_data::scoreboard::ScoreboardDisplaySlot::TeamDarkBlue
}
DisplaySlot::SidebarTeamDarkGreen => {
pumpkin_data::scoreboard::ScoreboardDisplaySlot::TeamDarkGreen
}
DisplaySlot::SidebarTeamDarkAqua => {
pumpkin_data::scoreboard::ScoreboardDisplaySlot::TeamDarkAqua
}
DisplaySlot::SidebarTeamDarkRed => {
pumpkin_data::scoreboard::ScoreboardDisplaySlot::TeamDarkRed
}
DisplaySlot::SidebarTeamDarkPurple => {
pumpkin_data::scoreboard::ScoreboardDisplaySlot::TeamDarkPurple
}
DisplaySlot::SidebarTeamGold => pumpkin_data::scoreboard::ScoreboardDisplaySlot::TeamGold,
DisplaySlot::SidebarTeamGray => pumpkin_data::scoreboard::ScoreboardDisplaySlot::TeamGray,
DisplaySlot::SidebarTeamDarkGray => {
pumpkin_data::scoreboard::ScoreboardDisplaySlot::TeamDarkGray
}
DisplaySlot::SidebarTeamBlue => pumpkin_data::scoreboard::ScoreboardDisplaySlot::TeamBlue,
DisplaySlot::SidebarTeamGreen => pumpkin_data::scoreboard::ScoreboardDisplaySlot::TeamGreen,
DisplaySlot::SidebarTeamAqua => pumpkin_data::scoreboard::ScoreboardDisplaySlot::TeamAqua,
DisplaySlot::SidebarTeamRed => pumpkin_data::scoreboard::ScoreboardDisplaySlot::TeamRed,
DisplaySlot::SidebarTeamLightPurple => {
pumpkin_data::scoreboard::ScoreboardDisplaySlot::TeamLightPurple
}
DisplaySlot::SidebarTeamYellow => {
pumpkin_data::scoreboard::ScoreboardDisplaySlot::TeamYellow
}
DisplaySlot::SidebarTeamWhite => pumpkin_data::scoreboard::ScoreboardDisplaySlot::TeamWhite,
}
}
fn map_team_settings(
name: String,
settings: &TeamSettings,
@@ -336,3 +666,247 @@ const fn map_named_color(
pumpkin::plugin::common::NamedColor::White => pumpkin_util::text::color::NamedColor::White,
}
}
impl HostBedrockScoreboard for PluginHostState {
async fn add_objective(
&mut self,
res: Resource<scoreboard::BedrockScoreboard>,
name: String,
display_name: String,
sort_order: scoreboard::BedrockSortOrder,
) -> wasmtime::Result<()> {
let player = self.get_bedrock_scoreboard_res(&res)?.provider.clone();
let mut custom_guard = player.custom_scoreboard.lock().await;
if !matches!(
*custom_guard,
Some(crate::entity::player::CustomScoreboard::Bedrock(_))
) {
*custom_guard = Some(crate::entity::player::CustomScoreboard::Bedrock(
crate::world::scoreboard::BedrockScoreboard::default(),
));
}
let Some(crate::entity::player::CustomScoreboard::Bedrock(sb)) = custom_guard.as_mut()
else {
return Err(wasmtime::Error::msg("Invalid scoreboard state"));
};
sb.add_objective(
player.as_ref(),
crate::world::scoreboard::BedrockObjective {
name,
display_name,
sort_order: match sort_order {
scoreboard::BedrockSortOrder::Ascending => {
crate::world::scoreboard::BedrockSortOrder::Ascending
}
scoreboard::BedrockSortOrder::Descending => {
crate::world::scoreboard::BedrockSortOrder::Descending
}
},
},
)
.await;
Ok(())
}
async fn update_objective(
&mut self,
res: Resource<scoreboard::BedrockScoreboard>,
name: String,
display_name: String,
sort_order: scoreboard::BedrockSortOrder,
) -> wasmtime::Result<()> {
let player = self.get_bedrock_scoreboard_res(&res)?.provider.clone();
let mut custom_guard = player.custom_scoreboard.lock().await;
if !matches!(
*custom_guard,
Some(crate::entity::player::CustomScoreboard::Bedrock(_))
) {
*custom_guard = Some(crate::entity::player::CustomScoreboard::Bedrock(
crate::world::scoreboard::BedrockScoreboard::default(),
));
}
let Some(crate::entity::player::CustomScoreboard::Bedrock(sb)) = custom_guard.as_mut()
else {
return Err(wasmtime::Error::msg("Invalid scoreboard state"));
};
sb.update_objective(
player.as_ref(),
crate::world::scoreboard::BedrockObjective {
name,
display_name,
sort_order: match sort_order {
scoreboard::BedrockSortOrder::Ascending => {
crate::world::scoreboard::BedrockSortOrder::Ascending
}
scoreboard::BedrockSortOrder::Descending => {
crate::world::scoreboard::BedrockSortOrder::Descending
}
},
},
)
.await;
Ok(())
}
async fn remove_objective(
&mut self,
res: Resource<scoreboard::BedrockScoreboard>,
name: String,
) -> wasmtime::Result<()> {
let player = self.get_bedrock_scoreboard_res(&res)?.provider.clone();
let mut custom_guard = player.custom_scoreboard.lock().await;
if let Some(crate::entity::player::CustomScoreboard::Bedrock(sb)) = custom_guard.as_mut() {
sb.remove_objective(player.as_ref(), &name).await;
}
Ok(())
}
async fn set_display_slot(
&mut self,
res: Resource<scoreboard::BedrockScoreboard>,
slot: scoreboard::BedrockDisplaySlot,
objective_name: String,
) -> wasmtime::Result<()> {
let player = self.get_bedrock_scoreboard_res(&res)?.provider.clone();
let mut custom_guard = player.custom_scoreboard.lock().await;
if !matches!(
*custom_guard,
Some(crate::entity::player::CustomScoreboard::Bedrock(_))
) {
*custom_guard = Some(crate::entity::player::CustomScoreboard::Bedrock(
crate::world::scoreboard::BedrockScoreboard::default(),
));
}
let Some(crate::entity::player::CustomScoreboard::Bedrock(sb)) = custom_guard.as_mut()
else {
return Err(wasmtime::Error::msg("Invalid scoreboard state"));
};
let b_slot = match slot {
scoreboard::BedrockDisplaySlot::PlayerList => {
crate::world::scoreboard::BedrockDisplaySlot::PlayerList
}
scoreboard::BedrockDisplaySlot::Sidebar => {
crate::world::scoreboard::BedrockDisplaySlot::Sidebar
}
scoreboard::BedrockDisplaySlot::BelowName => {
crate::world::scoreboard::BedrockDisplaySlot::BelowName
}
};
sb.set_display_objective(player.as_ref(), b_slot, Some(&objective_name))
.await;
Ok(())
}
async fn clear_display_slot(
&mut self,
res: Resource<scoreboard::BedrockScoreboard>,
slot: scoreboard::BedrockDisplaySlot,
) -> wasmtime::Result<()> {
let player = self.get_bedrock_scoreboard_res(&res)?.provider.clone();
let mut custom_guard = player.custom_scoreboard.lock().await;
if let Some(crate::entity::player::CustomScoreboard::Bedrock(sb)) = custom_guard.as_mut() {
let b_slot = match slot {
scoreboard::BedrockDisplaySlot::PlayerList => {
crate::world::scoreboard::BedrockDisplaySlot::PlayerList
}
scoreboard::BedrockDisplaySlot::Sidebar => {
crate::world::scoreboard::BedrockDisplaySlot::Sidebar
}
scoreboard::BedrockDisplaySlot::BelowName => {
crate::world::scoreboard::BedrockDisplaySlot::BelowName
}
};
sb.clear_display_objective(player.as_ref(), b_slot).await;
}
Ok(())
}
async fn update_score(
&mut self,
res: Resource<scoreboard::BedrockScoreboard>,
entity_name: String,
objective_name: String,
value: i32,
) -> wasmtime::Result<()> {
let player = self.get_bedrock_scoreboard_res(&res)?.provider.clone();
let mut custom_guard = player.custom_scoreboard.lock().await;
if !matches!(
*custom_guard,
Some(crate::entity::player::CustomScoreboard::Bedrock(_))
) {
*custom_guard = Some(crate::entity::player::CustomScoreboard::Bedrock(
crate::world::scoreboard::BedrockScoreboard::default(),
));
}
let Some(crate::entity::player::CustomScoreboard::Bedrock(sb)) = custom_guard.as_mut()
else {
return Err(wasmtime::Error::msg("Invalid scoreboard state"));
};
sb.update_score(player.as_ref(), &entity_name, &objective_name, value)
.await;
Ok(())
}
async fn add_score(
&mut self,
res: Resource<scoreboard::BedrockScoreboard>,
entity_name: String,
objective_name: String,
delta: i32,
) -> wasmtime::Result<i32> {
let player = self.get_bedrock_scoreboard_res(&res)?.provider.clone();
let mut custom_guard = player.custom_scoreboard.lock().await;
if !matches!(
*custom_guard,
Some(crate::entity::player::CustomScoreboard::Bedrock(_))
) {
*custom_guard = Some(crate::entity::player::CustomScoreboard::Bedrock(
crate::world::scoreboard::BedrockScoreboard::default(),
));
}
let Some(crate::entity::player::CustomScoreboard::Bedrock(sb)) = custom_guard.as_mut()
else {
return Err(wasmtime::Error::msg("Invalid scoreboard state"));
};
let new_val = sb
.add_score(player.as_ref(), entity_name, objective_name, delta)
.await;
Ok(new_val)
}
async fn remove_score(
&mut self,
res: Resource<scoreboard::BedrockScoreboard>,
entity_name: String,
objective_name: String,
) -> wasmtime::Result<()> {
let player = self.get_bedrock_scoreboard_res(&res)?.provider.clone();
let mut custom_guard = player.custom_scoreboard.lock().await;
if let Some(crate::entity::player::CustomScoreboard::Bedrock(sb)) = custom_guard.as_mut() {
sb.remove_score(player.as_ref(), &entity_name, &objective_name)
.await;
}
Ok(())
}
async fn reset_entity_scores(
&mut self,
res: Resource<scoreboard::BedrockScoreboard>,
entity_name: String,
) -> wasmtime::Result<()> {
let player = self.get_bedrock_scoreboard_res(&res)?.provider.clone();
let mut custom_guard = player.custom_scoreboard.lock().await;
if let Some(crate::entity::player::CustomScoreboard::Bedrock(sb)) = custom_guard.as_mut() {
sb.reset_scores_for_entity(player.as_ref(), &entity_name)
.await;
}
Ok(())
}
async fn drop(
&mut self,
_res: Resource<scoreboard::BedrockScoreboard>,
) -> wasmtime::Result<()> {
Ok(())
}
}

View File

@@ -495,7 +495,11 @@ impl pumpkin::plugin::world::HostWorld for PluginHostState {
world: Resource<World>,
) -> wasmtime::Result<Resource<pumpkin::plugin::scoreboard::Scoreboard>> {
let world_provider = self.get_world_res(&world)?.provider.clone();
self.add_scoreboard(world_provider)
self.add_scoreboard(
crate::plugin::loader::wasm::wasm_host::state::ScoreboardProvider::World(
world_provider,
),
)
}
async fn play_sound(

View File

@@ -3407,6 +3407,9 @@ impl World {
// Sends initial time
player.send_time(self).await;
// Sends initial scoreboard state
player.send_scoreboard().await;
let (spawn_block_pos, yaw, pitch) = {
let level_info_lock = self.level_info.load();
(

File diff suppressed because it is too large Load Diff

View File

@@ -14,6 +14,7 @@ pub fn build() -> TokenStream {
let variants = array_to_tokenstream(&sound_categories);
quote! {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum ScoreboardDisplaySlot {
#variants
}