From 452edf89874deec7dcd0f720cb214d6585338ef4 Mon Sep 17 00:00:00 2001 From: Alexander Medvedev Date: Sun, 19 Apr 2026 19:40:19 +0200 Subject: [PATCH] feat(plugin-api): add bossbars --- pumpkin-plugin-wit/v0.1/boss-bar.wit | 66 +++++ pumpkin-plugin-wit/v0.1/gui.wit | 12 + pumpkin-plugin-wit/v0.1/plugin.wit | 2 + pumpkin-plugin-wit/v0.1/server.wit | 41 +++ .../events/player/player_interact_event.rs | 1 - .../src/plugin/loader/wasm/wasm_host/state.rs | 13 + .../wasm/wasm_host/wit/v0_1/boss_bar.rs | 239 ++++++++++++++++++ .../loader/wasm/wasm_host/wit/v0_1/gui.rs | 60 +++++ .../loader/wasm/wasm_host/wit/v0_1/mod.rs | 1 + .../loader/wasm/wasm_host/wit/v0_1/server.rs | 154 +++++++++++ 10 files changed, 588 insertions(+), 1 deletion(-) create mode 100644 pumpkin-plugin-wit/v0.1/boss-bar.wit create mode 100644 pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/boss_bar.rs diff --git a/pumpkin-plugin-wit/v0.1/boss-bar.wit b/pumpkin-plugin-wit/v0.1/boss-bar.wit new file mode 100644 index 000000000..7c3638176 --- /dev/null +++ b/pumpkin-plugin-wit/v0.1/boss-bar.wit @@ -0,0 +1,66 @@ +interface boss-bar { + use text.{text-component}; + use player.{player}; + + /// Represents the color of a boss bar. + enum boss-bar-color { + pink, + blue, + red, + green, + yellow, + purple, + white, + } + + /// Represents the division (notches) of a boss bar. + enum boss-bar-division { + no-division, + notches-6, + notches-10, + notches-12, + notches-20, + } + + /// Represents a boss bar that can be shown to players. + resource boss-bar { + /// Creates a new boss bar. + constructor(title: text-component, color: boss-bar-color, division: boss-bar-division); + + /// Returns the title of this boss bar. + get-title: func() -> text-component; + + /// Sets the title of this boss bar. + set-title: func(title: text-component); + + /// Returns the health (0.0 to 1.0) of this boss bar. + get-health: func() -> f32; + + /// Sets the health (0.0 to 1.0) of this boss bar. + set-health: func(health: f32); + + /// Returns the color of this boss bar. + get-color: func() -> boss-bar-color; + + /// Sets the color of this boss bar. + set-color: func(color: boss-bar-color); + + /// Returns the division of this boss bar. + get-division: func() -> boss-bar-division; + + /// Sets the division of this boss bar. + set-division: func(division: boss-bar-division); + + /// Adds a player to this boss bar, making it visible to them. + add-player: func(player: player); + + /// Removes a player from this boss bar, hiding it from them. + remove-player: func(player: player); + + /// Returns a list of all players currently seeing this boss bar. + get-players: func() -> list; + + /// Removes this boss bar from all players and cleans it up. + remove-all: func(); + } +} diff --git a/pumpkin-plugin-wit/v0.1/gui.wit b/pumpkin-plugin-wit/v0.1/gui.wit index 4f57442f3..6dee603b5 100644 --- a/pumpkin-plugin-wit/v0.1/gui.wit +++ b/pumpkin-plugin-wit/v0.1/gui.wit @@ -34,5 +34,17 @@ interface gui { constructor(%type: gui-type, title: text-component); set-item: func(slot: u32, item: item-stack); get-item: func(slot: u32) -> option; + + /// Returns the type of this GUI. + get-type: func() -> gui-type; + + /// Returns the title of this GUI. + get-title: func() -> text-component; + + /// Returns the number of slots in this GUI. + get-size: func() -> u32; + + /// Clears all items from the GUI. + clear-items: func(); } } diff --git a/pumpkin-plugin-wit/v0.1/plugin.wit b/pumpkin-plugin-wit/v0.1/plugin.wit index 802c7bf7e..80bf6b193 100644 --- a/pumpkin-plugin-wit/v0.1/plugin.wit +++ b/pumpkin-plugin-wit/v0.1/plugin.wit @@ -33,6 +33,8 @@ world plugin { import %world; /// Provides entity types and manipulation. import entity; + /// Provides boss bar creation and management. + import boss-bar; // --- Plugin-provided exports --- diff --git a/pumpkin-plugin-wit/v0.1/server.wit b/pumpkin-plugin-wit/v0.1/server.wit index f71f82bbf..fff99a75a 100644 --- a/pumpkin-plugin-wit/v0.1/server.wit +++ b/pumpkin-plugin-wit/v0.1/server.wit @@ -1,5 +1,7 @@ interface server { use player.{player}; + use common.{game-mode}; + use %world.{%world}; /// Represents the difficulty setting of a Minecraft server. enum difficulty { @@ -37,5 +39,44 @@ interface server { /// Searches for a player by their UUID string. get-player-by-uuid: func(id: string) -> option; + + /// Returns a list of all worlds on this server. + get-all-worlds: func() -> list<%world>; + + /// Gets a world by its dimension name (e.g., "minecraft:overworld"). + get-world-by-name: func(name: string) -> option<%world>; + + /// Broadcasts a system chat message to all players. + broadcast: func(message: string); + + /// Returns the maximum number of players allowed on the server. + get-max-players: func() -> u32; + + /// Returns whether the server is in hardcore mode. + is-hardcore: func() -> bool; + + /// Returns whether the server is in online mode (authenticates with Mojang). + is-online-mode: func() -> bool; + + /// Returns the server's Message of the Day (MOTD). + get-motd: func() -> string; + + /// Returns whether the server has a whitelist enabled. + has-whitelist: func() -> bool; + + /// Returns whether the server allows the Nether dimension. + get-allow-nether: func() -> bool; + + /// Returns whether the server allows the End dimension. + get-allow-end: func() -> bool; + + /// Returns the server's maximum view distance. + get-view-distance: func() -> u8; + + /// Returns the server's maximum simulation distance. + get-simulation-distance: func() -> u8; + + /// Returns the server's default gamemode. + get-default-gamemode: func() -> game-mode; } } diff --git a/pumpkin/src/plugin/api/events/player/player_interact_event.rs b/pumpkin/src/plugin/api/events/player/player_interact_event.rs index d7fe7b14b..db3da2bcd 100644 --- a/pumpkin/src/plugin/api/events/player/player_interact_event.rs +++ b/pumpkin/src/plugin/api/events/player/player_interact_event.rs @@ -35,7 +35,6 @@ impl PlayerInteractEvent { /// /// - `player`: A reference-counted pointer to the player who triggered the event. /// - `action`: The type of interaction performed. - /// - `item`: A reference-counted, mutex-protected item stack used during the interaction. /// - `block`: The block that was interacted with. /// - `clicked_pos`: The optional position of the block that was clicked. /// diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/state.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/state.rs index e02635110..1ee3ef93b 100644 --- a/pumpkin/src/plugin/loader/wasm/wasm_host/state.rs +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/state.rs @@ -36,6 +36,9 @@ pub type EntityResource = WasmResource>; pub type WorldResource = WasmResource>; pub type ScoreboardResource = WasmResource>; pub type GuiResource = WasmResource>>; +pub type BossBarResource = WasmResource< + Arc>, +>; pub type TextComponentResource = WasmResource; pub type CommandResource = WasmResource; pub type CommandSenderResource = WasmResource; @@ -125,6 +128,16 @@ impl PluginHostState { Ok(wasmtime::component::Resource::new_own(resource.rep())) } + pub fn add_boss_bar( + &mut self, + provider: Arc< + Mutex, + >, + ) -> wasmtime::Result> { + let resource = self.resource_table.push(BossBarResource { provider })?; + Ok(wasmtime::component::Resource::new_own(resource.rep())) + } + pub fn add_text_component( &mut self, provider: TextComponent, diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/boss_bar.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/boss_bar.rs new file mode 100644 index 000000000..66d0a2e10 --- /dev/null +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/boss_bar.rs @@ -0,0 +1,239 @@ +use crate::plugin::loader::wasm::wasm_host::{ + state::{BossBarResource, PlayerResource, PluginHostState}, + wit::v0_1::pumpkin::plugin::boss_bar::{ + self, BossBar, BossBarColor as WitColor, BossBarDivision as WitDivision, + }, +}; +use crate::server::Server; +use crate::world::bossbar::{Bossbar, BossbarColor, BossbarDivisions}; +use std::sync::{Arc, Weak}; +use tokio::sync::Mutex; +use uuid::Uuid; +use wasmtime::component::Resource; + +pub struct PluginBossBar { + pub bossbar: Bossbar, + pub players: Vec, + pub server: Weak, +} + +impl PluginBossBar { + #[must_use] + pub const fn new(bossbar: Bossbar, server: Weak) -> Self { + Self { + bossbar, + players: Vec::new(), + server, + } + } +} + +fn player_from_resource( + state: &PluginHostState, + player: &Resource< + crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::player::Player, + >, +) -> wasmtime::Result> { + state + .resource_table + .get::(&Resource::new_own(player.rep())) + .map_err(wasmtime::Error::from) + .map(|resource| resource.provider.clone()) +} + +impl PluginHostState { + fn get_bossbar_res(&self, res: &Resource) -> wasmtime::Result<&BossBarResource> { + self.resource_table + .get::(&Resource::new_own(res.rep())) + .map_err(wasmtime::Error::from) + } +} + +impl boss_bar::Host for PluginHostState {} + +impl boss_bar::HostBossBar for PluginHostState { + async fn new( + &mut self, + title: Resource< + crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::text::TextComponent, + >, + color: WitColor, + division: WitDivision, + ) -> wasmtime::Result> { + let title = self.get_text_provider(&title)?; + let mut bossbar = Bossbar::new(title); + + bossbar.color = match color { + WitColor::Pink => BossbarColor::Pink, + WitColor::Blue => BossbarColor::Blue, + WitColor::Red => BossbarColor::Red, + WitColor::Green => BossbarColor::Green, + WitColor::Yellow => BossbarColor::Yellow, + WitColor::Purple => BossbarColor::Purple, + WitColor::White => BossbarColor::White, + }; + + bossbar.division = match division { + WitDivision::NoDivision => BossbarDivisions::NoDivision, + WitDivision::Notches6 => BossbarDivisions::Notches6, + WitDivision::Notches10 => BossbarDivisions::Notches10, + WitDivision::Notches12 => BossbarDivisions::Notches12, + WitDivision::Notches20 => BossbarDivisions::Notches20, + }; + + let server = self.server.as_ref().expect("server not available").clone(); + let plugin_bossbar = Arc::new(Mutex::new(PluginBossBar::new( + bossbar, + Arc::downgrade(&server), + ))); + self.add_boss_bar(plugin_bossbar) + } + + async fn get_title( + &mut self, + res: Resource, + ) -> wasmtime::Result< + Resource< + crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::text::TextComponent, + >, + > { + let title = { + let bossbar = self.get_bossbar_res(&res)?.provider.lock().await; + bossbar.bossbar.title.clone() + }; + self.add_text_component(title) + .map_err(|_| wasmtime::Error::msg("Failed to add text component")) + } + + async fn set_title( + &mut self, + res: Resource, + title: Resource< + crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::text::TextComponent, + >, + ) -> wasmtime::Result<()> { + let title = self.get_text_provider(&title)?; + let mut pbb = self.get_bossbar_res(&res)?.provider.lock().await; + pbb.bossbar.title = title.clone(); + if let Some(server) = pbb.server.upgrade() { + for uuid in &pbb.players { + if let Some(player) = server.get_player_by_uuid(*uuid) { + player + .update_bossbar_title(&pbb.bossbar.uuid, title.clone()) + .await; + } + } + } + Ok(()) + } + + async fn get_health(&mut self, res: Resource) -> wasmtime::Result { + let pbb = self.get_bossbar_res(&res)?.provider.lock().await; + Ok(pbb.bossbar.health) + } + + async fn set_health(&mut self, res: Resource, health: f32) -> wasmtime::Result<()> { + let mut pbb = self.get_bossbar_res(&res)?.provider.lock().await; + pbb.bossbar.health = health; + if let Some(server) = pbb.server.upgrade() { + for uuid in &pbb.players { + if let Some(player) = server.get_player_by_uuid(*uuid) { + player + .update_bossbar_health(&pbb.bossbar.uuid, health) + .await; + } + } + } + Ok(()) + } + + async fn get_color(&mut self, _res: Resource) -> wasmtime::Result { + todo!() + } + async fn set_color( + &mut self, + _res: Resource, + _color: WitColor, + ) -> wasmtime::Result<()> { + todo!() + } + async fn get_division(&mut self, _res: Resource) -> wasmtime::Result { + todo!() + } + async fn set_division( + &mut self, + _res: Resource, + _division: WitDivision, + ) -> wasmtime::Result<()> { + todo!() + } + async fn get_players( + &mut self, + _res: Resource, + ) -> wasmtime::Result< + Vec< + Resource< + crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::player::Player, + >, + >, + > { + todo!() + } + + async fn add_player( + &mut self, + res: Resource, + player: Resource< + crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::player::Player, + >, + ) -> wasmtime::Result<()> { + let mut pbb = self.get_bossbar_res(&res)?.provider.lock().await; + let player = player_from_resource(self, &player)?; + let uuid = player.gameprofile.id; + + if !pbb.players.contains(&uuid) { + pbb.players.push(uuid); + player.send_bossbar(&pbb.bossbar).await; + } + Ok(()) + } + + async fn remove_player( + &mut self, + res: Resource, + player: Resource< + crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::player::Player, + >, + ) -> wasmtime::Result<()> { + let mut pbb = self.get_bossbar_res(&res)?.provider.lock().await; + let player = player_from_resource(self, &player)?; + let uuid = player.gameprofile.id; + + if let Some(idx) = pbb.players.iter().position(|&x| x == uuid) { + pbb.players.remove(idx); + player.remove_bossbar(pbb.bossbar.uuid).await; + } + Ok(()) + } + + async fn remove_all(&mut self, res: Resource) -> wasmtime::Result<()> { + let pbb = self.get_bossbar_res(&res)?.provider.lock().await; + if let Some(server) = pbb.server.upgrade() { + for uuid in &pbb.players { + if let Some(player) = server.get_player_by_uuid(*uuid) { + player.remove_bossbar(pbb.bossbar.uuid).await; + } + } + } + Ok(()) + } + + async fn drop(&mut self, res: Resource) -> wasmtime::Result<()> { + let rep = res.rep(); + self.remove_all(res).await?; + self.resource_table + .delete::(Resource::new_own(rep)) + .map_err(wasmtime::Error::from)?; + Ok(()) + } +} diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/gui.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/gui.rs index b3751fb25..9810497bb 100644 --- a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/gui.rs +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/gui.rs @@ -117,6 +117,66 @@ impl gui::HostGui for PluginHostState { } } + async fn get_type(&mut self, res: Resource) -> wasmtime::Result { + let gui = self.get_gui_res(&res)?.provider.lock().await; + Ok(match gui.window_type { + pumpkin_data::screen::WindowType::Generic9x1 => GuiType::Generic9x1, + pumpkin_data::screen::WindowType::Generic9x2 => GuiType::Generic9x2, + pumpkin_data::screen::WindowType::Generic9x3 => GuiType::Generic9x3, + pumpkin_data::screen::WindowType::Generic9x4 => GuiType::Generic9x4, + pumpkin_data::screen::WindowType::Generic9x5 => GuiType::Generic9x5, + pumpkin_data::screen::WindowType::Generic9x6 => GuiType::Generic9x6, + pumpkin_data::screen::WindowType::Generic3x3 => GuiType::Generic3x3, + pumpkin_data::screen::WindowType::Crafter3x3 => GuiType::Crafter3x3, + pumpkin_data::screen::WindowType::Anvil => GuiType::Anvil, + pumpkin_data::screen::WindowType::Beacon => GuiType::Beacon, + pumpkin_data::screen::WindowType::BlastFurnace => GuiType::BlastFurnace, + pumpkin_data::screen::WindowType::BrewingStand => GuiType::BrewingStand, + pumpkin_data::screen::WindowType::Crafting => GuiType::Crafting, + pumpkin_data::screen::WindowType::Enchantment => GuiType::Enchantment, + pumpkin_data::screen::WindowType::Furnace => GuiType::Furnace, + pumpkin_data::screen::WindowType::Grindstone => GuiType::Grindstone, + pumpkin_data::screen::WindowType::Hopper => GuiType::Hopper, + pumpkin_data::screen::WindowType::Lectern => GuiType::Lectern, + pumpkin_data::screen::WindowType::Loom => GuiType::Loom, + pumpkin_data::screen::WindowType::Merchant => GuiType::Merchant, + pumpkin_data::screen::WindowType::ShulkerBox => GuiType::ShulkerBox, + pumpkin_data::screen::WindowType::Smithing => GuiType::Smithing, + pumpkin_data::screen::WindowType::Smoker => GuiType::Smoker, + pumpkin_data::screen::WindowType::CartographyTable => GuiType::CartographyTable, + pumpkin_data::screen::WindowType::Stonecutter => GuiType::Stonecutter, + }) + } + + async fn get_title( + &mut self, + res: Resource, + ) -> wasmtime::Result< + Resource< + crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::text::TextComponent, + >, + > { + let title = { + let gui = self.get_gui_res(&res)?.provider.lock().await; + gui.title.clone() + }; + self.add_text_component(title) + .map_err(|_| wasmtime::Error::msg("Failed to add text component resource")) + } + + async fn get_size(&mut self, res: Resource) -> wasmtime::Result { + use pumpkin_world::inventory::Inventory; + let gui = self.get_gui_res(&res)?.provider.lock().await; + Ok(gui.inventory.size() as u32) + } + + async fn clear_items(&mut self, res: Resource) -> wasmtime::Result<()> { + use pumpkin_world::inventory::Clearable; + let gui = self.get_gui_res(&res)?.provider.lock().await; + gui.inventory.clear().await; + Ok(()) + } + async fn drop(&mut self, rep: Resource) -> wasmtime::Result<()> { self.resource_table .delete::(Resource::new_own(rep.rep())) diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/mod.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/mod.rs index c4ea9c920..8bc77cec8 100644 --- a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/mod.rs +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/mod.rs @@ -7,6 +7,7 @@ use wasmtime::component::{HasSelf, InstancePre, Linker, bindgen}; use wasmtime::{Engine, Store}; pub mod block_entity; +pub mod boss_bar; pub mod commands; pub mod common; pub mod context; diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/server.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/server.rs index 000a9df96..a08f70833 100644 --- a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/server.rs +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/server.rs @@ -113,6 +113,160 @@ impl pumpkin::plugin::server::HostServer for PluginHostState { .transpose() } + async fn get_all_worlds( + &mut self, + _rep: Resource, + ) -> wasmtime::Result>> { + let server = self + .server + .as_ref() + .ok_or_else(|| wasmtime::Error::msg("Server not available"))?; + + Ok(server + .worlds + .load() + .iter() + .map(|world| { + self.add_world(world.clone()) + .expect("failed to add world resource") + }) + .collect()) + } + + async fn get_world_by_name( + &mut self, + _rep: Resource, + name: String, + ) -> wasmtime::Result>> { + let server = self + .server + .as_ref() + .ok_or_else(|| wasmtime::Error::msg("Server not available"))?; + + Ok(server + .worlds + .load() + .iter() + .find(|world| world.dimension.minecraft_name == name) + .map(|world| { + self.add_world(world.clone()) + .expect("failed to add world resource") + })) + } + + async fn broadcast(&mut self, _rep: Resource, message: String) -> wasmtime::Result<()> { + let server = self + .server + .as_ref() + .ok_or_else(|| wasmtime::Error::msg("Server not available"))?; + + server + .broadcast_message( + &pumpkin_util::text::TextComponent::text(message), + &pumpkin_util::text::TextComponent::text("Server"), + 0, + None, + ) + .await; + + Ok(()) + } + + async fn get_max_players(&mut self, _rep: Resource) -> wasmtime::Result { + let server = self + .server + .as_ref() + .ok_or_else(|| wasmtime::Error::msg("Server not available"))?; + + Ok(server.basic_config.max_players) + } + + async fn is_hardcore(&mut self, _rep: Resource) -> wasmtime::Result { + let server = self + .server + .as_ref() + .ok_or_else(|| wasmtime::Error::msg("Server not available"))?; + + Ok(server.basic_config.hardcore) + } + + async fn is_online_mode(&mut self, _rep: Resource) -> wasmtime::Result { + let server = self + .server + .as_ref() + .ok_or_else(|| wasmtime::Error::msg("Server not available"))?; + + Ok(server.advanced_config.networking.authentication.enabled) + } + + async fn get_motd(&mut self, _rep: Resource) -> wasmtime::Result { + let server = self + .server + .as_ref() + .ok_or_else(|| wasmtime::Error::msg("Server not available"))?; + + Ok(server.basic_config.motd.clone()) + } + + async fn has_whitelist(&mut self, _rep: Resource) -> wasmtime::Result { + let server = self + .server + .as_ref() + .ok_or_else(|| wasmtime::Error::msg("Server not available"))?; + + Ok(server.basic_config.white_list) + } + + async fn get_allow_nether(&mut self, _rep: Resource) -> wasmtime::Result { + let server = self + .server + .as_ref() + .ok_or_else(|| wasmtime::Error::msg("Server not available"))?; + + Ok(server.basic_config.allow_nether) + } + + async fn get_allow_end(&mut self, _rep: Resource) -> wasmtime::Result { + let server = self + .server + .as_ref() + .ok_or_else(|| wasmtime::Error::msg("Server not available"))?; + + Ok(server.basic_config.allow_end) + } + + async fn get_view_distance(&mut self, _rep: Resource) -> wasmtime::Result { + let server = self + .server + .as_ref() + .ok_or_else(|| wasmtime::Error::msg("Server not available"))?; + + Ok(server.basic_config.view_distance.get()) + } + + async fn get_simulation_distance(&mut self, _rep: Resource) -> wasmtime::Result { + let server = self + .server + .as_ref() + .ok_or_else(|| wasmtime::Error::msg("Server not available"))?; + + Ok(server.basic_config.simulation_distance.get()) + } + + async fn get_default_gamemode( + &mut self, + _rep: Resource, + ) -> wasmtime::Result { + let server = self + .server + .as_ref() + .ok_or_else(|| wasmtime::Error::msg("Server not available"))?; + + Ok(super::events::to_wasm_game_mode( + server.basic_config.default_gamemode, + )) + } + async fn drop(&mut self, rep: Resource) -> wasmtime::Result<()> { self.resource_table .delete::(Resource::new_own(rep.rep()))