feat: more plugin api features
This commit is contained in:
parent
bc7e59df39
commit
413f7ccb16
@ -42,7 +42,7 @@ pub mod command {
|
||||
pub use wit::pumpkin::plugin::{
|
||||
command as command_wit, common,
|
||||
context::{Context, Server},
|
||||
permission, server, text, world,
|
||||
gui, permission, scoreboard, server, text, world,
|
||||
};
|
||||
|
||||
pub mod logging;
|
||||
|
||||
38
pumpkin-plugin-wit/v0.1.0/gui.wit
Normal file
38
pumpkin-plugin-wit/v0.1.0/gui.wit
Normal file
@ -0,0 +1,38 @@
|
||||
interface gui {
|
||||
use text.{text-component};
|
||||
use common.{item-stack};
|
||||
|
||||
enum gui-type {
|
||||
generic-9x1,
|
||||
generic-9x2,
|
||||
generic-9x3,
|
||||
generic-9x4,
|
||||
generic-9x5,
|
||||
generic-9x6,
|
||||
generic-3x3,
|
||||
crafter-3x3,
|
||||
anvil,
|
||||
beacon,
|
||||
blast-furnace,
|
||||
brewing-stand,
|
||||
crafting,
|
||||
enchantment,
|
||||
furnace,
|
||||
grindstone,
|
||||
hopper,
|
||||
lectern,
|
||||
loom,
|
||||
merchant,
|
||||
shulker-box,
|
||||
smithing,
|
||||
smoker,
|
||||
cartography-table,
|
||||
stonecutter,
|
||||
}
|
||||
|
||||
resource gui {
|
||||
constructor(%type: gui-type, title: text-component);
|
||||
set-item: func(slot: u32, item: item-stack);
|
||||
get-item: func(slot: u32) -> option<item-stack>;
|
||||
}
|
||||
}
|
||||
@ -3,6 +3,7 @@ interface player {
|
||||
use %world.{%world};
|
||||
use permission.{permission-level};
|
||||
use text.{text-component};
|
||||
use gui.{gui};
|
||||
|
||||
record player-abilities {
|
||||
invulnerable: bool,
|
||||
@ -46,6 +47,8 @@ interface player {
|
||||
kick: func (message: text-component);
|
||||
respawn: func ();
|
||||
|
||||
open-gui: func (gui-ref: gui);
|
||||
|
||||
ban: func (reason: option<text-component>);
|
||||
ban-ip: func (reason: option<text-component>);
|
||||
|
||||
@ -90,4 +93,4 @@ interface player {
|
||||
|
||||
get-ip: func() -> string;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,6 +8,8 @@ world plugin {
|
||||
|
||||
// This is what the host should provide to the plugin
|
||||
import logging;
|
||||
import gui;
|
||||
import scoreboard;
|
||||
import server;
|
||||
import text;
|
||||
import command;
|
||||
|
||||
72
pumpkin-plugin-wit/v0.1.0/scoreboard.wit
Normal file
72
pumpkin-plugin-wit/v0.1.0/scoreboard.wit
Normal file
@ -0,0 +1,72 @@
|
||||
interface scoreboard {
|
||||
use text.{text-component};
|
||||
use common.{named-color};
|
||||
|
||||
enum render-type {
|
||||
integer,
|
||||
hearts
|
||||
}
|
||||
|
||||
enum display-slot {
|
||||
player-list,
|
||||
sidebar,
|
||||
below-name,
|
||||
sidebar-team-black,
|
||||
sidebar-team-dark-blue,
|
||||
sidebar-team-dark-green,
|
||||
sidebar-team-dark-aqua,
|
||||
sidebar-team-dark-red,
|
||||
sidebar-team-dark-purple,
|
||||
sidebar-team-gold,
|
||||
sidebar-team-gray,
|
||||
sidebar-team-dark-gray,
|
||||
sidebar-team-blue,
|
||||
sidebar-team-green,
|
||||
sidebar-team-aqua,
|
||||
sidebar-team-red,
|
||||
sidebar-team-light-purple,
|
||||
sidebar-team-yellow,
|
||||
sidebar-team-white,
|
||||
}
|
||||
|
||||
enum nametag-visibility {
|
||||
always,
|
||||
never,
|
||||
hide-for-other-teams,
|
||||
hide-for-own-team
|
||||
}
|
||||
|
||||
enum collision-rule {
|
||||
always,
|
||||
never,
|
||||
push-other-teams,
|
||||
push-own-team
|
||||
}
|
||||
|
||||
record team-settings {
|
||||
display-name: text-component,
|
||||
friendly-fire: bool,
|
||||
see-friendly-invisibles: bool,
|
||||
nametag-visibility: nametag-visibility,
|
||||
collision-rule: collision-rule,
|
||||
color: named-color,
|
||||
prefix: text-component,
|
||||
suffix: text-component
|
||||
}
|
||||
|
||||
resource scoreboard {
|
||||
add-objective: func(name: string, display-name: text-component, render-type: render-type);
|
||||
remove-objective: func(name: string);
|
||||
set-display-slot: func(slot: display-slot, objective-name: string);
|
||||
|
||||
update-score: func(entity-name: string, objective-name: string, value: s32);
|
||||
remove-score: func(entity-name: string, objective-name: string);
|
||||
|
||||
create-team: func(name: string, settings: team-settings);
|
||||
remove-team: func(name: string);
|
||||
update-team: func(name: string, settings: team-settings);
|
||||
|
||||
add-player-to-team: func(team-name: string, player-name: string);
|
||||
remove-player-from-team: func(team-name: string, player-name: string);
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
interface %world {
|
||||
use common.{position};
|
||||
use text.{text-component};
|
||||
use scoreboard.{scoreboard};
|
||||
|
||||
enum piston-behavior {
|
||||
normal,
|
||||
@ -34,6 +35,10 @@ interface %world {
|
||||
has-random-ticks: bool,
|
||||
}
|
||||
|
||||
record block-state-id {
|
||||
id: u16,
|
||||
}
|
||||
|
||||
record block-pos {
|
||||
x: s32,
|
||||
y: s32,
|
||||
@ -70,5 +75,7 @@ interface %world {
|
||||
set-thundering: func(thundering: bool);
|
||||
|
||||
broadcast-system-message: func(message: text-component, overlay: bool);
|
||||
|
||||
get-scoreboard: func() -> scoreboard;
|
||||
}
|
||||
}
|
||||
|
||||
@ -69,6 +69,7 @@ mod set_health;
|
||||
mod set_held_item;
|
||||
mod set_passengers;
|
||||
mod set_player_inventory;
|
||||
mod set_player_team;
|
||||
mod set_time;
|
||||
mod set_title;
|
||||
mod set_title_animation;
|
||||
@ -164,6 +165,7 @@ pub use set_health::*;
|
||||
pub use set_held_item::*;
|
||||
pub use set_passengers::*;
|
||||
pub use set_player_inventory::*;
|
||||
pub use set_player_team::*;
|
||||
pub use set_time::*;
|
||||
pub use set_title::*;
|
||||
pub use set_title_animation::*;
|
||||
|
||||
81
pumpkin-protocol/src/java/client/play/set_player_team.rs
Normal file
81
pumpkin-protocol/src/java/client/play/set_player_team.rs
Normal file
@ -0,0 +1,81 @@
|
||||
use std::io::Write;
|
||||
|
||||
use pumpkin_data::packet::clientbound::PLAY_SET_PLAYER_TEAM;
|
||||
use pumpkin_macros::java_packet;
|
||||
use pumpkin_util::{text::TextComponent, version::MinecraftVersion};
|
||||
|
||||
use crate::{
|
||||
ClientPacket,
|
||||
codec::var_int::VarInt,
|
||||
ser::{NetworkWriteExt, WritingError},
|
||||
};
|
||||
|
||||
#[repr(i8)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum TeamMethod {
|
||||
Create = 0,
|
||||
Remove = 1,
|
||||
Update = 2,
|
||||
AddPlayers = 3,
|
||||
RemovePlayers = 4,
|
||||
}
|
||||
|
||||
pub struct TeamParameters<'a> {
|
||||
pub display_name: &'a TextComponent,
|
||||
pub options: i8,
|
||||
pub nametag_visibility: &'a str,
|
||||
pub collision_rule: &'a str,
|
||||
pub color: i32,
|
||||
pub player_prefix: &'a TextComponent,
|
||||
pub player_suffix: &'a TextComponent,
|
||||
}
|
||||
|
||||
#[java_packet(PLAY_SET_PLAYER_TEAM)]
|
||||
pub struct CSetPlayerTeam<'a> {
|
||||
pub team_name: String,
|
||||
pub method: TeamMethod,
|
||||
pub parameters: Option<TeamParameters<'a>>,
|
||||
pub players: Vec<String>,
|
||||
}
|
||||
|
||||
impl ClientPacket for CSetPlayerTeam<'_> {
|
||||
fn write_packet_data(
|
||||
&self,
|
||||
mut write: impl Write,
|
||||
_version: &MinecraftVersion,
|
||||
) -> Result<(), WritingError> {
|
||||
write.write_string(&self.team_name)?;
|
||||
write.write_i8(self.method as i8)?;
|
||||
|
||||
match self.method {
|
||||
TeamMethod::Create | TeamMethod::Update => {
|
||||
if let Some(params) = &self.parameters {
|
||||
write.write_slice(¶ms.display_name.encode())?;
|
||||
write.write_i8(params.options)?;
|
||||
write.write_string(params.nametag_visibility)?;
|
||||
write.write_string(params.collision_rule)?;
|
||||
write.write_var_int(&VarInt(params.color))?;
|
||||
write.write_slice(¶ms.player_prefix.encode())?;
|
||||
write.write_slice(¶ms.player_suffix.encode())?;
|
||||
} else {
|
||||
return Err(WritingError::Message(
|
||||
"Parameters missing for Create/Update".into(),
|
||||
));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
match self.method {
|
||||
TeamMethod::Create | TeamMethod::AddPlayers | TeamMethod::RemovePlayers => {
|
||||
write.write_var_int(&VarInt(self.players.len() as i32))?;
|
||||
for player in &self.players {
|
||||
write.write_string(player)?;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -74,6 +74,7 @@ pub enum Mode {
|
||||
Update,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum RenderType {
|
||||
Integer,
|
||||
Hearts,
|
||||
|
||||
@ -45,4 +45,15 @@ impl CUpdateScore {
|
||||
number_format,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn new_remove(entity_name: String, objective_name: String) -> Self {
|
||||
Self {
|
||||
entity_name,
|
||||
objective_name,
|
||||
value: VarInt(0),
|
||||
display_name: None,
|
||||
number_format: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -423,7 +423,7 @@ pub struct KnownPack<'a> {
|
||||
pub version: &'a str,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[derive(Serialize, Clone)]
|
||||
pub enum NumberFormat {
|
||||
/// Show nothing.
|
||||
Blank,
|
||||
|
||||
@ -2902,6 +2902,40 @@ impl Player {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn open_handled_screen_direct(
|
||||
&self,
|
||||
screen_handler: Arc<Mutex<dyn ScreenHandler>>,
|
||||
title: TextComponent,
|
||||
) {
|
||||
if !self
|
||||
.current_screen_handler
|
||||
.lock()
|
||||
.await
|
||||
.lock()
|
||||
.await
|
||||
.as_any()
|
||||
.is::<PlayerScreenHandler>()
|
||||
{
|
||||
self.close_handled_screen().await;
|
||||
}
|
||||
|
||||
let screen_handler_temp = screen_handler.lock().await;
|
||||
self.client
|
||||
.enqueue_packet(&COpenScreen::new(
|
||||
screen_handler_temp.sync_id().into(),
|
||||
(screen_handler_temp
|
||||
.window_type()
|
||||
.expect("Can't open PlayerScreenHandler") as i32)
|
||||
.into(),
|
||||
&title,
|
||||
))
|
||||
.await;
|
||||
drop(screen_handler_temp);
|
||||
self.on_screen_handler_opened(screen_handler.clone()).await;
|
||||
*self.current_screen_handler.lock().await = screen_handler;
|
||||
self.open_container_pos.store(None);
|
||||
}
|
||||
|
||||
pub async fn on_slot_click(&self, packet: SClickSlot) {
|
||||
self.update_last_action_time();
|
||||
let screen_handler = self.current_screen_handler.lock().await;
|
||||
|
||||
136
pumpkin/src/plugin/api/gui.rs
Normal file
136
pumpkin/src/plugin/api/gui.rs
Normal file
@ -0,0 +1,136 @@
|
||||
use std::any::Any;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use pumpkin_data::{item_stack::ItemStack, screen::WindowType};
|
||||
use pumpkin_inventory::screen_handler::{
|
||||
InventoryPlayer, ItemStackFuture, ScreenHandler, ScreenHandlerBehaviour, ScreenHandlerFuture,
|
||||
};
|
||||
use pumpkin_inventory::slot::NormalSlot;
|
||||
use pumpkin_util::text::TextComponent;
|
||||
use pumpkin_world::inventory::{Clearable, Inventory, InventoryFuture};
|
||||
|
||||
pub struct PluginGui {
|
||||
pub window_type: WindowType,
|
||||
pub title: TextComponent,
|
||||
pub inventory: Arc<PluginInventory>,
|
||||
}
|
||||
|
||||
pub struct PluginInventory {
|
||||
pub slots: Vec<Arc<Mutex<ItemStack>>>,
|
||||
}
|
||||
|
||||
impl PluginInventory {
|
||||
#[must_use]
|
||||
pub fn new(size: usize) -> Self {
|
||||
let mut slots = Vec::with_capacity(size);
|
||||
for _ in 0..size {
|
||||
slots.push(Arc::new(Mutex::new(ItemStack::EMPTY.clone())));
|
||||
}
|
||||
Self { slots }
|
||||
}
|
||||
}
|
||||
|
||||
impl Clearable for PluginInventory {
|
||||
fn clear(&self) -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send + '_>> {
|
||||
Box::pin(async move {
|
||||
for slot in &self.slots {
|
||||
*slot.lock().await = ItemStack::EMPTY.clone();
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Inventory for PluginInventory {
|
||||
fn size(&self) -> usize {
|
||||
self.slots.len()
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> InventoryFuture<'_, bool> {
|
||||
Box::pin(async move {
|
||||
for slot in &self.slots {
|
||||
if !slot.lock().await.is_empty() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
})
|
||||
}
|
||||
|
||||
fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc<Mutex<ItemStack>>> {
|
||||
Box::pin(async move { self.slots[slot].clone() })
|
||||
}
|
||||
|
||||
fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
|
||||
Box::pin(async move {
|
||||
let mut stack = self.slots[slot].lock().await;
|
||||
std::mem::replace(&mut *stack, ItemStack::EMPTY.clone())
|
||||
})
|
||||
}
|
||||
|
||||
fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> {
|
||||
Box::pin(async move {
|
||||
let mut stack = self.slots[slot].lock().await;
|
||||
stack.split(amount)
|
||||
})
|
||||
}
|
||||
|
||||
fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> {
|
||||
Box::pin(async move {
|
||||
*self.slots[slot].lock().await = stack;
|
||||
})
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PluginScreenHandler {
|
||||
pub inventory: Arc<PluginInventory>,
|
||||
behaviour: ScreenHandlerBehaviour,
|
||||
}
|
||||
|
||||
impl PluginScreenHandler {
|
||||
#[must_use]
|
||||
pub fn new(sync_id: u8, window_type: WindowType, inventory: &Arc<PluginInventory>) -> Self {
|
||||
let mut handler = Self {
|
||||
inventory: inventory.clone(),
|
||||
behaviour: ScreenHandlerBehaviour::new(sync_id, Some(window_type)),
|
||||
};
|
||||
|
||||
for i in 0..inventory.size() {
|
||||
handler.add_slot(Arc::new(NormalSlot::new(inventory.clone(), i)));
|
||||
}
|
||||
|
||||
handler
|
||||
}
|
||||
}
|
||||
|
||||
impl ScreenHandler for PluginScreenHandler {
|
||||
fn on_closed<'a>(&'a mut self, player: &'a dyn InventoryPlayer) -> ScreenHandlerFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
self.default_on_closed(player).await;
|
||||
})
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn get_behaviour(&self) -> &ScreenHandlerBehaviour {
|
||||
&self.behaviour
|
||||
}
|
||||
|
||||
fn get_behaviour_mut(&mut self) -> &mut ScreenHandlerBehaviour {
|
||||
&mut self.behaviour
|
||||
}
|
||||
|
||||
fn quick_move<'a>(
|
||||
&'a mut self,
|
||||
_player: &'a dyn InventoryPlayer,
|
||||
_slot_index: i32,
|
||||
) -> ItemStackFuture<'a> {
|
||||
Box::pin(async move { ItemStack::EMPTY.clone() })
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
pub mod context;
|
||||
pub mod events;
|
||||
pub mod gui;
|
||||
|
||||
use std::{pin::Pin, sync::Arc};
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ use std::{
|
||||
};
|
||||
|
||||
use pumpkin_util::text::TextComponent;
|
||||
use tokio::sync::Mutex;
|
||||
use wasmtime::component::ResourceTable;
|
||||
use wasmtime_wasi::{WasiCtx, WasiCtxBuilder, WasiCtxView, WasiView};
|
||||
|
||||
@ -16,6 +17,7 @@ use crate::{
|
||||
entity::player::Player,
|
||||
plugin::{
|
||||
Context,
|
||||
api::gui::PluginGui,
|
||||
loader::wasm::wasm_host::{WasmPlugin, args::OwnedArg},
|
||||
},
|
||||
server::Server,
|
||||
@ -30,6 +32,8 @@ pub type ServerResource = WasmResource<Arc<Server>>;
|
||||
pub type ContextResource = WasmResource<Arc<Context>>;
|
||||
pub type PlayerResource = WasmResource<Arc<Player>>;
|
||||
pub type WorldResource = WasmResource<Arc<World>>;
|
||||
pub type ScoreboardResource = WasmResource<Arc<World>>;
|
||||
pub type GuiResource = WasmResource<Arc<Mutex<PluginGui>>>;
|
||||
pub type TextComponentResource = WasmResource<TextComponent>;
|
||||
pub type CommandResource = WasmResource<CommandTree>;
|
||||
pub type CommandSenderResource = WasmResource<CommandSender>;
|
||||
@ -95,6 +99,22 @@ impl PluginHostState {
|
||||
Ok(wasmtime::component::Resource::new_own(resource.rep()))
|
||||
}
|
||||
|
||||
pub fn add_scoreboard<T>(
|
||||
&mut self,
|
||||
provider: Arc<World>,
|
||||
) -> 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_gui<T>(
|
||||
&mut self,
|
||||
provider: Arc<Mutex<PluginGui>>,
|
||||
) -> wasmtime::Result<wasmtime::component::Resource<T>> {
|
||||
let resource = self.resource_table.push(GuiResource { provider })?;
|
||||
Ok(wasmtime::component::Resource::new_own(resource.rep()))
|
||||
}
|
||||
|
||||
pub fn add_text_component<T>(
|
||||
&mut self,
|
||||
provider: TextComponent,
|
||||
|
||||
124
pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/gui.rs
Normal file
124
pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/gui.rs
Normal file
@ -0,0 +1,124 @@
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
use wasmtime::component::Resource;
|
||||
|
||||
use crate::plugin::api::gui::{PluginGui, PluginInventory};
|
||||
use crate::plugin::loader::wasm::wasm_host::{
|
||||
state::{GuiResource, PluginHostState},
|
||||
wit::v0_1_0::pumpkin::plugin::{
|
||||
common::ItemStack as WitItemStack,
|
||||
gui::{self, Gui, GuiType},
|
||||
},
|
||||
};
|
||||
use pumpkin_data::item_stack::ItemStack;
|
||||
|
||||
impl PluginHostState {
|
||||
fn get_gui_res(&self, res: &Resource<Gui>) -> wasmtime::Result<&GuiResource> {
|
||||
self.resource_table
|
||||
.get::<GuiResource>(&Resource::new_own(res.rep()))
|
||||
.map_err(wasmtime::Error::from)
|
||||
}
|
||||
}
|
||||
|
||||
impl gui::Host for PluginHostState {}
|
||||
|
||||
impl gui::HostGui for PluginHostState {
|
||||
async fn new(
|
||||
&mut self,
|
||||
gui_type: GuiType,
|
||||
title: Resource<crate::plugin::loader::wasm::wasm_host::wit::v0_1_0::pumpkin::plugin::text::TextComponent>,
|
||||
) -> wasmtime::Result<Resource<Gui>> {
|
||||
let title = self.get_text_provider(&title)?;
|
||||
let window_type = match gui_type {
|
||||
GuiType::Generic9x1 => pumpkin_data::screen::WindowType::Generic9x1,
|
||||
GuiType::Generic9x2 => pumpkin_data::screen::WindowType::Generic9x2,
|
||||
GuiType::Generic9x3 => pumpkin_data::screen::WindowType::Generic9x3,
|
||||
GuiType::Generic9x4 => pumpkin_data::screen::WindowType::Generic9x4,
|
||||
GuiType::Generic9x5 => pumpkin_data::screen::WindowType::Generic9x5,
|
||||
GuiType::Generic9x6 => pumpkin_data::screen::WindowType::Generic9x6,
|
||||
GuiType::Generic3x3 => pumpkin_data::screen::WindowType::Generic3x3,
|
||||
GuiType::Crafter3x3 => pumpkin_data::screen::WindowType::Crafter3x3,
|
||||
GuiType::Anvil => pumpkin_data::screen::WindowType::Anvil,
|
||||
GuiType::Beacon => pumpkin_data::screen::WindowType::Beacon,
|
||||
GuiType::BlastFurnace => pumpkin_data::screen::WindowType::BlastFurnace,
|
||||
GuiType::BrewingStand => pumpkin_data::screen::WindowType::BrewingStand,
|
||||
GuiType::Crafting => pumpkin_data::screen::WindowType::Crafting,
|
||||
GuiType::Enchantment => pumpkin_data::screen::WindowType::Enchantment,
|
||||
GuiType::Furnace => pumpkin_data::screen::WindowType::Furnace,
|
||||
GuiType::Grindstone => pumpkin_data::screen::WindowType::Grindstone,
|
||||
GuiType::Hopper => pumpkin_data::screen::WindowType::Hopper,
|
||||
GuiType::Lectern => pumpkin_data::screen::WindowType::Lectern,
|
||||
GuiType::Loom => pumpkin_data::screen::WindowType::Loom,
|
||||
GuiType::Merchant => pumpkin_data::screen::WindowType::Merchant,
|
||||
GuiType::ShulkerBox => pumpkin_data::screen::WindowType::ShulkerBox,
|
||||
GuiType::Smithing => pumpkin_data::screen::WindowType::Smithing,
|
||||
GuiType::Smoker => pumpkin_data::screen::WindowType::Smoker,
|
||||
GuiType::CartographyTable => pumpkin_data::screen::WindowType::CartographyTable,
|
||||
GuiType::Stonecutter => pumpkin_data::screen::WindowType::Stonecutter,
|
||||
};
|
||||
|
||||
let size = match window_type {
|
||||
pumpkin_data::screen::WindowType::Generic9x2 => 18,
|
||||
pumpkin_data::screen::WindowType::Generic9x4 => 36,
|
||||
pumpkin_data::screen::WindowType::Generic9x5 => 45,
|
||||
pumpkin_data::screen::WindowType::Generic9x6 => 54,
|
||||
pumpkin_data::screen::WindowType::Generic3x3 => 9,
|
||||
pumpkin_data::screen::WindowType::Generic9x1
|
||||
| pumpkin_data::screen::WindowType::Hopper => 5,
|
||||
_ => 27, // Default
|
||||
};
|
||||
|
||||
let gui = Arc::new(Mutex::new(PluginGui {
|
||||
window_type,
|
||||
title,
|
||||
inventory: Arc::new(PluginInventory::new(size)),
|
||||
}));
|
||||
|
||||
self.add_gui(gui)
|
||||
}
|
||||
|
||||
async fn set_item(
|
||||
&mut self,
|
||||
res: Resource<Gui>,
|
||||
slot: u32,
|
||||
item: WitItemStack,
|
||||
) -> wasmtime::Result<()> {
|
||||
let gui = self.get_gui_res(&res)?.provider.lock().await;
|
||||
if (slot as usize) < gui.inventory.slots.len() {
|
||||
let item_id = item.registry_key;
|
||||
let item_data = pumpkin_data::item::Item::from_registry_key(&item_id)
|
||||
.ok_or_else(|| wasmtime::Error::msg("Invalid item"))?;
|
||||
let item_stack = ItemStack::new(item.count, item_data);
|
||||
*gui.inventory.slots[slot as usize].lock().await = item_stack;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_item(
|
||||
&mut self,
|
||||
res: Resource<Gui>,
|
||||
slot: u32,
|
||||
) -> wasmtime::Result<Option<WitItemStack>> {
|
||||
let gui = self.get_gui_res(&res)?.provider.lock().await;
|
||||
if (slot as usize) < gui.inventory.slots.len() {
|
||||
let stack = gui.inventory.slots[slot as usize].lock().await;
|
||||
if stack.is_empty() {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(WitItemStack {
|
||||
registry_key: stack.get_item().registry_key.to_string(),
|
||||
count: stack.item_count,
|
||||
}))
|
||||
}
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
async fn drop(&mut self, rep: Resource<Gui>) -> wasmtime::Result<()> {
|
||||
self.resource_table
|
||||
.delete::<GuiResource>(Resource::new_own(rep.rep()))
|
||||
.map_err(wasmtime::Error::from)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -11,10 +11,12 @@ pub mod common;
|
||||
pub mod context;
|
||||
pub mod entity;
|
||||
pub mod events;
|
||||
pub mod gui;
|
||||
pub mod i18n;
|
||||
pub mod logging;
|
||||
pub mod permission;
|
||||
pub mod player;
|
||||
pub mod scoreboard;
|
||||
pub mod server;
|
||||
pub mod text;
|
||||
pub mod world;
|
||||
|
||||
@ -1,12 +1,17 @@
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::Ordering;
|
||||
use tokio::sync::Mutex;
|
||||
use wasmtime::component::Resource;
|
||||
|
||||
use crate::plugin::api::gui::PluginScreenHandler;
|
||||
use crate::{
|
||||
entity::{EntityBase, player::TitleMode},
|
||||
net::DisconnectReason,
|
||||
plugin::loader::wasm::wasm_host::{
|
||||
DowncastResourceExt,
|
||||
state::{PlayerResource, PluginHostState, TextComponentResource, WorldResource},
|
||||
state::{
|
||||
GuiResource, PlayerResource, PluginHostState, TextComponentResource, WorldResource,
|
||||
},
|
||||
wit::v0_1_0::{
|
||||
events::{
|
||||
from_wasm_game_mode, from_wasm_position, to_wasm_game_mode, to_wasm_position,
|
||||
@ -333,6 +338,32 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn open_gui(
|
||||
&mut self,
|
||||
player: Resource<Player>,
|
||||
gui: Resource<pumpkin::plugin::gui::Gui>,
|
||||
) -> wasmtime::Result<()> {
|
||||
let player = player_from_resource(self, &player)?;
|
||||
let gui_res = self
|
||||
.resource_table
|
||||
.get::<GuiResource>(&Resource::new_own(gui.rep()))
|
||||
.map_err(|_| wasmtime::Error::msg("invalid gui resource handle"))?;
|
||||
let gui = gui_res.provider.lock().await;
|
||||
|
||||
player.increment_screen_handler_sync_id();
|
||||
let sync_id = player.screen_handler_sync_id.load(Ordering::Relaxed);
|
||||
let screen_handler = Arc::new(Mutex::new(PluginScreenHandler::new(
|
||||
sync_id,
|
||||
gui.window_type,
|
||||
&gui.inventory,
|
||||
)));
|
||||
|
||||
player
|
||||
.open_handled_screen_direct(screen_handler, gui.title.clone())
|
||||
.await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn ban(
|
||||
&mut self,
|
||||
player: Resource<Player>,
|
||||
|
||||
@ -0,0 +1,344 @@
|
||||
use wasmtime::component::Resource;
|
||||
|
||||
use crate::plugin::loader::wasm::wasm_host::{
|
||||
state::{PluginHostState, ScoreboardResource},
|
||||
wit::v0_1_0::pumpkin::{
|
||||
self,
|
||||
plugin::scoreboard::{
|
||||
self, CollisionRule, DisplaySlot, NametagVisibility, RenderType, TeamSettings,
|
||||
},
|
||||
},
|
||||
};
|
||||
use crate::world::scoreboard::{ScoreboardObjective, ScoreboardScore, Team};
|
||||
use pumpkin_protocol::codec::var_int::VarInt;
|
||||
|
||||
impl PluginHostState {
|
||||
fn get_scoreboard_res(
|
||||
&self,
|
||||
res: &Resource<scoreboard::Scoreboard>,
|
||||
) -> wasmtime::Result<&ScoreboardResource> {
|
||||
self.resource_table
|
||||
.get::<ScoreboardResource>(&Resource::new_own(res.rep()))
|
||||
.map_err(wasmtime::Error::from)
|
||||
}
|
||||
}
|
||||
|
||||
impl scoreboard::Host for PluginHostState {}
|
||||
|
||||
impl scoreboard::HostScoreboard for PluginHostState {
|
||||
async fn add_objective(
|
||||
&mut self,
|
||||
res: Resource<scoreboard::Scoreboard>,
|
||||
name: String,
|
||||
display_name: Resource<pumpkin::plugin::text::TextComponent>,
|
||||
render_type: RenderType,
|
||||
) -> wasmtime::Result<()> {
|
||||
let world = 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(Box::leak(name.into_boxed_str()), display_name, rt, None);
|
||||
world
|
||||
.scoreboard
|
||||
.lock()
|
||||
.await
|
||||
.add_objective(&world, objective)
|
||||
.await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn remove_objective(
|
||||
&mut self,
|
||||
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;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn set_display_slot(
|
||||
&mut self,
|
||||
res: Resource<scoreboard::Scoreboard>,
|
||||
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
|
||||
}
|
||||
};
|
||||
|
||||
world
|
||||
.broadcast_packet_all(
|
||||
&pumpkin_protocol::java::client::play::CDisplayObjective::new(slot, objective_name),
|
||||
)
|
||||
.await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn update_score(
|
||||
&mut self,
|
||||
res: Resource<scoreboard::Scoreboard>,
|
||||
entity_name: String,
|
||||
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;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn create_team(
|
||||
&mut self,
|
||||
res: Resource<scoreboard::Scoreboard>,
|
||||
name: String,
|
||||
settings: TeamSettings,
|
||||
) -> wasmtime::Result<()> {
|
||||
let world = self.get_scoreboard_res(&res)?.provider.clone();
|
||||
let team = map_team_settings(name, &settings, self)?;
|
||||
world.scoreboard.lock().await.add_team(&world, team).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn remove_team(
|
||||
&mut self,
|
||||
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)
|
||||
.await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn update_team(
|
||||
&mut self,
|
||||
res: Resource<scoreboard::Scoreboard>,
|
||||
name: String,
|
||||
settings: TeamSettings,
|
||||
) -> wasmtime::Result<()> {
|
||||
let world = self.get_scoreboard_res(&res)?.provider.clone();
|
||||
let team = map_team_settings(name, &settings, self)?;
|
||||
world
|
||||
.scoreboard
|
||||
.lock()
|
||||
.await
|
||||
.update_team(&world, team)
|
||||
.await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn add_player_to_team(
|
||||
&mut self,
|
||||
res: Resource<scoreboard::Scoreboard>,
|
||||
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)
|
||||
.await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn remove_player_from_team(
|
||||
&mut self,
|
||||
res: Resource<scoreboard::Scoreboard>,
|
||||
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)
|
||||
.await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn drop(&mut self, rep: Resource<scoreboard::Scoreboard>) -> wasmtime::Result<()> {
|
||||
self.resource_table
|
||||
.delete::<ScoreboardResource>(Resource::new_own(rep.rep()))
|
||||
.map_err(wasmtime::Error::from)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn map_team_settings(
|
||||
name: String,
|
||||
settings: &TeamSettings,
|
||||
state: &PluginHostState,
|
||||
) -> wasmtime::Result<Team> {
|
||||
let display_name = state.get_text_provider(&settings.display_name)?;
|
||||
let player_prefix = state.get_text_provider(&settings.prefix)?;
|
||||
let player_suffix = state.get_text_provider(&settings.suffix)?;
|
||||
|
||||
let mut options = 0;
|
||||
if settings.friendly_fire {
|
||||
options |= 0x01;
|
||||
}
|
||||
if settings.see_friendly_invisibles {
|
||||
options |= 0x02;
|
||||
}
|
||||
|
||||
Ok(Team {
|
||||
name,
|
||||
display_name,
|
||||
options,
|
||||
nametag_visibility: match settings.nametag_visibility {
|
||||
NametagVisibility::Always => crate::world::scoreboard::NameTagVisibility::Always,
|
||||
NametagVisibility::Never => crate::world::scoreboard::NameTagVisibility::Never,
|
||||
NametagVisibility::HideForOtherTeams => {
|
||||
crate::world::scoreboard::NameTagVisibility::HideForOtherTeams
|
||||
}
|
||||
NametagVisibility::HideForOwnTeam => {
|
||||
crate::world::scoreboard::NameTagVisibility::HideForOwnTeam
|
||||
}
|
||||
},
|
||||
collision_rule: match settings.collision_rule {
|
||||
CollisionRule::Always => crate::world::scoreboard::CollisionRule::Always,
|
||||
CollisionRule::Never => crate::world::scoreboard::CollisionRule::Never,
|
||||
CollisionRule::PushOtherTeams => {
|
||||
crate::world::scoreboard::CollisionRule::PushOtherTeams
|
||||
}
|
||||
CollisionRule::PushOwnTeam => crate::world::scoreboard::CollisionRule::PushOwnTeam,
|
||||
},
|
||||
color: map_named_color(settings.color),
|
||||
player_prefix,
|
||||
player_suffix,
|
||||
players: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
const fn map_named_color(
|
||||
color: pumpkin::plugin::common::NamedColor,
|
||||
) -> pumpkin_util::text::color::NamedColor {
|
||||
match color {
|
||||
pumpkin::plugin::common::NamedColor::Black => pumpkin_util::text::color::NamedColor::Black,
|
||||
pumpkin::plugin::common::NamedColor::DarkBlue => {
|
||||
pumpkin_util::text::color::NamedColor::DarkBlue
|
||||
}
|
||||
pumpkin::plugin::common::NamedColor::DarkGreen => {
|
||||
pumpkin_util::text::color::NamedColor::DarkGreen
|
||||
}
|
||||
pumpkin::plugin::common::NamedColor::DarkAqua => {
|
||||
pumpkin_util::text::color::NamedColor::DarkAqua
|
||||
}
|
||||
pumpkin::plugin::common::NamedColor::DarkRed => {
|
||||
pumpkin_util::text::color::NamedColor::DarkRed
|
||||
}
|
||||
pumpkin::plugin::common::NamedColor::DarkPurple => {
|
||||
pumpkin_util::text::color::NamedColor::DarkPurple
|
||||
}
|
||||
pumpkin::plugin::common::NamedColor::Gold => pumpkin_util::text::color::NamedColor::Gold,
|
||||
pumpkin::plugin::common::NamedColor::Gray => pumpkin_util::text::color::NamedColor::Gray,
|
||||
pumpkin::plugin::common::NamedColor::DarkGray => {
|
||||
pumpkin_util::text::color::NamedColor::DarkGray
|
||||
}
|
||||
pumpkin::plugin::common::NamedColor::Blue => pumpkin_util::text::color::NamedColor::Blue,
|
||||
pumpkin::plugin::common::NamedColor::Green => pumpkin_util::text::color::NamedColor::Green,
|
||||
pumpkin::plugin::common::NamedColor::Aqua => pumpkin_util::text::color::NamedColor::Aqua,
|
||||
pumpkin::plugin::common::NamedColor::Red => pumpkin_util::text::color::NamedColor::Red,
|
||||
pumpkin::plugin::common::NamedColor::LightPurple => {
|
||||
pumpkin_util::text::color::NamedColor::LightPurple
|
||||
}
|
||||
pumpkin::plugin::common::NamedColor::Yellow => {
|
||||
pumpkin_util::text::color::NamedColor::Yellow
|
||||
}
|
||||
pumpkin::plugin::common::NamedColor::White => pumpkin_util::text::color::NamedColor::White,
|
||||
}
|
||||
}
|
||||
@ -20,7 +20,7 @@ impl PluginHostState {
|
||||
.map_err(wasmtime::Error::from)
|
||||
}
|
||||
|
||||
fn get_text_provider(
|
||||
pub(crate) fn get_text_provider(
|
||||
&self,
|
||||
res: &Resource<pumpkin::plugin::text::TextComponent>,
|
||||
) -> wasmtime::Result<pumpkin_util::text::TextComponent> {
|
||||
@ -220,6 +220,14 @@ impl pumpkin::plugin::world::HostWorld for PluginHostState {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_scoreboard(
|
||||
&mut self,
|
||||
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)
|
||||
}
|
||||
|
||||
async fn drop(&mut self, rep: Resource<World>) -> wasmtime::Result<()> {
|
||||
self.resource_table
|
||||
.delete::<WorldResource>(Resource::new_own(rep.rep()))
|
||||
|
||||
@ -4,9 +4,12 @@ use pumpkin_data::scoreboard::ScoreboardDisplaySlot;
|
||||
use pumpkin_protocol::{
|
||||
NumberFormat,
|
||||
codec::var_int::VarInt,
|
||||
java::client::play::{CDisplayObjective, CUpdateObjectives, CUpdateScore, RenderType},
|
||||
java::client::play::{
|
||||
CDisplayObjective, CSetPlayerTeam, CUpdateObjectives, CUpdateScore, RenderType, TeamMethod,
|
||||
TeamParameters,
|
||||
},
|
||||
};
|
||||
use pumpkin_util::text::TextComponent;
|
||||
use pumpkin_util::text::{TextComponent, color::NamedColor};
|
||||
use tracing::warn;
|
||||
|
||||
use super::World;
|
||||
@ -14,13 +17,13 @@ use super::World;
|
||||
#[derive(Default)]
|
||||
pub struct Scoreboard {
|
||||
objectives: HashMap<String, ScoreboardObjective<'static>>,
|
||||
// teams: HashMap<String, Team>,
|
||||
teams: HashMap<String, Team>,
|
||||
scores: HashMap<String, HashMap<String, ScoreboardScore<'static>>>,
|
||||
}
|
||||
|
||||
impl Scoreboard {
|
||||
pub async fn add_objective(&mut self, world: &World, objective: ScoreboardObjective<'_>) {
|
||||
pub async fn add_objective(&mut self, world: &World, objective: ScoreboardObjective<'static>) {
|
||||
if self.objectives.contains_key(objective.name) {
|
||||
// Maybe make this an error?
|
||||
warn!(
|
||||
"Tried to create an objective which already exists: {}",
|
||||
&objective.name
|
||||
@ -31,9 +34,9 @@ impl Scoreboard {
|
||||
.broadcast_packet_all(&CUpdateObjectives::new(
|
||||
objective.name.to_string(),
|
||||
pumpkin_protocol::java::client::play::Mode::Add,
|
||||
objective.display_name,
|
||||
objective.display_name.clone(),
|
||||
objective.render_type,
|
||||
objective.number_format,
|
||||
objective.number_format.clone(),
|
||||
))
|
||||
.await;
|
||||
world
|
||||
@ -42,10 +45,33 @@ impl Scoreboard {
|
||||
objective.name.to_string(),
|
||||
))
|
||||
.await;
|
||||
self.objectives
|
||||
.insert(objective.name.to_string(), objective);
|
||||
}
|
||||
|
||||
pub async fn update_score(&self, world: &World, score: ScoreboardScore<'_>) {
|
||||
if self.objectives.contains_key(score.objective_name) {
|
||||
pub async fn remove_objective(&mut self, world: &World, name: &str) {
|
||||
if !self.objectives.contains_key(name) {
|
||||
warn!(
|
||||
"Tried to remove an objective which does not exist: {}",
|
||||
name
|
||||
);
|
||||
return;
|
||||
}
|
||||
world
|
||||
.broadcast_packet_all(&CUpdateObjectives::new(
|
||||
name.to_string(),
|
||||
pumpkin_protocol::java::client::play::Mode::Remove,
|
||||
TextComponent::empty(),
|
||||
pumpkin_protocol::java::client::play::RenderType::Integer,
|
||||
None,
|
||||
))
|
||||
.await;
|
||||
self.objectives.remove(name);
|
||||
self.scores.remove(name);
|
||||
}
|
||||
|
||||
pub async fn update_score(&mut self, world: &World, score: ScoreboardScore<'static>) {
|
||||
if !self.objectives.contains_key(score.objective_name) {
|
||||
warn!(
|
||||
"Tried to place a score into an objective which does not exist: {}",
|
||||
&score.objective_name
|
||||
@ -57,25 +83,163 @@ impl Scoreboard {
|
||||
score.entity_name.to_string(),
|
||||
score.objective_name.to_string(),
|
||||
score.value,
|
||||
score.display_name,
|
||||
score.number_format,
|
||||
score.display_name.clone(),
|
||||
score.number_format.clone(),
|
||||
))
|
||||
.await;
|
||||
|
||||
self.scores
|
||||
.entry(score.objective_name.to_string())
|
||||
.or_default()
|
||||
.insert(score.entity_name.to_string(), score);
|
||||
}
|
||||
|
||||
// pub fn add_team(&mut self, name: String) {
|
||||
// if self.teams.contains_key(&name) {
|
||||
// // Maybe make this an error ?
|
||||
// log::warn!("Tried to create Team which does already exist, {}", name);
|
||||
// }
|
||||
// }
|
||||
pub async fn remove_score(&mut self, world: &World, entity_name: &str, objective_name: &str) {
|
||||
world
|
||||
.broadcast_packet_all(&CUpdateScore::new_remove(
|
||||
entity_name.to_string(),
|
||||
objective_name.to_string(),
|
||||
))
|
||||
.await;
|
||||
|
||||
if let Some(objective_scores) = self.scores.get_mut(objective_name) {
|
||||
objective_scores.remove(entity_name);
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn add_team(&mut self, world: &World, team: Team) {
|
||||
if self.teams.contains_key(&team.name) {
|
||||
warn!(
|
||||
"Tried to create Team which does already 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,
|
||||
};
|
||||
|
||||
world
|
||||
.broadcast_packet_all(&CSetPlayerTeam {
|
||||
team_name: team.name.clone(),
|
||||
method: TeamMethod::Create,
|
||||
parameters: Some(parameters),
|
||||
players: team.players.clone(),
|
||||
})
|
||||
.await;
|
||||
|
||||
self.teams.insert(team.name.clone(), team);
|
||||
}
|
||||
|
||||
pub async fn update_team(&mut self, world: &World, 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,
|
||||
};
|
||||
|
||||
world
|
||||
.broadcast_packet_all(&CSetPlayerTeam {
|
||||
team_name: team.name.clone(),
|
||||
method: TeamMethod::Update,
|
||||
parameters: Some(parameters),
|
||||
players: Vec::new(),
|
||||
})
|
||||
.await;
|
||||
|
||||
self.teams.insert(team.name.clone(), team);
|
||||
}
|
||||
|
||||
pub async fn remove_team(&mut self, world: &World, name: &str) {
|
||||
if !self.teams.contains_key(name) {
|
||||
warn!("Tried to remove Team which does not exist, {}", name);
|
||||
return;
|
||||
}
|
||||
|
||||
world
|
||||
.broadcast_packet_all(&CSetPlayerTeam {
|
||||
team_name: name.to_string(),
|
||||
method: TeamMethod::Remove,
|
||||
parameters: None,
|
||||
players: Vec::new(),
|
||||
})
|
||||
.await;
|
||||
|
||||
self.teams.remove(name);
|
||||
}
|
||||
|
||||
pub async fn add_player_to_team(&mut self, world: &World, 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;
|
||||
}
|
||||
|
||||
world
|
||||
.broadcast_packet_all(&CSetPlayerTeam {
|
||||
team_name: team_name.to_string(),
|
||||
method: TeamMethod::AddPlayers,
|
||||
parameters: None,
|
||||
players: vec![player.clone()],
|
||||
})
|
||||
.await;
|
||||
|
||||
team.players.push(player);
|
||||
}
|
||||
|
||||
pub async fn remove_player_from_team(&mut self, world: &World, 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;
|
||||
}
|
||||
|
||||
world
|
||||
.broadcast_packet_all(&CSetPlayerTeam {
|
||||
team_name: team_name.to_string(),
|
||||
method: TeamMethod::RemovePlayers,
|
||||
parameters: None,
|
||||
players: vec![player.to_string()],
|
||||
})
|
||||
.await;
|
||||
|
||||
team.players.retain(|p| p != player);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ScoreboardObjective<'a> {
|
||||
name: &'a str,
|
||||
display_name: TextComponent,
|
||||
render_type: RenderType,
|
||||
number_format: Option<NumberFormat>,
|
||||
pub name: &'a str,
|
||||
pub display_name: TextComponent,
|
||||
pub render_type: RenderType,
|
||||
pub number_format: Option<NumberFormat>,
|
||||
}
|
||||
|
||||
impl<'a> ScoreboardObjective<'a> {
|
||||
@ -96,11 +260,11 @@ impl<'a> ScoreboardObjective<'a> {
|
||||
}
|
||||
|
||||
pub struct ScoreboardScore<'a> {
|
||||
entity_name: &'a str,
|
||||
objective_name: &'a str,
|
||||
value: VarInt,
|
||||
display_name: Option<TextComponent>,
|
||||
number_format: Option<NumberFormat>,
|
||||
pub entity_name: &'a str,
|
||||
pub objective_name: &'a str,
|
||||
pub value: VarInt,
|
||||
pub display_name: Option<TextComponent>,
|
||||
pub number_format: Option<NumberFormat>,
|
||||
}
|
||||
|
||||
impl<'a> ScoreboardScore<'a> {
|
||||
@ -121,3 +285,53 @@ impl<'a> ScoreboardScore<'a> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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>,
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user