feat: add more plugin stuff
This commit is contained in:
parent
f8961d1fe0
commit
1ffb0d855b
@ -161,7 +161,7 @@ mod entity_pose;
|
|||||||
#[cfg(feature = "entity_status")]
|
#[cfg(feature = "entity_status")]
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
#[path = "generated/entity_status.rs"]
|
#[path = "generated/entity_status.rs"]
|
||||||
mod entity_status;
|
pub mod entity_status;
|
||||||
|
|
||||||
#[cfg(feature = "entity_type")]
|
#[cfg(feature = "entity_type")]
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
|
|||||||
160
crates/pumpkin-plugin-api/src/block.rs
Normal file
160
crates/pumpkin-plugin-api/src/block.rs
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
pub use crate::generated::block::BlockType;
|
||||||
|
use crate::wit::pumpkin::plugin::world::{Block, BlockState};
|
||||||
|
|
||||||
|
/// Trait for converting types into a valid Minecraft block registry key (e.g. `"minecraft:stone"`).
|
||||||
|
pub trait IntoBlockKey {
|
||||||
|
/// Returns the namespaced block registry key.
|
||||||
|
fn into_block_key(self) -> String;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoBlockKey for BlockType {
|
||||||
|
fn into_block_key(self) -> String {
|
||||||
|
self.resource_location().to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoBlockKey for &BlockType {
|
||||||
|
fn into_block_key(self) -> String {
|
||||||
|
self.resource_location().to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoBlockKey for &str {
|
||||||
|
fn into_block_key(self) -> String {
|
||||||
|
if self.contains(':') {
|
||||||
|
self.to_string()
|
||||||
|
} else {
|
||||||
|
format!("minecraft:{self}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoBlockKey for String {
|
||||||
|
fn into_block_key(self) -> String {
|
||||||
|
if self.contains(':') {
|
||||||
|
self
|
||||||
|
} else {
|
||||||
|
format!("minecraft:{self}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoBlockKey for &String {
|
||||||
|
fn into_block_key(self) -> String {
|
||||||
|
self.as_str().into_block_key()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extension trait providing typed helper methods on `Block`.
|
||||||
|
pub trait BlockTypeExt {
|
||||||
|
/// Gets a block definition by any valid block key (e.g. `BlockType::Stone`, `"minecraft:stone"`, or `"custom:block"`).
|
||||||
|
#[must_use]
|
||||||
|
fn of(key: impl IntoBlockKey) -> Option<Block>;
|
||||||
|
|
||||||
|
/// Returns the typed `BlockType` if this block corresponds to a known vanilla block,
|
||||||
|
/// or `None` if it is a custom, modded, or newer unknown block.
|
||||||
|
#[must_use]
|
||||||
|
fn get_block_type(&self) -> Option<BlockType>;
|
||||||
|
|
||||||
|
/// Checks if this block matches the given `BlockType`.
|
||||||
|
#[must_use]
|
||||||
|
fn is_block_type(&self, block_type: BlockType) -> bool;
|
||||||
|
|
||||||
|
/// Checks if this block matches the given block key.
|
||||||
|
#[must_use]
|
||||||
|
fn matches_block(&self, key: impl IntoBlockKey) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BlockTypeExt for Block {
|
||||||
|
fn of(key: impl IntoBlockKey) -> Option<Block> {
|
||||||
|
Block::from_name(&key.into_block_key())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_block_type(&self) -> Option<BlockType> {
|
||||||
|
BlockType::from_registry_key(&self.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_block_type(&self, block_type: BlockType) -> bool {
|
||||||
|
self.get_block_type() == Some(block_type)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn matches_block(&self, key: impl IntoBlockKey) -> bool {
|
||||||
|
let expected = key.into_block_key();
|
||||||
|
let expected_clean = expected.strip_prefix("minecraft:").unwrap_or(&expected);
|
||||||
|
let actual_clean = self.name.strip_prefix("minecraft:").unwrap_or(&self.name);
|
||||||
|
expected_clean == actual_clean
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extension trait providing typed helper methods on `BlockState`.
|
||||||
|
pub trait BlockStateTypeExt {
|
||||||
|
/// Returns the typed `BlockType` if this block state corresponds to a known vanilla block,
|
||||||
|
/// or `None` if it is a custom, modded, or newer unknown block.
|
||||||
|
#[must_use]
|
||||||
|
fn get_block_type(&self) -> Option<BlockType>;
|
||||||
|
|
||||||
|
/// Checks if this block state matches the given `BlockType`.
|
||||||
|
#[must_use]
|
||||||
|
fn is_block_type(&self, block_type: BlockType) -> bool;
|
||||||
|
|
||||||
|
/// Checks if this block state matches the given block key.
|
||||||
|
#[must_use]
|
||||||
|
fn matches_block(&self, key: impl IntoBlockKey) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BlockStateTypeExt for BlockState {
|
||||||
|
fn get_block_type(&self) -> Option<BlockType> {
|
||||||
|
BlockType::from_registry_key(&self.block_name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_block_type(&self, block_type: BlockType) -> bool {
|
||||||
|
self.get_block_type() == Some(block_type)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn matches_block(&self, key: impl IntoBlockKey) -> bool {
|
||||||
|
let expected = key.into_block_key();
|
||||||
|
let expected_clean = expected.strip_prefix("minecraft:").unwrap_or(&expected);
|
||||||
|
let actual_clean = self
|
||||||
|
.block_name
|
||||||
|
.strip_prefix("minecraft:")
|
||||||
|
.unwrap_or(&self.block_name);
|
||||||
|
expected_clean == actual_clean
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn block_to_resource_location() {
|
||||||
|
assert_eq!(BlockType::Stone.into_block_key(), "minecraft:stone");
|
||||||
|
assert_eq!(
|
||||||
|
BlockType::DiamondBlock.into_block_key(),
|
||||||
|
"minecraft:diamond_block"
|
||||||
|
);
|
||||||
|
assert_eq!(BlockType::OakLog.into_block_key(), "minecraft:oak_log");
|
||||||
|
assert_eq!(BlockType::Obsidian.into_block_key(), "minecraft:obsidian");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn string_to_resource_location() {
|
||||||
|
assert_eq!("stone".into_block_key(), "minecraft:stone");
|
||||||
|
assert_eq!("minecraft:stone".into_block_key(), "minecraft:stone");
|
||||||
|
assert_eq!("custom:ruby_block".into_block_key(), "custom:ruby_block");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn block_parsing() {
|
||||||
|
assert_eq!(BlockType::from_name("stone"), Some(BlockType::Stone));
|
||||||
|
assert_eq!(
|
||||||
|
BlockType::from_name("minecraft:stone"),
|
||||||
|
Some(BlockType::Stone)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
BlockType::from_name("diamond_block"),
|
||||||
|
Some(BlockType::DiamondBlock)
|
||||||
|
);
|
||||||
|
assert_eq!(BlockType::from_name("custom:ruby_block"), None);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -29,3 +29,23 @@ impl AttributeModifier {
|
|||||||
Self::new(id, amount, ModifierOperation::MultiplyTotal)
|
Self::new(id, amount, ModifierOperation::MultiplyTotal)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use crate::wit::pumpkin::plugin::attributes::Attribute;
|
||||||
|
use crate::wit::pumpkin::plugin::enchantments::AttributeModifierSlot;
|
||||||
|
use crate::wit::pumpkin::plugin::item_stack::ItemAttributeModifier;
|
||||||
|
|
||||||
|
impl ItemAttributeModifier {
|
||||||
|
/// Creates a new item attribute modifier for a specific equipment slot.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn new(
|
||||||
|
attribute: Attribute,
|
||||||
|
modifier: AttributeModifier,
|
||||||
|
slot: AttributeModifierSlot,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
attribute,
|
||||||
|
modifier,
|
||||||
|
slot,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -133,6 +133,32 @@ impl BanIpOptions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extension trait providing typed cooldown helpers for `Player`.
|
||||||
|
pub trait PlayerCooldownExt {
|
||||||
|
/// Sets a client-side item cooldown overlay using any item key or `Item` enum.
|
||||||
|
fn set_cooldown(&self, item: impl crate::item::IntoItemKey, ticks: i32);
|
||||||
|
|
||||||
|
/// Returns the remaining cooldown ticks for an item, if active.
|
||||||
|
fn get_cooldown(&self, item: impl crate::item::IntoItemKey) -> Option<i32>;
|
||||||
|
|
||||||
|
/// Checks if an item is currently on cooldown.
|
||||||
|
fn has_cooldown(&self, item: impl crate::item::IntoItemKey) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PlayerCooldownExt for Player {
|
||||||
|
fn set_cooldown(&self, item: impl crate::item::IntoItemKey, ticks: i32) {
|
||||||
|
self.set_item_cooldown(&item.into_item_key(), ticks);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_cooldown(&self, item: impl crate::item::IntoItemKey) -> Option<i32> {
|
||||||
|
self.get_item_cooldown(&item.into_item_key())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_cooldown(&self, item: impl crate::item::IntoItemKey) -> bool {
|
||||||
|
self.has_item_cooldown(&item.into_item_key())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{CustomStatistic, StatisticCategory};
|
use crate::{CustomStatistic, StatisticCategory};
|
||||||
|
|||||||
4835
crates/pumpkin-plugin-api/src/generated/block.rs
Normal file
4835
crates/pumpkin-plugin-api/src/generated/block.rs
Normal file
File diff suppressed because it is too large
Load Diff
6213
crates/pumpkin-plugin-api/src/generated/item.rs
Normal file
6213
crates/pumpkin-plugin-api/src/generated/item.rs
Normal file
File diff suppressed because it is too large
Load Diff
4
crates/pumpkin-plugin-api/src/generated/mod.rs
Normal file
4
crates/pumpkin-plugin-api/src/generated/mod.rs
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
#![allow(missing_docs)]
|
||||||
|
|
||||||
|
pub mod block;
|
||||||
|
pub mod item;
|
||||||
126
crates/pumpkin-plugin-api/src/item.rs
Normal file
126
crates/pumpkin-plugin-api/src/item.rs
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
pub use crate::generated::item::Item;
|
||||||
|
use crate::wit::pumpkin::plugin::item_stack::ItemStack;
|
||||||
|
|
||||||
|
/// Trait for converting types into a valid Minecraft item registry key (e.g. `"minecraft:diamond"`).
|
||||||
|
pub trait IntoItemKey {
|
||||||
|
/// Returns the namespaced item registry key.
|
||||||
|
fn into_item_key(self) -> String;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoItemKey for Item {
|
||||||
|
fn into_item_key(self) -> String {
|
||||||
|
self.resource_location().to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoItemKey for &Item {
|
||||||
|
fn into_item_key(self) -> String {
|
||||||
|
self.resource_location().to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoItemKey for &str {
|
||||||
|
fn into_item_key(self) -> String {
|
||||||
|
if self.contains(':') {
|
||||||
|
self.to_string()
|
||||||
|
} else {
|
||||||
|
format!("minecraft:{self}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoItemKey for String {
|
||||||
|
fn into_item_key(self) -> String {
|
||||||
|
if self.contains(':') {
|
||||||
|
self
|
||||||
|
} else {
|
||||||
|
format!("minecraft:{self}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoItemKey for &String {
|
||||||
|
fn into_item_key(self) -> String {
|
||||||
|
self.as_str().into_item_key()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extension trait providing typed helper constructors and utilities on `ItemStack`.
|
||||||
|
pub trait ItemStackExt {
|
||||||
|
/// Creates a new `ItemStack` from any valid item key (e.g. `Item::Diamond`, `"minecraft:diamond"`, or `"custom:item"`).
|
||||||
|
#[must_use]
|
||||||
|
fn of(item: impl IntoItemKey, count: u8) -> Self;
|
||||||
|
|
||||||
|
/// Returns the typed `Item` enum if this item stack corresponds to a known vanilla item,
|
||||||
|
/// or `None` if it is a custom, modded, or newer unknown item.
|
||||||
|
#[must_use]
|
||||||
|
fn get_item(&self) -> Option<Item>;
|
||||||
|
|
||||||
|
/// Returns whether this item matches the specified typed `Item` enum.
|
||||||
|
#[must_use]
|
||||||
|
fn is_item(&self, item: Item) -> bool;
|
||||||
|
|
||||||
|
/// Checks if this item stack matches a given item (either `Item` enum or string identifier).
|
||||||
|
#[must_use]
|
||||||
|
fn matches_item(&self, item: impl IntoItemKey) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ItemStackExt for ItemStack {
|
||||||
|
fn of(item: impl IntoItemKey, count: u8) -> Self {
|
||||||
|
Self::new(&item.into_item_key(), count)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_item(&self) -> Option<Item> {
|
||||||
|
Item::from_registry_key(&self.get_registry_key())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_item(&self, item: Item) -> bool {
|
||||||
|
self.get_item() == Some(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn matches_item(&self, item: impl IntoItemKey) -> bool {
|
||||||
|
let expected = item.into_item_key();
|
||||||
|
let actual = self.get_registry_key();
|
||||||
|
let expected_clean = expected.strip_prefix("minecraft:").unwrap_or(&expected);
|
||||||
|
let actual_clean = actual.strip_prefix("minecraft:").unwrap_or(&actual);
|
||||||
|
expected_clean == actual_clean
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn item_to_resource_location() {
|
||||||
|
assert_eq!(Item::Diamond.into_item_key(), "minecraft:diamond");
|
||||||
|
assert_eq!(
|
||||||
|
Item::NetheriteSword.into_item_key(),
|
||||||
|
"minecraft:netherite_sword"
|
||||||
|
);
|
||||||
|
assert_eq!(Item::AcaciaBoat.into_item_key(), "minecraft:acacia_boat");
|
||||||
|
assert_eq!(
|
||||||
|
Item::AllaySpawnEgg.into_item_key(),
|
||||||
|
"minecraft:allay_spawn_egg"
|
||||||
|
);
|
||||||
|
assert_eq!(Item::Tnt.into_item_key(), "minecraft:tnt");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn string_to_resource_location() {
|
||||||
|
assert_eq!("diamond".into_item_key(), "minecraft:diamond");
|
||||||
|
assert_eq!("minecraft:diamond".into_item_key(), "minecraft:diamond");
|
||||||
|
assert_eq!("custom:laser_gun".into_item_key(), "custom:laser_gun");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn item_parsing() {
|
||||||
|
assert_eq!(Item::from_name("diamond"), Some(Item::Diamond));
|
||||||
|
assert_eq!(Item::from_name("minecraft:diamond"), Some(Item::Diamond));
|
||||||
|
assert_eq!(
|
||||||
|
Item::from_name("netherite_sword"),
|
||||||
|
Some(Item::NetheriteSword)
|
||||||
|
);
|
||||||
|
assert_eq!(Item::from_name("custom:magic_wand"), None);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -74,6 +74,8 @@ use crate::{
|
|||||||
text::TextComponent,
|
text::TextComponent,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Block definitions and block type helpers.
|
||||||
|
pub mod block;
|
||||||
/// Plugin command registration and handling utilities.
|
/// Plugin command registration and handling utilities.
|
||||||
pub mod commands;
|
pub mod commands;
|
||||||
/// Datapack management and query utilities.
|
/// Datapack management and query utilities.
|
||||||
@ -87,8 +89,11 @@ pub mod events;
|
|||||||
mod ext;
|
mod ext;
|
||||||
/// Bedrock UI form builders.
|
/// Bedrock UI form builders.
|
||||||
pub mod forms;
|
pub mod forms;
|
||||||
|
pub(crate) mod generated;
|
||||||
/// Unified inventory and container management utilities.
|
/// Unified inventory and container management utilities.
|
||||||
pub mod inventory;
|
pub mod inventory;
|
||||||
|
/// Typed item definitions and `ItemStack` construction helpers.
|
||||||
|
pub mod item;
|
||||||
/// Specialized mob entity wrappers and helpers.
|
/// Specialized mob entity wrappers and helpers.
|
||||||
pub mod mobs;
|
pub mod mobs;
|
||||||
/// Constants for plugin permissions.
|
/// Constants for plugin permissions.
|
||||||
@ -118,15 +123,18 @@ pub use wit::pumpkin::plugin::{
|
|||||||
context::{self, Context, MarketplaceMetadata, Server},
|
context::{self, Context, MarketplaceMetadata, Server},
|
||||||
damage_types as damage_types_wit, data_components, datapack as datapack_wit,
|
damage_types as damage_types_wit, data_components, datapack as datapack_wit,
|
||||||
display as display_wit, enchantments as enchantments_wit, entity,
|
display as display_wit, enchantments as enchantments_wit, entity,
|
||||||
|
entity_statuses as entity_statuses_wit,
|
||||||
entity_types::EntityType,
|
entity_types::EntityType,
|
||||||
event::{self as events_wit, EventType},
|
event::{self as events_wit, EventType},
|
||||||
gui, i18n, inventory as inventory_wit, ipc, item_stack, java_dialogs, java_packets, particles,
|
game_events as game_events_wit, gui, i18n, inventory as inventory_wit, ipc, item_stack,
|
||||||
permission, player, recipe as recipe_wit, scoreboard, screens as screens_wit, server,
|
java_dialogs, java_packets, particles, permission, player, potions as potions_wit,
|
||||||
statistics as statistics_wit, text, uuid, world,
|
recipe as recipe_wit, scoreboard, screens as screens_wit, server, statistics as statistics_wit,
|
||||||
|
text, uuid, world,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Convenience re-exports of commonly-used plugin types so plugin authors can
|
// Convenience re-exports of commonly-used plugin types so plugin authors can
|
||||||
// name them directly (e.g. build an `ItemStack` for a GUI or `/give`).
|
// name them directly (e.g. build an `ItemStack` for a GUI or `/give`).
|
||||||
|
pub use block::{BlockStateTypeExt, BlockType, BlockTypeExt, IntoBlockKey};
|
||||||
pub use damage_types_wit::DamageType;
|
pub use damage_types_wit::DamageType;
|
||||||
pub use datapack::{DatapackInfo, DatapackManager, EnablePosition};
|
pub use datapack::{DatapackInfo, DatapackManager, EnablePosition};
|
||||||
pub use display::{
|
pub use display::{
|
||||||
@ -139,15 +147,19 @@ pub use enchantment::{
|
|||||||
AttributeModifierSlot, CustomEnchantment, CustomEnchantmentValue, Enchantment,
|
AttributeModifierSlot, CustomEnchantment, CustomEnchantmentValue, Enchantment,
|
||||||
EnchantmentBuilder, EnchantmentError, EnchantmentManager, RegistrableEnchantment,
|
EnchantmentBuilder, EnchantmentError, EnchantmentManager, RegistrableEnchantment,
|
||||||
};
|
};
|
||||||
|
pub use entity_statuses_wit::EntityStatus;
|
||||||
pub use events::{EventHandler, FromIntoEvent};
|
pub use events::{EventHandler, FromIntoEvent};
|
||||||
pub use ext::player::PlayerEnderChestExt;
|
pub use ext::player::{PlayerCooldownExt, PlayerEnderChestExt};
|
||||||
|
pub use game_events_wit::GameEvent;
|
||||||
pub use inventory::{Inventory, PlayerInventory};
|
pub use inventory::{Inventory, PlayerInventory};
|
||||||
|
pub use item::{IntoItemKey, Item, ItemStackExt};
|
||||||
pub use mobs::{
|
pub use mobs::{
|
||||||
Ageable, AgeableData, Cat, CatData, Creeper, CreeperData, DyeColor, Enderman, EndermanData,
|
Ageable, AgeableData, Cat, CatData, Creeper, CreeperData, DyeColor, Enderman, EndermanData,
|
||||||
EntityCastExt, Fox, FoxData, IronGolem, IronGolemData, MobCast, MobData, Sheep, SheepData,
|
EntityCastExt, Fox, FoxData, IronGolem, IronGolemData, MobCast, MobData, Sheep, SheepData,
|
||||||
Shulker, ShulkerData, Slime, SlimeData, Villager, VillagerData, VillagerProfession, Wolf,
|
Shulker, ShulkerData, Slime, SlimeData, Villager, VillagerData, VillagerProfession, Wolf,
|
||||||
WolfData, Zombie, ZombieData,
|
WolfData, Zombie, ZombieData,
|
||||||
};
|
};
|
||||||
|
pub use potions_wit::PotionType;
|
||||||
pub use recipe::{
|
pub use recipe::{
|
||||||
CookingRecipeBuilder, Ingredient, RecipeCategory, RecipeError, RecipeManager,
|
CookingRecipeBuilder, Ingredient, RecipeCategory, RecipeError, RecipeManager,
|
||||||
RegistrableRecipe, ShapedRecipeBuilder, ShapelessRecipeBuilder,
|
RegistrableRecipe, ShapedRecipeBuilder, ShapelessRecipeBuilder,
|
||||||
@ -155,7 +167,8 @@ pub use recipe::{
|
|||||||
pub use screens_wit::Screen;
|
pub use screens_wit::Screen;
|
||||||
pub use statistics_wit::{CustomStatistic, StatisticCategory};
|
pub use statistics_wit::{CustomStatistic, StatisticCategory};
|
||||||
pub use team::{PlayerTeamExt, ScoreboardTeamExt, Team, TeamSettingsBuilder};
|
pub use team::{PlayerTeamExt, ScoreboardTeamExt, Team, TeamSettingsBuilder};
|
||||||
pub use wit::pumpkin::plugin::item_stack::ItemStack;
|
pub use wit::pumpkin::plugin::attributes::{Attribute, AttributeModifier, ModifierOperation};
|
||||||
|
pub use wit::pumpkin::plugin::item_stack::{ItemAttributeModifier, ItemStack};
|
||||||
pub use wit::pumpkin::plugin::player::Player;
|
pub use wit::pumpkin::plugin::player::Player;
|
||||||
pub use wit::pumpkin::plugin::scoreboard::{CollisionRule, NametagVisibility, TeamSettings};
|
pub use wit::pumpkin::plugin::scoreboard::{CollisionRule, NametagVisibility, TeamSettings};
|
||||||
pub use wit::pumpkin::plugin::server::Dimension;
|
pub use wit::pumpkin::plugin::server::Dimension;
|
||||||
|
|||||||
@ -11,6 +11,7 @@ pub struct CDeleteChat<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> CDeleteChat<'a> {
|
impl<'a> CDeleteChat<'a> {
|
||||||
|
/// Creates a new `CDeleteChat` packet using a raw signature ID `VarInt`.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn from_id(signature_id: VarInt) -> Self {
|
pub const fn from_id(signature_id: VarInt) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@ -19,6 +20,21 @@ impl<'a> CDeleteChat<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a new `CDeleteChat` packet using a 0-indexed signature cache ID.
|
||||||
|
///
|
||||||
|
/// In Minecraft's `MessageSignature.Packed`, cached signatures are written as `cache_id + 1`.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn from_cache_id(cache_id: i32) -> Self {
|
||||||
|
Self {
|
||||||
|
signature_id: VarInt(cache_id + 1),
|
||||||
|
signature: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new `CDeleteChat` packet using a full 256-byte message signature.
|
||||||
|
///
|
||||||
|
/// In Minecraft's `MessageSignature.Packed`, a full signature is written as `VarInt(0)`
|
||||||
|
/// followed by the 256 raw signature bytes.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn from_signature(signature: &'a [u8]) -> Self {
|
pub const fn from_signature(signature: &'a [u8]) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
|
use crate::ClientPacket;
|
||||||
use crate::Link;
|
use crate::Link;
|
||||||
|
use crate::ser::NetworkWriteExt;
|
||||||
use pumpkin_data::packet::clientbound::play::SERVER_LINKS;
|
use pumpkin_data::packet::clientbound::play::SERVER_LINKS;
|
||||||
use pumpkin_macros::java_packet;
|
use pumpkin_macros::java_packet;
|
||||||
|
use pumpkin_util::version::JavaMinecraftVersion;
|
||||||
|
|
||||||
#[java_packet(SERVER_LINKS)]
|
#[java_packet(SERVER_LINKS)]
|
||||||
pub struct CPlayServerLinks<'a> {
|
pub struct CPlayServerLinks<'a> {
|
||||||
@ -13,3 +16,17 @@ impl<'a> CPlayServerLinks<'a> {
|
|||||||
Self { links }
|
Self { links }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ClientPacket for CPlayServerLinks<'_> {
|
||||||
|
fn write_packet_data(
|
||||||
|
&self,
|
||||||
|
mut write: impl std::io::Write,
|
||||||
|
_version: &JavaMinecraftVersion,
|
||||||
|
) -> Result<(), crate::ser::WritingError> {
|
||||||
|
write.write_var_int(&crate::VarInt(self.links.len() as i32))?;
|
||||||
|
for link in self.links {
|
||||||
|
link.write(&mut write)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -25,7 +25,6 @@ use pumpkin_util::random::xoroshiro128::Xoroshiro;
|
|||||||
use pumpkin_util::random::{RandomGenerator, get_seed};
|
use pumpkin_util::random::{RandomGenerator, get_seed};
|
||||||
use pumpkin_util::version::JavaMinecraftVersion;
|
use pumpkin_util::version::JavaMinecraftVersion;
|
||||||
use rand::RngExt;
|
use rand::RngExt;
|
||||||
use std::pin::Pin;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::atomic::Ordering::Relaxed;
|
use std::sync::atomic::Ordering::Relaxed;
|
||||||
use std::sync::atomic::{AtomicI32, AtomicU8, Ordering};
|
use std::sync::atomic::{AtomicI32, AtomicU8, Ordering};
|
||||||
@ -1305,15 +1304,13 @@ pub trait PathAwareEntity: Mob + Send + Sync {
|
|||||||
) >= 0.0
|
) >= 0.0
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_navigation<'a>(&'a self) -> Pin<Box<dyn Future<Output = bool> + Send + 'a>> {
|
fn is_navigation(&self) -> bool {
|
||||||
Box::pin(async {
|
|
||||||
let navigator = self
|
let navigator = self
|
||||||
.get_mob_entity()
|
.get_mob_entity()
|
||||||
.navigator
|
.navigator
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||||
!navigator.is_idle()
|
!navigator.is_idle()
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: implement
|
// TODO: implement
|
||||||
|
|||||||
@ -2619,6 +2619,29 @@ impl Entity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the number of ticks the entity has been frozen.
|
||||||
|
pub fn set_frozen_ticks(&self, ticks: i32) {
|
||||||
|
let new_frozen_ticks = ticks.clamp(0, Self::MAX_FROZEN_TICKS);
|
||||||
|
self.frozen_ticks.store(new_frozen_ticks, Ordering::Relaxed);
|
||||||
|
let mut bedrock_meta = SyncedActorDataList::new();
|
||||||
|
bedrock_meta.set(
|
||||||
|
entity_data_key::FREEZING_EFFECT_STRENGTH,
|
||||||
|
MetadataValue::Float(new_frozen_ticks as f32),
|
||||||
|
);
|
||||||
|
self.send_meta_data(
|
||||||
|
&[Metadata::new(
|
||||||
|
tracked_data::entity::DATA_TICKS_FROZEN,
|
||||||
|
VarInt(new_frozen_ticks),
|
||||||
|
)],
|
||||||
|
Some(&bedrock_meta),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the number of ticks the entity has been frozen.
|
||||||
|
pub fn get_frozen_ticks(&self) -> i32 {
|
||||||
|
self.frozen_ticks.load(Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets the `Entity` yaw & pitch rotation
|
/// Sets the `Entity` yaw & pitch rotation
|
||||||
pub fn set_rotation(&self, yaw: f32, pitch: f32) {
|
pub fn set_rotation(&self, yaw: f32, pitch: f32) {
|
||||||
// TODO
|
// TODO
|
||||||
|
|||||||
@ -260,15 +260,16 @@ use pumpkin_protocol::codec::var_int::VarInt;
|
|||||||
use pumpkin_protocol::codec::var_long::VarLong;
|
use pumpkin_protocol::codec::var_long::VarLong;
|
||||||
use pumpkin_protocol::codec::var_ulong::VarULong;
|
use pumpkin_protocol::codec::var_ulong::VarULong;
|
||||||
use pumpkin_protocol::java::client::play::{
|
use pumpkin_protocol::java::client::play::{
|
||||||
Animation, CAcknowledgeBlockChange, CActionBar, CAwardStats, CChangeDifficulty,
|
Animation, CAcknowledgeBlockChange, CActionBar, CAwardStats, CBlockUpdate, CChangeDifficulty,
|
||||||
CCloseContainer, CCombatDeath, CCustomPayload, CDisguisedChatMessage, CEntityAnimation,
|
CCloseContainer, CCombatDeath, CCustomPayload, CDisguisedChatMessage, CEntityAnimation,
|
||||||
CEntityPositionSync, CGameEvent, CItemCooldown, CMapItemData, COpenScreen, CParticle,
|
CEntityPositionSync, CEntityVelocity, CGameEvent, CHurtAnimation, CItemCooldown, CMapItemData,
|
||||||
CPlayerAbilities, CPlayerInfoUpdate, CPlayerPosition, CPlayerSpawnPosition, CRespawn,
|
COpenBook, COpenScreen, COpenSignEditor, CParticle, CPlayServerLinks, CPlayerAbilities,
|
||||||
CSetCamera, CSetContainerContent, CSetContainerProperty, CSetContainerSlot, CSetCursorItem,
|
CPlayerInfoUpdate, CPlayerPosition, CPlayerSpawnPosition, CRespawn, CSetCamera,
|
||||||
CSetExperience, CSetHealth, CSetPlayerInventory, CSetSelectedSlot, CSoundEffect, CStopSound,
|
CSetContainerContent, CSetContainerProperty, CSetContainerSlot, CSetCursorItem, CSetExperience,
|
||||||
CSubtitle, CSystemChatMessage, CTabList, CTitleAnimation, CTitleText, CUnloadChunk,
|
CSetHealth, CSetPlayerInventory, CSetSelectedSlot, CSoundEffect, CStopSound, CSubtitle,
|
||||||
CUpdateMobEffect, CUpdateTime, GameEvent, MapIcon, MapPatch, Metadata, PlayerAction,
|
CSystemChatMessage, CTabList, CTitleAnimation, CTitleText, CUnloadChunk, CUpdateMobEffect,
|
||||||
PlayerInfoFlags, PlayerSpawnData, PreviousMessage, Statistic,
|
CUpdateTime, GameEvent, MapIcon, MapPatch, Metadata, PlayerAction, PlayerInfoFlags,
|
||||||
|
PlayerSpawnData, PreviousMessage, Statistic,
|
||||||
};
|
};
|
||||||
use pumpkin_protocol::java::server::play::{
|
use pumpkin_protocol::java::server::play::{
|
||||||
SClickSlot, SContainerButtonClick, SRenameItem, SlotActionType,
|
SClickSlot, SContainerButtonClick, SRenameItem, SlotActionType,
|
||||||
@ -462,6 +463,8 @@ pub struct Player {
|
|||||||
/// Whether the client has reported that it has loaded.
|
/// Whether the client has reported that it has loaded.
|
||||||
pub client_loaded: AtomicBool,
|
pub client_loaded: AtomicBool,
|
||||||
pub bedrock_spawned: AtomicBool,
|
pub bedrock_spawned: AtomicBool,
|
||||||
|
/// Whether the player is frozen in place (movement locked for dialogues/cutscenes).
|
||||||
|
pub is_movement_locked: AtomicBool,
|
||||||
/// The amount of time (in ticks) the client has to report having finished loading before being timed out.
|
/// The amount of time (in ticks) the client has to report having finished loading before being timed out.
|
||||||
pub client_loaded_timeout: AtomicU32,
|
pub client_loaded_timeout: AtomicU32,
|
||||||
/// Counter for tracking chat and command spam. Decays each server tick.
|
/// Counter for tracking chat and command spam. Decays each server tick.
|
||||||
@ -733,6 +736,7 @@ impl Player {
|
|||||||
gamemode: AtomicCell::new(gamemode),
|
gamemode: AtomicCell::new(gamemode),
|
||||||
previous_gamemode: AtomicCell::new(None),
|
previous_gamemode: AtomicCell::new(None),
|
||||||
camera_target_id: AtomicCell::new(None),
|
camera_target_id: AtomicCell::new(None),
|
||||||
|
is_movement_locked: AtomicBool::new(false),
|
||||||
// TODO: Send the CPlayerSpawnPosition packet when the client connects with proper values
|
// TODO: Send the CPlayerSpawnPosition packet when the client connects with proper values
|
||||||
respawn_point: std::sync::Mutex::new(None),
|
respawn_point: std::sync::Mutex::new(None),
|
||||||
sleeping_since: AtomicCell::new(None),
|
sleeping_since: AtomicCell::new(None),
|
||||||
@ -2188,6 +2192,126 @@ impl Player {
|
|||||||
self.try_send_client_packet(&packet);
|
self.try_send_client_packet(&packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Plays a custom sound event by identifier for the player.
|
||||||
|
pub fn play_custom_sound(
|
||||||
|
&self,
|
||||||
|
sound_name: &str,
|
||||||
|
category: SoundCategory,
|
||||||
|
position: &Vector3<f64>,
|
||||||
|
volume: f32,
|
||||||
|
pitch: f32,
|
||||||
|
) {
|
||||||
|
self.play_sound_event(
|
||||||
|
pumpkin_protocol::SoundEvent {
|
||||||
|
sound_name: sound_name.into(),
|
||||||
|
range: None,
|
||||||
|
},
|
||||||
|
category,
|
||||||
|
position,
|
||||||
|
volume,
|
||||||
|
pitch,
|
||||||
|
rand::random::<f64>(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn spawn_particles(
|
||||||
|
&self,
|
||||||
|
particle: Particle,
|
||||||
|
pos: Vector3<f64>,
|
||||||
|
count: u32,
|
||||||
|
offset: Vector3<f32>,
|
||||||
|
max_speed: f32,
|
||||||
|
) {
|
||||||
|
let packet = CParticle::new(
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
pos,
|
||||||
|
offset,
|
||||||
|
max_speed,
|
||||||
|
count as i32,
|
||||||
|
(particle.to_id() as i32).into(),
|
||||||
|
&[],
|
||||||
|
);
|
||||||
|
self.try_send_client_packet(&packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_block_change(&self, location: BlockPos, state_id: u16) {
|
||||||
|
let packet = CBlockUpdate::new(location, (state_id as i32).into());
|
||||||
|
self.try_send_client_packet(&packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset_block_change(&self, location: BlockPos) {
|
||||||
|
let state_id = self.world().get_block_state_id(&location);
|
||||||
|
let packet = CBlockUpdate::new(location, (state_id.as_u16() as i32).into());
|
||||||
|
self.try_send_client_packet(&packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_hurt_animation(&self, yaw: f32) {
|
||||||
|
let packet = CHurtAnimation::new(self.entity_id().into(), yaw);
|
||||||
|
self.try_send_client_packet(&packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn open_book(&self, hand: Hand) {
|
||||||
|
let hand_val = match hand {
|
||||||
|
Hand::Right => 0,
|
||||||
|
Hand::Left => 1,
|
||||||
|
};
|
||||||
|
let packet = COpenBook::new(hand_val.into());
|
||||||
|
self.try_send_client_packet(&packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn open_sign_editor(&self, location: BlockPos, is_front_text: bool) {
|
||||||
|
let packet = COpenSignEditor::new(location, is_front_text);
|
||||||
|
self.try_send_client_packet(&packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_velocity(&self, velocity: Vector3<f64>) {
|
||||||
|
self.living_entity.entity.set_velocity(velocity);
|
||||||
|
self.try_send_client_packet(&CEntityVelocity::new(self.entity_id().into(), velocity));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn apply_knockback(&self, strength: f64, x: f64, z: f64) {
|
||||||
|
let current_vel = self.living_entity.entity.velocity.load();
|
||||||
|
let norm = x.hypot(z);
|
||||||
|
if norm > 0.0 {
|
||||||
|
let vx = current_vel.x / 2.0 - (x / norm) * strength;
|
||||||
|
let vz = current_vel.z / 2.0 - (z / norm) * strength;
|
||||||
|
let vy = (current_vel.y / 2.0 + strength).min(0.4);
|
||||||
|
self.set_velocity(Vector3::new(vx, vy, vz));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_movement_locked(&self, locked: bool) {
|
||||||
|
self.is_movement_locked.store(locked, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_movement_locked(&self) -> bool {
|
||||||
|
self.is_movement_locked.load(Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_freeze_ticks(&self, ticks: i32) {
|
||||||
|
self.living_entity.entity.set_frozen_ticks(ticks);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_freeze_ticks(&self) -> i32 {
|
||||||
|
self.living_entity.entity.get_frozen_ticks()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_game_event(
|
||||||
|
&self,
|
||||||
|
event: pumpkin_protocol::java::client::play::GameEvent,
|
||||||
|
value: f32,
|
||||||
|
) {
|
||||||
|
let packet = CGameEvent::new(event, value);
|
||||||
|
self.try_send_client_packet(&packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sends custom server links to the player (displayed in the client Esc pause menu).
|
||||||
|
pub fn set_server_links(&self, links: &[pumpkin_protocol::Link<'_>]) {
|
||||||
|
let packet = CPlayServerLinks::new(links);
|
||||||
|
self.try_send_client_packet(&packet);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn process_inbound_packets(&self) {
|
pub fn process_inbound_packets(&self) {
|
||||||
const MAX_PACKETS_PER_TICK: usize = 64;
|
const MAX_PACKETS_PER_TICK: usize = 64;
|
||||||
|
|
||||||
@ -2662,6 +2786,32 @@ impl Player {
|
|||||||
self.living_entity.entity.entity_id
|
self.living_entity.entity.entity_id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the player's camera target entity ID.
|
||||||
|
/// If `target_id` matches the player's own entity ID, resets the camera back to the player.
|
||||||
|
pub fn set_camera_entity_id(&self, target_id: i32) {
|
||||||
|
if target_id == self.entity_id() {
|
||||||
|
self.camera_target_id.store(None);
|
||||||
|
self.try_send_client_packet(&CSetCamera::new(self.entity_id().into()));
|
||||||
|
} else {
|
||||||
|
self.camera_target_id.store(Some(target_id));
|
||||||
|
self.try_send_client_packet(&CSetCamera::new(target_id.into()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resets the player's camera back to their own perspective.
|
||||||
|
pub fn reset_camera(&self) {
|
||||||
|
self.camera_target_id.store(None);
|
||||||
|
self.try_send_client_packet(&CSetCamera::new(self.entity_id().into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the entity ID of the entity that the player's camera is currently attached to,
|
||||||
|
/// or the player's own entity ID if not overridden.
|
||||||
|
pub fn get_camera_entity_id(&self) -> i32 {
|
||||||
|
self.camera_target_id
|
||||||
|
.load()
|
||||||
|
.unwrap_or_else(|| self.entity_id())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn world(&self) -> Arc<World> {
|
pub fn world(&self) -> Arc<World> {
|
||||||
self.living_entity.entity.world.load_full()
|
self.living_entity.entity.world.load_full()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,7 +16,7 @@ impl BedrockClient {
|
|||||||
|
|
||||||
send_cancellable! {{
|
send_cancellable! {{
|
||||||
server;
|
server;
|
||||||
PlayerChatEvent::new(player.clone(), packet.message.into_owned(), vec![]);
|
PlayerChatEvent::new(player.clone(), packet.message.into_owned(), vec![], None);
|
||||||
|
|
||||||
'after: {
|
'after: {
|
||||||
info!("<chat> {}: {}", gameprofile.name, event.message);
|
info!("<chat> {}: {}", gameprofile.name, event.message);
|
||||||
|
|||||||
@ -41,7 +41,12 @@ impl JavaClient {
|
|||||||
|
|
||||||
send_cancellable! {{
|
send_cancellable! {{
|
||||||
server;
|
server;
|
||||||
PlayerChatEvent::new(player.clone(), chat_message.message.to_string(), vec![]);
|
PlayerChatEvent::new(
|
||||||
|
player.clone(),
|
||||||
|
chat_message.message.to_string(),
|
||||||
|
vec![],
|
||||||
|
chat_message.signature.map(<[u8]>::to_vec),
|
||||||
|
);
|
||||||
|
|
||||||
'after: {
|
'after: {
|
||||||
info!("<chat> {}: {}", gameprofile.name, event.message);
|
info!("<chat> {}: {}", gameprofile.name, event.message);
|
||||||
|
|||||||
@ -64,6 +64,10 @@ impl JavaClient {
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if player.is_movement_locked.load(Ordering::Relaxed) {
|
||||||
|
self.force_tp(player, player.get_entity().pos.load());
|
||||||
|
return;
|
||||||
|
}
|
||||||
// y = feet Y
|
// y = feet Y
|
||||||
let position = packet.position;
|
let position = packet.position;
|
||||||
if position.x.is_nan() || position.y.is_nan() || position.z.is_nan() {
|
if position.x.is_nan() || position.y.is_nan() || position.z.is_nan() {
|
||||||
@ -197,6 +201,12 @@ impl JavaClient {
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if player.is_movement_locked.load(Ordering::Relaxed) {
|
||||||
|
let entity = player.get_entity();
|
||||||
|
entity.set_rotation(packet.yaw, packet.pitch);
|
||||||
|
self.force_tp(player, entity.pos.load());
|
||||||
|
return;
|
||||||
|
}
|
||||||
// y = feet Y
|
// y = feet Y
|
||||||
let position = packet.position;
|
let position = packet.position;
|
||||||
if !position.x.is_finite()
|
if !position.x.is_finite()
|
||||||
|
|||||||
@ -19,6 +19,9 @@ pub struct PlayerChatEvent {
|
|||||||
|
|
||||||
/// The recipients of the message. If empty, the message is broadcasted to all players.
|
/// The recipients of the message. If empty, the message is broadcasted to all players.
|
||||||
pub recipients: Vec<Arc<Player>>,
|
pub recipients: Vec<Arc<Player>>,
|
||||||
|
|
||||||
|
/// The optional 256-byte cryptographic signature of the message.
|
||||||
|
pub signature: Option<Vec<u8>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlayerChatEvent {
|
impl PlayerChatEvent {
|
||||||
@ -28,14 +31,21 @@ impl PlayerChatEvent {
|
|||||||
/// - `player`: A reference to the player sending the message.
|
/// - `player`: A reference to the player sending the message.
|
||||||
/// - `message`: The message being sent.
|
/// - `message`: The message being sent.
|
||||||
/// - `recipients`: The recipients of the message. If empty, the message is broadcasted to all players.
|
/// - `recipients`: The recipients of the message. If empty, the message is broadcasted to all players.
|
||||||
|
/// - `signature`: The optional cryptographic signature of the message.
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
/// A new instance of `PlayerChatEvent`.
|
/// A new instance of `PlayerChatEvent`.
|
||||||
pub const fn new(player: Arc<Player>, message: String, recipients: Vec<Arc<Player>>) -> Self {
|
pub const fn new(
|
||||||
|
player: Arc<Player>,
|
||||||
|
message: String,
|
||||||
|
recipients: Vec<Arc<Player>>,
|
||||||
|
signature: Option<Vec<u8>>,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
player,
|
player,
|
||||||
message,
|
message,
|
||||||
recipients,
|
recipients,
|
||||||
|
signature,
|
||||||
cancelled: false,
|
cancelled: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -322,6 +322,7 @@ impl ToFromWasmEvent for PlayerChatEvent {
|
|||||||
player,
|
player,
|
||||||
message: self.message.clone(),
|
message: self.message.clone(),
|
||||||
recipients,
|
recipients,
|
||||||
|
signature: self.signature.clone(),
|
||||||
cancelled: self.cancelled,
|
cancelled: self.cancelled,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -336,6 +337,7 @@ impl ToFromWasmEvent for PlayerChatEvent {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|recipient| consume_player(state, &recipient))
|
.map(|recipient| consume_player(state, &recipient))
|
||||||
.collect(),
|
.collect(),
|
||||||
|
signature: data.signature,
|
||||||
cancelled: data.cancelled,
|
cancelled: data.cancelled,
|
||||||
},
|
},
|
||||||
_ => panic!("unexpected event type"),
|
_ => panic!("unexpected event type"),
|
||||||
|
|||||||
@ -1,10 +1,15 @@
|
|||||||
use crate::plugin::loader::wasm::wasm_host::state::{ItemStackResource, PluginHostState};
|
use crate::plugin::loader::wasm::wasm_host::state::{ItemStackResource, PluginHostState};
|
||||||
|
use crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::attributes::{
|
||||||
|
Attribute as WitAttribute, AttributeModifier as WitAttributeModifier,
|
||||||
|
ModifierOperation as WitModifierOperation,
|
||||||
|
};
|
||||||
use crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::data_components::DataComponent as WitDataComponent;
|
use crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::data_components::DataComponent as WitDataComponent;
|
||||||
use crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::enchantments::Enchantment as WitEnchantment;
|
use crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::enchantments::Enchantment as WitEnchantment;
|
||||||
use crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::item_stack::{
|
use crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::item_stack::{
|
||||||
CustomEnchantmentValue as WitCustomEnchantmentValue,
|
CustomEnchantmentValue as WitCustomEnchantmentValue,
|
||||||
DataComponentValue as WitDataComponentValue, EnchantmentValue as WitEnchantmentValue,
|
DataComponentValue as WitDataComponentValue, EnchantmentValue as WitEnchantmentValue,
|
||||||
Host as ItemStackInterfaceHost, HostItemStack, ItemStack as ItemStackHandle,
|
Host as ItemStackInterfaceHost, HostItemStack,
|
||||||
|
ItemAttributeModifier as WitItemAttributeModifier, ItemStack as ItemStackHandle,
|
||||||
};
|
};
|
||||||
use crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::text::TextComponent as WitTextComponent;
|
use crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::text::TextComponent as WitTextComponent;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@ -14,8 +19,12 @@ use wasmtime::component::Resource;
|
|||||||
use super::common::{WitNbtTree, from_wit_nbt_tree, to_wit_nbt_tree};
|
use super::common::{WitNbtTree, from_wit_nbt_tree, to_wit_nbt_tree};
|
||||||
use crate::plugin::loader::wasm::wasm_host::wit::v0_1::player::text_component_from_resource;
|
use crate::plugin::loader::wasm::wasm_host::wit::v0_1::player::text_component_from_resource;
|
||||||
use pumpkin_data::Enchantment;
|
use pumpkin_data::Enchantment;
|
||||||
|
use pumpkin_data::attributes::Attributes;
|
||||||
use pumpkin_data::data_component::DataComponent;
|
use pumpkin_data::data_component::DataComponent;
|
||||||
use pumpkin_data::data_component_impl::{CustomNameImpl, EnchantmentsImpl, LoreImpl};
|
use pumpkin_data::data_component_impl::combat::{Modifier, Operation};
|
||||||
|
use pumpkin_data::data_component_impl::{
|
||||||
|
AttributeModifiersImpl, CustomNameImpl, EnchantmentsImpl, LoreImpl,
|
||||||
|
};
|
||||||
use pumpkin_nbt::tag::NbtTag;
|
use pumpkin_nbt::tag::NbtTag;
|
||||||
use pumpkin_protocol::codec::data_component::{deserialize, serialize};
|
use pumpkin_protocol::codec::data_component::{deserialize, serialize};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
@ -40,6 +49,30 @@ pub(crate) fn from_wit_enchantment(id: WitEnchantment) -> &'static Enchantment {
|
|||||||
Enchantment::from_id(id as u8).expect("valid enchantment ID")
|
Enchantment::from_id(id as u8).expect("valid enchantment ID")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn to_wit_attribute(attr: &Attributes) -> WitAttribute {
|
||||||
|
// SAFETY: WIT enum is generated in the same order as the internal ID
|
||||||
|
unsafe { std::mem::transmute(attr.id) }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub const fn from_wit_item_operation(op: WitModifierOperation) -> Operation {
|
||||||
|
match op {
|
||||||
|
WitModifierOperation::Add => Operation::AddValue,
|
||||||
|
WitModifierOperation::MultiplyBase => Operation::AddMultipliedBase,
|
||||||
|
WitModifierOperation::MultiplyTotal => Operation::AddMultipliedTotal,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub const fn to_wit_item_operation(op: Operation) -> WitModifierOperation {
|
||||||
|
match op {
|
||||||
|
Operation::AddValue => WitModifierOperation::Add,
|
||||||
|
Operation::AddMultipliedBase => WitModifierOperation::MultiplyBase,
|
||||||
|
Operation::AddMultipliedTotal => WitModifierOperation::MultiplyTotal,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl PluginHostState {
|
impl PluginHostState {
|
||||||
pub fn get_item_stack(
|
pub fn get_item_stack(
|
||||||
&self,
|
&self,
|
||||||
@ -380,6 +413,101 @@ impl HostItemStack for PluginHostState {
|
|||||||
Ok(false)
|
Ok(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn get_attribute_modifiers(
|
||||||
|
&mut self,
|
||||||
|
res: Resource<ItemStackHandle>,
|
||||||
|
) -> wasmtime::Result<Vec<WitItemAttributeModifier>> {
|
||||||
|
let stack = self.get_item_stack(&res)?;
|
||||||
|
let stack = stack.lock().await;
|
||||||
|
let mut modifiers = Vec::new();
|
||||||
|
if let Some(comp) = stack.get_data_component::<AttributeModifiersImpl>() {
|
||||||
|
for m in comp.attribute_modifiers.iter() {
|
||||||
|
modifiers.push(WitItemAttributeModifier {
|
||||||
|
attribute: to_wit_attribute(m.r#type),
|
||||||
|
modifier: WitAttributeModifier {
|
||||||
|
id: m.id.to_string(),
|
||||||
|
amount: m.amount,
|
||||||
|
operation: to_wit_item_operation(m.operation),
|
||||||
|
},
|
||||||
|
slot: super::enchantment::to_wit_slot(&m.slot),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(modifiers)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn add_attribute_modifier(
|
||||||
|
&mut self,
|
||||||
|
res: Resource<ItemStackHandle>,
|
||||||
|
modifier: WitItemAttributeModifier,
|
||||||
|
) -> wasmtime::Result<()> {
|
||||||
|
let stack = self.get_item_stack(&res)?;
|
||||||
|
let mut stack = stack.lock().await;
|
||||||
|
let attr = super::living_entity::from_wit_attribute(modifier.attribute);
|
||||||
|
let slot = super::enchantment::to_data_slot(modifier.slot);
|
||||||
|
let op = from_wit_item_operation(modifier.modifier.operation);
|
||||||
|
let leaked_id: &'static str = Box::leak(modifier.modifier.id.into_boxed_str());
|
||||||
|
|
||||||
|
let mut current_mods = stack
|
||||||
|
.get_data_component::<AttributeModifiersImpl>()
|
||||||
|
.map_or_else(Vec::new, |comp| {
|
||||||
|
comp.attribute_modifiers.clone().into_owned()
|
||||||
|
});
|
||||||
|
|
||||||
|
current_mods.retain(|m| !(m.r#type == attr && m.id == leaked_id && m.slot == slot));
|
||||||
|
current_mods.push(Modifier {
|
||||||
|
r#type: attr,
|
||||||
|
id: leaked_id,
|
||||||
|
amount: modifier.modifier.amount,
|
||||||
|
operation: op,
|
||||||
|
slot,
|
||||||
|
});
|
||||||
|
|
||||||
|
stack.set_data_component(AttributeModifiersImpl {
|
||||||
|
attribute_modifiers: Cow::Owned(current_mods),
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn remove_attribute_modifiers(
|
||||||
|
&mut self,
|
||||||
|
res: Resource<ItemStackHandle>,
|
||||||
|
attribute: WitAttribute,
|
||||||
|
) -> wasmtime::Result<()> {
|
||||||
|
let stack = self.get_item_stack(&res)?;
|
||||||
|
let mut stack = stack.lock().await;
|
||||||
|
let attr = super::living_entity::from_wit_attribute(attribute);
|
||||||
|
|
||||||
|
if let Some(comp) = stack.get_data_component::<AttributeModifiersImpl>() {
|
||||||
|
let mut current_mods = comp.attribute_modifiers.clone().into_owned();
|
||||||
|
current_mods.retain(|m| m.r#type != attr);
|
||||||
|
if current_mods.is_empty() {
|
||||||
|
stack
|
||||||
|
.patch
|
||||||
|
.retain(|(id, _)| *id != DataComponent::AttributeModifiers);
|
||||||
|
} else {
|
||||||
|
stack.set_data_component(AttributeModifiersImpl {
|
||||||
|
attribute_modifiers: Cow::Owned(current_mods),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn clear_attribute_modifiers(
|
||||||
|
&mut self,
|
||||||
|
res: Resource<ItemStackHandle>,
|
||||||
|
) -> wasmtime::Result<()> {
|
||||||
|
let stack = self.get_item_stack(&res)?;
|
||||||
|
let mut stack = stack.lock().await;
|
||||||
|
stack
|
||||||
|
.patch
|
||||||
|
.retain(|(id, _)| *id != DataComponent::AttributeModifiers);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
async fn get_lore(
|
async fn get_lore(
|
||||||
&mut self,
|
&mut self,
|
||||||
res: Resource<ItemStackHandle>,
|
res: Resource<ItemStackHandle>,
|
||||||
|
|||||||
@ -980,6 +980,21 @@ impl HostMob for PluginHostState {
|
|||||||
Ok(false)
|
Ok(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn set_freeze_ticks(
|
||||||
|
&mut self,
|
||||||
|
this: Resource<WitMob>,
|
||||||
|
ticks: i32,
|
||||||
|
) -> wasmtime::Result<()> {
|
||||||
|
let entity = mob_from_resource(self, &this)?;
|
||||||
|
entity.get_entity().set_frozen_ticks(ticks);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_freeze_ticks(&mut self, this: Resource<WitMob>) -> wasmtime::Result<i32> {
|
||||||
|
let entity = mob_from_resource(self, &this)?;
|
||||||
|
Ok(entity.get_entity().get_frozen_ticks())
|
||||||
|
}
|
||||||
|
|
||||||
async fn drop(&mut self, rep: Resource<WitMob>) -> wasmtime::Result<()> {
|
async fn drop(&mut self, rep: Resource<WitMob>) -> wasmtime::Result<()> {
|
||||||
let _ = self
|
let _ = self
|
||||||
.resource_table
|
.resource_table
|
||||||
|
|||||||
@ -84,6 +84,9 @@ impl pumpkin::plugin::damage_types::Host for PluginHostState {}
|
|||||||
impl pumpkin::plugin::screens::Host for PluginHostState {}
|
impl pumpkin::plugin::screens::Host for PluginHostState {}
|
||||||
impl pumpkin::plugin::statistics::Host for PluginHostState {}
|
impl pumpkin::plugin::statistics::Host for PluginHostState {}
|
||||||
impl pumpkin::plugin::game_rules::Host for PluginHostState {}
|
impl pumpkin::plugin::game_rules::Host for PluginHostState {}
|
||||||
|
impl pumpkin::plugin::game_events::Host for PluginHostState {}
|
||||||
|
impl pumpkin::plugin::potions::Host for PluginHostState {}
|
||||||
|
impl pumpkin::plugin::entity_statuses::Host for PluginHostState {}
|
||||||
|
|
||||||
pub fn add_to_linker(linker: &mut Linker<PluginHostState>) -> wasmtime::Result<()> {
|
pub fn add_to_linker(linker: &mut Linker<PluginHostState>) -> wasmtime::Result<()> {
|
||||||
Plugin::add_to_linker::<_, HasSelf<_>>(linker, |state: &mut PluginHostState| state)?;
|
Plugin::add_to_linker::<_, HasSelf<_>>(linker, |state: &mut PluginHostState| state)?;
|
||||||
|
|||||||
@ -48,6 +48,62 @@ use pumpkin_protocol::bedrock::client::set_actor_data::{
|
|||||||
use pumpkin_protocol::codec::var_ulong::VarULong;
|
use pumpkin_protocol::codec::var_ulong::VarULong;
|
||||||
use pumpkin_util::version::{BedrockMinecraftVersion, JavaMinecraftVersion};
|
use pumpkin_util::version::{BedrockMinecraftVersion, JavaMinecraftVersion};
|
||||||
|
|
||||||
|
const fn from_wit_client_game_event(
|
||||||
|
event: pumpkin::plugin::player::ClientGameEvent,
|
||||||
|
) -> pumpkin_protocol::java::client::play::GameEvent {
|
||||||
|
use pumpkin::plugin::player::ClientGameEvent as W;
|
||||||
|
use pumpkin_protocol::java::client::play::GameEvent as G;
|
||||||
|
match event {
|
||||||
|
W::NoRespawnBlockAvailable => G::NoRespawnBlockAvailable,
|
||||||
|
W::BeginRaining => G::BeginRaining,
|
||||||
|
W::EndRaining => G::EndRaining,
|
||||||
|
W::ChangeGameMode => G::ChangeGameMode,
|
||||||
|
W::WinGame => G::WinGame,
|
||||||
|
W::DemoEvent => G::DemoEvent,
|
||||||
|
W::ArrowHitPlayer => G::ArrowHitPlayer,
|
||||||
|
W::RainLevelChange => G::RainLevelChange,
|
||||||
|
W::ThunderLevelChange => G::ThunderLevelChange,
|
||||||
|
W::PlayPufferfishStringSound => G::PlayPufferfishStringSound,
|
||||||
|
W::PlayElderGuardianMobAppearance => G::PlayElderGuardianMobAppearance,
|
||||||
|
W::EnabledRespawnScreen => G::EnabledRespawnScreen,
|
||||||
|
W::LimitedCrafting => G::LimitedCrafting,
|
||||||
|
W::StartWaitingChunks => G::StartWaitingChunks,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn from_wit_server_link(
|
||||||
|
state: &PluginHostState,
|
||||||
|
link: pumpkin::plugin::player::ServerLink,
|
||||||
|
) -> wasmtime::Result<(pumpkin_protocol::Label, String)> {
|
||||||
|
use pumpkin::plugin::player::{KnownServerLink, ServerLinkLabel};
|
||||||
|
let label = match link.label {
|
||||||
|
ServerLinkLabel::Known(known) => {
|
||||||
|
let link_type = match known {
|
||||||
|
KnownServerLink::BugReport => pumpkin_protocol::LinkType::BugReport,
|
||||||
|
KnownServerLink::CommunityGuidelines => {
|
||||||
|
pumpkin_protocol::LinkType::CommunityGuidelines
|
||||||
|
}
|
||||||
|
KnownServerLink::Support => pumpkin_protocol::LinkType::Support,
|
||||||
|
KnownServerLink::Status => pumpkin_protocol::LinkType::Status,
|
||||||
|
KnownServerLink::Feedback => pumpkin_protocol::LinkType::Feedback,
|
||||||
|
KnownServerLink::Community => pumpkin_protocol::LinkType::Community,
|
||||||
|
KnownServerLink::Website => pumpkin_protocol::LinkType::Website,
|
||||||
|
KnownServerLink::Forums => pumpkin_protocol::LinkType::Forums,
|
||||||
|
KnownServerLink::News => pumpkin_protocol::LinkType::News,
|
||||||
|
KnownServerLink::Announcements => pumpkin_protocol::LinkType::Announcements,
|
||||||
|
};
|
||||||
|
pumpkin_protocol::Label::BuiltIn(link_type)
|
||||||
|
}
|
||||||
|
ServerLinkLabel::Custom(text) => {
|
||||||
|
let text_res = state
|
||||||
|
.resource_table
|
||||||
|
.get::<TextComponentResource>(&Resource::new_own(text.rep()))?;
|
||||||
|
pumpkin_protocol::Label::TextComponent(Box::new(text_res.provider.clone()))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok((label, link.url))
|
||||||
|
}
|
||||||
|
|
||||||
const fn to_wasm_java_version(
|
const fn to_wasm_java_version(
|
||||||
version: JavaMinecraftVersion,
|
version: JavaMinecraftVersion,
|
||||||
) -> pumpkin::plugin::player::JavaMinecraftVersion {
|
) -> pumpkin::plugin::player::JavaMinecraftVersion {
|
||||||
@ -1512,6 +1568,363 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn delete_message_by_signature(
|
||||||
|
&mut self,
|
||||||
|
player: Resource<Player>,
|
||||||
|
signature: Vec<u8>,
|
||||||
|
) -> wasmtime::Result<()> {
|
||||||
|
let player = player_from_resource(self, &player)?;
|
||||||
|
if let Some(client) = player.client.java() {
|
||||||
|
let packet =
|
||||||
|
pumpkin_protocol::java::client::play::CDeleteChat::from_signature(&signature);
|
||||||
|
client.send_packet(&packet).await;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn delete_message_by_id(
|
||||||
|
&mut self,
|
||||||
|
player: Resource<Player>,
|
||||||
|
signature_id: i32,
|
||||||
|
) -> wasmtime::Result<()> {
|
||||||
|
let player = player_from_resource(self, &player)?;
|
||||||
|
if let Some(client) = player.client.java() {
|
||||||
|
let packet =
|
||||||
|
pumpkin_protocol::java::client::play::CDeleteChat::from_cache_id(signature_id);
|
||||||
|
client.send_packet(&packet).await;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn set_camera(
|
||||||
|
&mut self,
|
||||||
|
player: Resource<Player>,
|
||||||
|
entity: Option<
|
||||||
|
Resource<
|
||||||
|
crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::world::Entity,
|
||||||
|
>,
|
||||||
|
>,
|
||||||
|
) -> wasmtime::Result<()> {
|
||||||
|
let player = player_from_resource(self, &player)?;
|
||||||
|
if let Some(target_res) = entity {
|
||||||
|
let target =
|
||||||
|
crate::plugin::loader::wasm::wasm_host::wit::v0_1::entity::entity_from_resource(
|
||||||
|
self,
|
||||||
|
&target_res,
|
||||||
|
)?;
|
||||||
|
player.set_camera_entity_id(target.get_entity().entity_id);
|
||||||
|
} else {
|
||||||
|
player.reset_camera();
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn set_camera_entity_id(
|
||||||
|
&mut self,
|
||||||
|
player: Resource<Player>,
|
||||||
|
entity_id: u32,
|
||||||
|
) -> wasmtime::Result<()> {
|
||||||
|
let player = player_from_resource(self, &player)?;
|
||||||
|
player.set_camera_entity_id(entity_id as i32);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_camera_entity_id(&mut self, player: Resource<Player>) -> wasmtime::Result<u32> {
|
||||||
|
let player = player_from_resource(self, &player)?;
|
||||||
|
Ok(player.get_camera_entity_id() as u32)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn reset_camera(&mut self, player: Resource<Player>) -> wasmtime::Result<()> {
|
||||||
|
let player = player_from_resource(self, &player)?;
|
||||||
|
player.reset_camera();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn play_sound(
|
||||||
|
&mut self,
|
||||||
|
player: Resource<Player>,
|
||||||
|
sound: pumpkin::plugin::sounds::Sound,
|
||||||
|
category: pumpkin::plugin::sounds::SoundCategory,
|
||||||
|
volume: f32,
|
||||||
|
pitch: f32,
|
||||||
|
) -> wasmtime::Result<()> {
|
||||||
|
let player = player_from_resource(self, &player)?;
|
||||||
|
let sound_name = format!("{sound:?}").to_lowercase().replace('_', ".");
|
||||||
|
let sound_data = pumpkin_data::sound::Sound::from_name(&sound_name)
|
||||||
|
.ok_or_else(|| wasmtime::Error::msg(format!("Unknown sound: {sound_name}")))?;
|
||||||
|
let internal_category = super::world::from_wit_sound_category(category);
|
||||||
|
let pos = player.position();
|
||||||
|
player.play_sound(
|
||||||
|
sound_data as u16,
|
||||||
|
internal_category,
|
||||||
|
&pos,
|
||||||
|
volume,
|
||||||
|
pitch,
|
||||||
|
rand::random::<f64>(),
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn play_sound_at(
|
||||||
|
&mut self,
|
||||||
|
player: Resource<Player>,
|
||||||
|
pos: pumpkin::plugin::common::Position,
|
||||||
|
sound: pumpkin::plugin::sounds::Sound,
|
||||||
|
category: pumpkin::plugin::sounds::SoundCategory,
|
||||||
|
volume: f32,
|
||||||
|
pitch: f32,
|
||||||
|
) -> wasmtime::Result<()> {
|
||||||
|
let player = player_from_resource(self, &player)?;
|
||||||
|
let sound_name = format!("{sound:?}").to_lowercase().replace('_', ".");
|
||||||
|
let sound_data = pumpkin_data::sound::Sound::from_name(&sound_name)
|
||||||
|
.ok_or_else(|| wasmtime::Error::msg(format!("Unknown sound: {sound_name}")))?;
|
||||||
|
let internal_category = super::world::from_wit_sound_category(category);
|
||||||
|
player.play_sound(
|
||||||
|
sound_data as u16,
|
||||||
|
internal_category,
|
||||||
|
&pumpkin_util::math::vector3::Vector3::new(pos.0, pos.1, pos.2),
|
||||||
|
volume,
|
||||||
|
pitch,
|
||||||
|
rand::random::<f64>(),
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn stop_sound(
|
||||||
|
&mut self,
|
||||||
|
player: Resource<Player>,
|
||||||
|
sound: Option<pumpkin::plugin::sounds::Sound>,
|
||||||
|
category: Option<pumpkin::plugin::sounds::SoundCategory>,
|
||||||
|
) -> wasmtime::Result<()> {
|
||||||
|
let player = player_from_resource(self, &player)?;
|
||||||
|
let sound_rl = sound.and_then(|s| {
|
||||||
|
let sound_name = format!("{s:?}").to_lowercase().replace('_', ".");
|
||||||
|
pumpkin_data::sound::Sound::from_name(&sound_name).map(|s| s.to_name().into())
|
||||||
|
});
|
||||||
|
let cat = category.map(super::world::from_wit_sound_category);
|
||||||
|
player.stop_sound(sound_rl, cat);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn play_custom_sound(
|
||||||
|
&mut self,
|
||||||
|
player: Resource<Player>,
|
||||||
|
sound_name: String,
|
||||||
|
category: pumpkin::plugin::sounds::SoundCategory,
|
||||||
|
volume: f32,
|
||||||
|
pitch: f32,
|
||||||
|
) -> wasmtime::Result<()> {
|
||||||
|
let player = player_from_resource(self, &player)?;
|
||||||
|
let internal_category = super::world::from_wit_sound_category(category);
|
||||||
|
let pos = player.position();
|
||||||
|
player.play_custom_sound(&sound_name, internal_category, &pos, volume, pitch);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn play_custom_sound_at(
|
||||||
|
&mut self,
|
||||||
|
player: Resource<Player>,
|
||||||
|
pos: pumpkin::plugin::common::Position,
|
||||||
|
sound_name: String,
|
||||||
|
category: pumpkin::plugin::sounds::SoundCategory,
|
||||||
|
volume: f32,
|
||||||
|
pitch: f32,
|
||||||
|
) -> wasmtime::Result<()> {
|
||||||
|
let player = player_from_resource(self, &player)?;
|
||||||
|
let internal_category = super::world::from_wit_sound_category(category);
|
||||||
|
player.play_custom_sound(
|
||||||
|
&sound_name,
|
||||||
|
internal_category,
|
||||||
|
&pumpkin_util::math::vector3::Vector3::new(pos.0, pos.1, pos.2),
|
||||||
|
volume,
|
||||||
|
pitch,
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn stop_custom_sound(
|
||||||
|
&mut self,
|
||||||
|
player: Resource<Player>,
|
||||||
|
sound_name: Option<String>,
|
||||||
|
category: Option<pumpkin::plugin::sounds::SoundCategory>,
|
||||||
|
) -> wasmtime::Result<()> {
|
||||||
|
let player = player_from_resource(self, &player)?;
|
||||||
|
let sound_rl = sound_name
|
||||||
|
.as_deref()
|
||||||
|
.map(pumpkin_util::resource_location::ResourceLocation::from);
|
||||||
|
let cat = category.map(super::world::from_wit_sound_category);
|
||||||
|
player.stop_sound(sound_rl, cat);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn spawn_particles(
|
||||||
|
&mut self,
|
||||||
|
player: Resource<Player>,
|
||||||
|
particle: pumpkin::plugin::particles::Particle,
|
||||||
|
pos: pumpkin::plugin::common::Position,
|
||||||
|
count: u32,
|
||||||
|
offset: pumpkin::plugin::common::Position,
|
||||||
|
max_speed: f32,
|
||||||
|
) -> wasmtime::Result<()> {
|
||||||
|
let player = player_from_resource(self, &player)?;
|
||||||
|
let particle_data =
|
||||||
|
pumpkin_data::particle::Particle::from_id(particle as u16).ok_or_else(|| {
|
||||||
|
wasmtime::Error::msg(format!("Unknown particle ID: {}", particle as u16))
|
||||||
|
})?;
|
||||||
|
player.spawn_particles(
|
||||||
|
particle_data,
|
||||||
|
pumpkin_util::math::vector3::Vector3::new(pos.0, pos.1, pos.2),
|
||||||
|
count,
|
||||||
|
pumpkin_util::math::vector3::Vector3::new(
|
||||||
|
offset.0 as f32,
|
||||||
|
offset.1 as f32,
|
||||||
|
offset.2 as f32,
|
||||||
|
),
|
||||||
|
max_speed,
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_block_change(
|
||||||
|
&mut self,
|
||||||
|
player: Resource<Player>,
|
||||||
|
pos: pumpkin::plugin::common::BlockPos,
|
||||||
|
block_id: u16,
|
||||||
|
) -> wasmtime::Result<()> {
|
||||||
|
let player = player_from_resource(self, &player)?;
|
||||||
|
player.send_block_change(
|
||||||
|
pumpkin_util::math::position::BlockPos(pumpkin_util::math::vector3::Vector3::new(
|
||||||
|
pos.x, pos.y, pos.z,
|
||||||
|
)),
|
||||||
|
block_id,
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn reset_block_change(
|
||||||
|
&mut self,
|
||||||
|
player: Resource<Player>,
|
||||||
|
pos: pumpkin::plugin::common::BlockPos,
|
||||||
|
) -> wasmtime::Result<()> {
|
||||||
|
let player = player_from_resource(self, &player)?;
|
||||||
|
player.reset_block_change(pumpkin_util::math::position::BlockPos(
|
||||||
|
pumpkin_util::math::vector3::Vector3::new(pos.x, pos.y, pos.z),
|
||||||
|
));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_hurt_animation(
|
||||||
|
&mut self,
|
||||||
|
player: Resource<Player>,
|
||||||
|
yaw: f32,
|
||||||
|
) -> wasmtime::Result<()> {
|
||||||
|
let player = player_from_resource(self, &player)?;
|
||||||
|
player.send_hurt_animation(yaw);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn open_book(
|
||||||
|
&mut self,
|
||||||
|
player: Resource<Player>,
|
||||||
|
hand: pumpkin::plugin::common::Hand,
|
||||||
|
) -> wasmtime::Result<()> {
|
||||||
|
let player = player_from_resource(self, &player)?;
|
||||||
|
let hand = match hand {
|
||||||
|
pumpkin::plugin::common::Hand::Right => pumpkin_util::Hand::Right,
|
||||||
|
pumpkin::plugin::common::Hand::Left => pumpkin_util::Hand::Left,
|
||||||
|
};
|
||||||
|
player.open_book(hand);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn open_sign_editor(
|
||||||
|
&mut self,
|
||||||
|
player: Resource<Player>,
|
||||||
|
pos: pumpkin::plugin::common::BlockPos,
|
||||||
|
is_front_text: bool,
|
||||||
|
) -> wasmtime::Result<()> {
|
||||||
|
let player = player_from_resource(self, &player)?;
|
||||||
|
player.open_sign_editor(
|
||||||
|
pumpkin_util::math::position::BlockPos(pumpkin_util::math::vector3::Vector3::new(
|
||||||
|
pos.x, pos.y, pos.z,
|
||||||
|
)),
|
||||||
|
is_front_text,
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn set_velocity(
|
||||||
|
&mut self,
|
||||||
|
player: Resource<Player>,
|
||||||
|
velocity: pumpkin::plugin::common::Position,
|
||||||
|
) -> wasmtime::Result<()> {
|
||||||
|
let player = player_from_resource(self, &player)?;
|
||||||
|
player.set_velocity(pumpkin_util::math::vector3::Vector3::new(
|
||||||
|
velocity.0, velocity.1, velocity.2,
|
||||||
|
));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn apply_knockback(
|
||||||
|
&mut self,
|
||||||
|
player: Resource<Player>,
|
||||||
|
strength: f64,
|
||||||
|
x: f64,
|
||||||
|
z: f64,
|
||||||
|
) -> wasmtime::Result<()> {
|
||||||
|
let player = player_from_resource(self, &player)?;
|
||||||
|
player.apply_knockback(strength, x, z);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn set_movement_locked(
|
||||||
|
&mut self,
|
||||||
|
player: Resource<Player>,
|
||||||
|
locked: bool,
|
||||||
|
) -> wasmtime::Result<()> {
|
||||||
|
let player = player_from_resource(self, &player)?;
|
||||||
|
player.set_movement_locked(locked);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn is_movement_locked(&mut self, player: Resource<Player>) -> wasmtime::Result<bool> {
|
||||||
|
let player = player_from_resource(self, &player)?;
|
||||||
|
Ok(player.is_movement_locked())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn set_freeze_ticks(
|
||||||
|
&mut self,
|
||||||
|
player: Resource<Player>,
|
||||||
|
ticks: i32,
|
||||||
|
) -> wasmtime::Result<()> {
|
||||||
|
let player = player_from_resource(self, &player)?;
|
||||||
|
player.set_freeze_ticks(ticks);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_freeze_ticks(&mut self, player: Resource<Player>) -> wasmtime::Result<i32> {
|
||||||
|
let player = player_from_resource(self, &player)?;
|
||||||
|
Ok(player.get_freeze_ticks())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn set_server_links(
|
||||||
|
&mut self,
|
||||||
|
player: Resource<Player>,
|
||||||
|
links: Vec<pumpkin::plugin::player::ServerLink>,
|
||||||
|
) -> wasmtime::Result<()> {
|
||||||
|
let player = player_from_resource(self, &player)?;
|
||||||
|
let mut converted = Vec::new();
|
||||||
|
for link in links {
|
||||||
|
converted.push(from_wit_server_link(self, link)?);
|
||||||
|
}
|
||||||
|
let protocol_links: Vec<pumpkin_protocol::Link<'_>> = converted
|
||||||
|
.iter()
|
||||||
|
.map(|(label, url)| pumpkin_protocol::Link::new(label.clone(), url))
|
||||||
|
.collect();
|
||||||
|
player.set_server_links(&protocol_links);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
async fn add_effect(
|
async fn add_effect(
|
||||||
&mut self,
|
&mut self,
|
||||||
player: Resource<Player>,
|
player: Resource<Player>,
|
||||||
@ -3331,6 +3744,52 @@ impl pumpkin::plugin::player::HostJavaPlayer for PluginHostState {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn send_game_event(
|
||||||
|
&mut self,
|
||||||
|
player: Resource<pumpkin::plugin::player::JavaPlayer>,
|
||||||
|
event: pumpkin::plugin::player::ClientGameEvent,
|
||||||
|
value: f32,
|
||||||
|
) -> wasmtime::Result<()> {
|
||||||
|
let player = self
|
||||||
|
.resource_table
|
||||||
|
.get::<crate::plugin::loader::wasm::wasm_host::state::JavaPlayerResource>(
|
||||||
|
&Resource::new_own(player.rep()),
|
||||||
|
)
|
||||||
|
.map_err(|_| wasmtime::Error::msg("invalid java-player resource handle"))?
|
||||||
|
.provider
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
let internal_event = from_wit_client_game_event(event);
|
||||||
|
player.send_game_event(internal_event, value);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_entity_status(
|
||||||
|
&mut self,
|
||||||
|
player: Resource<pumpkin::plugin::player::JavaPlayer>,
|
||||||
|
entity_id: i32,
|
||||||
|
status: pumpkin::plugin::entity_statuses::EntityStatus,
|
||||||
|
) -> wasmtime::Result<()> {
|
||||||
|
let player = self
|
||||||
|
.resource_table
|
||||||
|
.get::<crate::plugin::loader::wasm::wasm_host::state::JavaPlayerResource>(
|
||||||
|
&Resource::new_own(player.rep()),
|
||||||
|
)
|
||||||
|
.map_err(|_| wasmtime::Error::msg("invalid java-player resource handle"))?
|
||||||
|
.provider
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
// SAFETY: The WIT enum variants and discriminants are 1:1 generated from entity_statuses.json
|
||||||
|
let internal_status: pumpkin_data::entity_status::EntityStatus =
|
||||||
|
unsafe { std::mem::transmute(status as u8) };
|
||||||
|
let packet = pumpkin_protocol::java::client::play::CEntityStatus::new(
|
||||||
|
entity_id,
|
||||||
|
internal_status as u8 as i8,
|
||||||
|
);
|
||||||
|
player.try_send_client_packet(&packet);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
async fn drop(
|
async fn drop(
|
||||||
&mut self,
|
&mut self,
|
||||||
rep: Resource<pumpkin::plugin::player::JavaPlayer>,
|
rep: Resource<pumpkin::plugin::player::JavaPlayer>,
|
||||||
|
|||||||
@ -304,6 +304,34 @@ impl pumpkin::plugin::server::HostServer for PluginHostState {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn delete_message_by_signature(
|
||||||
|
&mut self,
|
||||||
|
_rep: Resource<Server>,
|
||||||
|
signature: Vec<u8>,
|
||||||
|
) -> wasmtime::Result<()> {
|
||||||
|
let server = self
|
||||||
|
.server
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
|
||||||
|
let packet = pumpkin_protocol::java::client::play::CDeleteChat::from_signature(&signature);
|
||||||
|
server.broadcast_packet_all(&packet);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn delete_message_by_id(
|
||||||
|
&mut self,
|
||||||
|
_rep: Resource<Server>,
|
||||||
|
signature_id: i32,
|
||||||
|
) -> wasmtime::Result<()> {
|
||||||
|
let server = self
|
||||||
|
.server
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
|
||||||
|
let packet = pumpkin_protocol::java::client::play::CDeleteChat::from_cache_id(signature_id);
|
||||||
|
server.broadcast_packet_all(&packet);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
async fn broadcast_tab_list_header_footer(
|
async fn broadcast_tab_list_header_footer(
|
||||||
&mut self,
|
&mut self,
|
||||||
_rep: Resource<Server>,
|
_rep: Resource<Server>,
|
||||||
@ -618,6 +646,27 @@ impl pumpkin::plugin::server::HostServer for PluginHostState {
|
|||||||
self.add_datapack_manager(server.clone())
|
self.add_datapack_manager(server.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn set_server_links(
|
||||||
|
&mut self,
|
||||||
|
_rep: Resource<Server>,
|
||||||
|
links: Vec<pumpkin::plugin::player::ServerLink>,
|
||||||
|
) -> wasmtime::Result<()> {
|
||||||
|
let server = self
|
||||||
|
.server
|
||||||
|
.clone()
|
||||||
|
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
|
||||||
|
let mut converted = Vec::new();
|
||||||
|
for link in links {
|
||||||
|
converted.push(super::player::from_wit_server_link(self, link)?);
|
||||||
|
}
|
||||||
|
let protocol_links: Vec<pumpkin_protocol::Link<'_>> = converted
|
||||||
|
.iter()
|
||||||
|
.map(|(label, url)| pumpkin_protocol::Link::new(label.clone(), url))
|
||||||
|
.collect();
|
||||||
|
server.broadcast_server_links(&protocol_links);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
async fn drop(&mut self, rep: Resource<Server>) -> wasmtime::Result<()> {
|
async fn drop(&mut self, rep: Resource<Server>) -> wasmtime::Result<()> {
|
||||||
self.resource_table
|
self.resource_table
|
||||||
.delete::<ServerResource>(Resource::new_own(rep.rep()))
|
.delete::<ServerResource>(Resource::new_own(rep.rep()))
|
||||||
|
|||||||
@ -864,7 +864,7 @@ impl pumpkin::plugin::world::HostWorld for PluginHostState {
|
|||||||
&mut self,
|
&mut self,
|
||||||
world: Resource<World>,
|
world: Resource<World>,
|
||||||
sound: pumpkin::plugin::sounds::Sound,
|
sound: pumpkin::plugin::sounds::Sound,
|
||||||
category: pumpkin::plugin::world::SoundCategory,
|
category: pumpkin::plugin::sounds::SoundCategory,
|
||||||
pos: pumpkin::plugin::common::Position,
|
pos: pumpkin::plugin::common::Position,
|
||||||
volume: f32,
|
volume: f32,
|
||||||
pitch: f32,
|
pitch: f32,
|
||||||
@ -874,38 +874,7 @@ impl pumpkin::plugin::world::HostWorld for PluginHostState {
|
|||||||
let sound_data = pumpkin_data::sound::Sound::from_name(&sound_name)
|
let sound_data = pumpkin_data::sound::Sound::from_name(&sound_name)
|
||||||
.ok_or_else(|| wasmtime::Error::msg(format!("Unknown sound: {sound_name}")))?;
|
.ok_or_else(|| wasmtime::Error::msg(format!("Unknown sound: {sound_name}")))?;
|
||||||
|
|
||||||
let internal_category = match category {
|
let internal_category = from_wit_sound_category(category);
|
||||||
pumpkin::plugin::world::SoundCategory::Master => {
|
|
||||||
pumpkin_data::sound::SoundCategory::Master
|
|
||||||
}
|
|
||||||
pumpkin::plugin::world::SoundCategory::Music => {
|
|
||||||
pumpkin_data::sound::SoundCategory::Music
|
|
||||||
}
|
|
||||||
pumpkin::plugin::world::SoundCategory::Records => {
|
|
||||||
pumpkin_data::sound::SoundCategory::Records
|
|
||||||
}
|
|
||||||
pumpkin::plugin::world::SoundCategory::Weather => {
|
|
||||||
pumpkin_data::sound::SoundCategory::Weather
|
|
||||||
}
|
|
||||||
pumpkin::plugin::world::SoundCategory::Blocks => {
|
|
||||||
pumpkin_data::sound::SoundCategory::Blocks
|
|
||||||
}
|
|
||||||
pumpkin::plugin::world::SoundCategory::Hostile => {
|
|
||||||
pumpkin_data::sound::SoundCategory::Hostile
|
|
||||||
}
|
|
||||||
pumpkin::plugin::world::SoundCategory::Neutral => {
|
|
||||||
pumpkin_data::sound::SoundCategory::Neutral
|
|
||||||
}
|
|
||||||
pumpkin::plugin::world::SoundCategory::Players => {
|
|
||||||
pumpkin_data::sound::SoundCategory::Players
|
|
||||||
}
|
|
||||||
pumpkin::plugin::world::SoundCategory::Ambient => {
|
|
||||||
pumpkin_data::sound::SoundCategory::Ambient
|
|
||||||
}
|
|
||||||
pumpkin::plugin::world::SoundCategory::Voice => {
|
|
||||||
pumpkin_data::sound::SoundCategory::Voice
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
world_ref.provider.play_sound_raw(
|
world_ref.provider.play_sound_raw(
|
||||||
sound_data as u16,
|
sound_data as u16,
|
||||||
@ -917,6 +886,27 @@ impl pumpkin::plugin::world::HostWorld for PluginHostState {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn play_custom_sound(
|
||||||
|
&mut self,
|
||||||
|
world: Resource<World>,
|
||||||
|
sound_name: String,
|
||||||
|
category: pumpkin::plugin::sounds::SoundCategory,
|
||||||
|
pos: pumpkin::plugin::common::Position,
|
||||||
|
volume: f32,
|
||||||
|
pitch: f32,
|
||||||
|
) -> wasmtime::Result<()> {
|
||||||
|
let world_ref = self.get_world_res(&world)?;
|
||||||
|
let internal_category = from_wit_sound_category(category);
|
||||||
|
world_ref.provider.play_custom_sound(
|
||||||
|
&sound_name,
|
||||||
|
internal_category,
|
||||||
|
&pumpkin_util::math::vector3::Vector3::new(pos.0, pos.1, pos.2),
|
||||||
|
volume,
|
||||||
|
pitch,
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
async fn spawn_particle(
|
async fn spawn_particle(
|
||||||
&mut self,
|
&mut self,
|
||||||
world: Resource<World>,
|
world: Resource<World>,
|
||||||
@ -2307,6 +2297,41 @@ impl pumpkin_world::generation::generator::CustomChunkGenerator for WasmChunkGen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub const fn from_wit_sound_category(
|
||||||
|
category: pumpkin::plugin::sounds::SoundCategory,
|
||||||
|
) -> pumpkin_data::sound::SoundCategory {
|
||||||
|
match category {
|
||||||
|
pumpkin::plugin::sounds::SoundCategory::Master => {
|
||||||
|
pumpkin_data::sound::SoundCategory::Master
|
||||||
|
}
|
||||||
|
pumpkin::plugin::sounds::SoundCategory::Music => pumpkin_data::sound::SoundCategory::Music,
|
||||||
|
pumpkin::plugin::sounds::SoundCategory::Records => {
|
||||||
|
pumpkin_data::sound::SoundCategory::Records
|
||||||
|
}
|
||||||
|
pumpkin::plugin::sounds::SoundCategory::Weather => {
|
||||||
|
pumpkin_data::sound::SoundCategory::Weather
|
||||||
|
}
|
||||||
|
pumpkin::plugin::sounds::SoundCategory::Blocks => {
|
||||||
|
pumpkin_data::sound::SoundCategory::Blocks
|
||||||
|
}
|
||||||
|
pumpkin::plugin::sounds::SoundCategory::Hostile => {
|
||||||
|
pumpkin_data::sound::SoundCategory::Hostile
|
||||||
|
}
|
||||||
|
pumpkin::plugin::sounds::SoundCategory::Neutral => {
|
||||||
|
pumpkin_data::sound::SoundCategory::Neutral
|
||||||
|
}
|
||||||
|
pumpkin::plugin::sounds::SoundCategory::Players => {
|
||||||
|
pumpkin_data::sound::SoundCategory::Players
|
||||||
|
}
|
||||||
|
pumpkin::plugin::sounds::SoundCategory::Ambient => {
|
||||||
|
pumpkin_data::sound::SoundCategory::Ambient
|
||||||
|
}
|
||||||
|
pumpkin::plugin::sounds::SoundCategory::Voice => pumpkin_data::sound::SoundCategory::Voice,
|
||||||
|
pumpkin::plugin::sounds::SoundCategory::Ui => pumpkin_data::sound::SoundCategory::Ui,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::pumpkin;
|
use super::pumpkin;
|
||||||
|
|||||||
@ -748,6 +748,12 @@ impl Server {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Broadcasts custom server links to all players on the server.
|
||||||
|
pub fn broadcast_server_links(&self, links: &[pumpkin_protocol::Link<'_>]) {
|
||||||
|
let packet = pumpkin_protocol::java::client::play::CPlayServerLinks::new(links);
|
||||||
|
self.broadcast_packet_all(&packet);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn broadcast_tab_list_header_footer(&self, header: &TextComponent, footer: &TextComponent) {
|
pub fn broadcast_tab_list_header_footer(&self, header: &TextComponent, footer: &TextComponent) {
|
||||||
let packet = CTabList::new(header, footer);
|
let packet = CTabList::new(header, footer);
|
||||||
for world in self.worlds.load().iter() {
|
for world in self.worlds.load().iter() {
|
||||||
|
|||||||
@ -126,7 +126,9 @@ use pumpkin_protocol::{
|
|||||||
};
|
};
|
||||||
use pumpkin_protocol::{
|
use pumpkin_protocol::{
|
||||||
codec::item_stack_seralizer::ItemStackSerializer,
|
codec::item_stack_seralizer::ItemStackSerializer,
|
||||||
java::client::play::{CBlockEvent, CRemoveMobEffect, CSetEquipment, CUpdateMobEffect},
|
java::client::play::{
|
||||||
|
CBlockEvent, CParticle, CRemoveMobEffect, CSetEquipment, CUpdateMobEffect,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use pumpkin_util::resource_location::ResourceLocation;
|
use pumpkin_util::resource_location::ResourceLocation;
|
||||||
use pumpkin_util::text::{TextComponent, color::NamedColor};
|
use pumpkin_util::text::{TextComponent, color::NamedColor};
|
||||||
@ -1207,6 +1209,52 @@ impl World {
|
|||||||
self.play_sound_raw(sound as u16, category, position, volume, pitch);
|
self.play_sound_raw(sound as u16, category, position, volume, pitch);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Plays a custom sound event by identifier for all players in range.
|
||||||
|
pub fn play_custom_sound(
|
||||||
|
&self,
|
||||||
|
sound_name: &str,
|
||||||
|
category: SoundCategory,
|
||||||
|
position: &Vector3<f64>,
|
||||||
|
volume: f32,
|
||||||
|
pitch: f32,
|
||||||
|
) {
|
||||||
|
let seed = rand::random::<f64>();
|
||||||
|
let packet = CSoundEffect::new(
|
||||||
|
pumpkin_protocol::IdOr::Value(pumpkin_protocol::SoundEvent {
|
||||||
|
sound_name: sound_name.into(),
|
||||||
|
range: None,
|
||||||
|
}),
|
||||||
|
category,
|
||||||
|
position,
|
||||||
|
volume,
|
||||||
|
pitch,
|
||||||
|
seed,
|
||||||
|
);
|
||||||
|
self.broadcast_packet_all(&packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Spawns a cluster of particles in the world for all players in range.
|
||||||
|
pub fn spawn_particles(
|
||||||
|
&self,
|
||||||
|
particle: pumpkin_data::particle::Particle,
|
||||||
|
pos: Vector3<f64>,
|
||||||
|
count: u32,
|
||||||
|
offset: Vector3<f32>,
|
||||||
|
max_speed: f32,
|
||||||
|
) {
|
||||||
|
let packet = CParticle::new(
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
pos,
|
||||||
|
offset,
|
||||||
|
max_speed,
|
||||||
|
count as i32,
|
||||||
|
(particle.to_id() as i32).into(),
|
||||||
|
&[],
|
||||||
|
);
|
||||||
|
self.broadcast_packet_all(&packet);
|
||||||
|
}
|
||||||
|
|
||||||
/// Plays a Bedrock level sound for players close enough to hear it.
|
/// Plays a Bedrock level sound for players close enough to hear it.
|
||||||
pub fn play_bedrock_level_sound(
|
pub fn play_bedrock_level_sound(
|
||||||
&self,
|
&self,
|
||||||
|
|||||||
@ -73,6 +73,7 @@ mod registry;
|
|||||||
mod remap;
|
mod remap;
|
||||||
mod scoreboard_slot;
|
mod scoreboard_slot;
|
||||||
mod screen;
|
mod screen;
|
||||||
|
mod sdk;
|
||||||
mod sound;
|
mod sound;
|
||||||
mod sound_category;
|
mod sound_category;
|
||||||
mod spawn_egg;
|
mod spawn_egg;
|
||||||
@ -174,6 +175,7 @@ pub fn main() {
|
|||||||
let filters: Vec<String> = std::env::args().skip(1).collect();
|
let filters: Vec<String> = std::env::args().skip(1).collect();
|
||||||
let build_functions: Vec<_> = if filters.is_empty() {
|
let build_functions: Vec<_> = if filters.is_empty() {
|
||||||
wit::main();
|
wit::main();
|
||||||
|
sdk::main();
|
||||||
build_functions
|
build_functions
|
||||||
} else {
|
} else {
|
||||||
build_functions
|
build_functions
|
||||||
|
|||||||
103
tools/pumpkin-codegen/src/sdk/block.rs
Normal file
103
tools/pumpkin-codegen/src/sdk/block.rs
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
use heck::ToPascalCase;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use std::collections::BTreeSet;
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct BlocksAsset {
|
||||||
|
blocks: Vec<BlockEntry>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct BlockEntry {
|
||||||
|
name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build() -> String {
|
||||||
|
let json: BlocksAsset =
|
||||||
|
serde_json::from_str(&fs::read_to_string("../../assets/blocks.json").unwrap())
|
||||||
|
.expect("Failed to parse blocks.json");
|
||||||
|
|
||||||
|
let mut names: BTreeSet<String> = BTreeSet::new();
|
||||||
|
for b in json.blocks {
|
||||||
|
names.insert(b.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut code = String::new();
|
||||||
|
code.push_str("// Generated by pumpkin-codegen. Do not edit.\n");
|
||||||
|
code.push_str("#![allow(missing_docs, clippy::all)]\n\n");
|
||||||
|
code.push_str("#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\n");
|
||||||
|
code.push_str("pub enum BlockType {\n");
|
||||||
|
|
||||||
|
for name in &names {
|
||||||
|
let pascal = name.to_pascal_case();
|
||||||
|
code.push_str(&format!(" {pascal},\n"));
|
||||||
|
}
|
||||||
|
|
||||||
|
code.push_str("}\n\n");
|
||||||
|
|
||||||
|
code.push_str("impl BlockType {\n");
|
||||||
|
code.push_str(
|
||||||
|
" /// Returns the plain block name (e.g. `\"stone\"` or `\"diamond_block\"`).\n",
|
||||||
|
);
|
||||||
|
code.push_str(" #[must_use]\n");
|
||||||
|
code.push_str(" pub const fn as_str(&self) -> &'static str {\n");
|
||||||
|
code.push_str(" match self {\n");
|
||||||
|
for name in &names {
|
||||||
|
let pascal = name.to_pascal_case();
|
||||||
|
code.push_str(&format!(" Self::{pascal} => \"{name}\",\n"));
|
||||||
|
}
|
||||||
|
code.push_str(" }\n");
|
||||||
|
code.push_str(" }\n\n");
|
||||||
|
|
||||||
|
code.push_str(
|
||||||
|
" /// Returns the namespaced resource location (e.g. `\"minecraft:stone\"`).\n",
|
||||||
|
);
|
||||||
|
code.push_str(" #[must_use]\n");
|
||||||
|
code.push_str(" pub const fn resource_location(&self) -> &'static str {\n");
|
||||||
|
code.push_str(" match self {\n");
|
||||||
|
for name in &names {
|
||||||
|
let pascal = name.to_pascal_case();
|
||||||
|
code.push_str(&format!(
|
||||||
|
" Self::{pascal} => \"minecraft:{name}\",\n"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
code.push_str(" }\n");
|
||||||
|
code.push_str(" }\n\n");
|
||||||
|
|
||||||
|
code.push_str(" /// Resolves a typed `BlockType` from a name or namespaced identifier.\n");
|
||||||
|
code.push_str(" #[must_use]\n");
|
||||||
|
code.push_str(" pub fn from_name(name: &str) -> Option<Self> {\n");
|
||||||
|
code.push_str(" let clean = name.strip_prefix(\"minecraft:\").unwrap_or(name);\n");
|
||||||
|
code.push_str(" match clean {\n");
|
||||||
|
for name in &names {
|
||||||
|
let pascal = name.to_pascal_case();
|
||||||
|
code.push_str(&format!(
|
||||||
|
" \"{name}\" => Some(Self::{pascal}),\n"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
code.push_str(" _ => None,\n");
|
||||||
|
code.push_str(" }\n");
|
||||||
|
code.push_str(" }\n\n");
|
||||||
|
|
||||||
|
code.push_str(" /// Resolves a typed `BlockType` from a registry key.\n");
|
||||||
|
code.push_str(" #[must_use]\n");
|
||||||
|
code.push_str(" pub fn from_registry_key(key: &str) -> Option<Self> {\n");
|
||||||
|
code.push_str(" Self::from_name(key)\n");
|
||||||
|
code.push_str(" }\n");
|
||||||
|
code.push_str("}\n\n");
|
||||||
|
|
||||||
|
code.push_str("impl std::fmt::Display for BlockType {\n");
|
||||||
|
code.push_str(" fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n");
|
||||||
|
code.push_str(" write!(f, \"{}\", self.resource_location())\n");
|
||||||
|
code.push_str(" }\n");
|
||||||
|
code.push_str("}\n\n");
|
||||||
|
|
||||||
|
code.push_str("impl AsRef<str> for BlockType {\n");
|
||||||
|
code.push_str(" fn as_ref(&self) -> &str {\n");
|
||||||
|
code.push_str(" self.as_str()\n");
|
||||||
|
code.push_str(" }\n");
|
||||||
|
code.push_str("}\n");
|
||||||
|
|
||||||
|
code
|
||||||
|
}
|
||||||
87
tools/pumpkin-codegen/src/sdk/item.rs
Normal file
87
tools/pumpkin-codegen/src/sdk/item.rs
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
use heck::ToPascalCase;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
|
pub fn build() -> String {
|
||||||
|
let json: BTreeMap<String, serde_json::Value> =
|
||||||
|
serde_json::from_str(&fs::read_to_string("../../assets/items.json").unwrap())
|
||||||
|
.expect("Failed to parse items.json");
|
||||||
|
|
||||||
|
let mut code = String::new();
|
||||||
|
code.push_str("// Generated by pumpkin-codegen. Do not edit.\n");
|
||||||
|
code.push_str("#![allow(missing_docs, clippy::all)]\n\n");
|
||||||
|
code.push_str("#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\n");
|
||||||
|
code.push_str("pub enum Item {\n");
|
||||||
|
|
||||||
|
for name in json.keys() {
|
||||||
|
let pascal = name.to_pascal_case();
|
||||||
|
code.push_str(&format!(" {pascal},\n"));
|
||||||
|
}
|
||||||
|
|
||||||
|
code.push_str("}\n\n");
|
||||||
|
|
||||||
|
code.push_str("impl Item {\n");
|
||||||
|
code.push_str(
|
||||||
|
" /// Returns the plain item name (e.g. `\"diamond\"` or `\"netherite_sword\"`).\n",
|
||||||
|
);
|
||||||
|
code.push_str(" #[must_use]\n");
|
||||||
|
code.push_str(" pub const fn as_str(&self) -> &'static str {\n");
|
||||||
|
code.push_str(" match self {\n");
|
||||||
|
for name in json.keys() {
|
||||||
|
let pascal = name.to_pascal_case();
|
||||||
|
code.push_str(&format!(" Self::{pascal} => \"{name}\",\n"));
|
||||||
|
}
|
||||||
|
code.push_str(" }\n");
|
||||||
|
code.push_str(" }\n\n");
|
||||||
|
|
||||||
|
code.push_str(
|
||||||
|
" /// Returns the namespaced resource location (e.g. `\"minecraft:diamond\"`).\n",
|
||||||
|
);
|
||||||
|
code.push_str(" #[must_use]\n");
|
||||||
|
code.push_str(" pub const fn resource_location(&self) -> &'static str {\n");
|
||||||
|
code.push_str(" match self {\n");
|
||||||
|
for name in json.keys() {
|
||||||
|
let pascal = name.to_pascal_case();
|
||||||
|
code.push_str(&format!(
|
||||||
|
" Self::{pascal} => \"minecraft:{name}\",\n"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
code.push_str(" }\n");
|
||||||
|
code.push_str(" }\n\n");
|
||||||
|
|
||||||
|
code.push_str(" /// Resolves a typed `Item` from a name or namespaced identifier.\n");
|
||||||
|
code.push_str(" #[must_use]\n");
|
||||||
|
code.push_str(" pub fn from_name(name: &str) -> Option<Self> {\n");
|
||||||
|
code.push_str(" let clean = name.strip_prefix(\"minecraft:\").unwrap_or(name);\n");
|
||||||
|
code.push_str(" match clean {\n");
|
||||||
|
for name in json.keys() {
|
||||||
|
let pascal = name.to_pascal_case();
|
||||||
|
code.push_str(&format!(
|
||||||
|
" \"{name}\" => Some(Self::{pascal}),\n"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
code.push_str(" _ => None,\n");
|
||||||
|
code.push_str(" }\n");
|
||||||
|
code.push_str(" }\n\n");
|
||||||
|
|
||||||
|
code.push_str(" /// Resolves a typed `Item` from a registry key.\n");
|
||||||
|
code.push_str(" #[must_use]\n");
|
||||||
|
code.push_str(" pub fn from_registry_key(key: &str) -> Option<Self> {\n");
|
||||||
|
code.push_str(" Self::from_name(key)\n");
|
||||||
|
code.push_str(" }\n");
|
||||||
|
code.push_str("}\n\n");
|
||||||
|
|
||||||
|
code.push_str("impl std::fmt::Display for Item {\n");
|
||||||
|
code.push_str(" fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n");
|
||||||
|
code.push_str(" write!(f, \"{}\", self.resource_location())\n");
|
||||||
|
code.push_str(" }\n");
|
||||||
|
code.push_str("}\n\n");
|
||||||
|
|
||||||
|
code.push_str("impl AsRef<str> for Item {\n");
|
||||||
|
code.push_str(" fn as_ref(&self) -> &str {\n");
|
||||||
|
code.push_str(" self.as_str()\n");
|
||||||
|
code.push_str(" }\n");
|
||||||
|
code.push_str("}\n");
|
||||||
|
|
||||||
|
code
|
||||||
|
}
|
||||||
21
tools/pumpkin-codegen/src/sdk/mod.rs
Normal file
21
tools/pumpkin-codegen/src/sdk/mod.rs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
pub mod block;
|
||||||
|
pub mod item;
|
||||||
|
|
||||||
|
use std::fs;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
pub const SDK_OUT_DIR: &str = "../../crates/pumpkin-plugin-api/src/generated";
|
||||||
|
|
||||||
|
pub fn main() {
|
||||||
|
fs::create_dir_all(SDK_OUT_DIR).expect("Failed to create SDK generated directory");
|
||||||
|
|
||||||
|
let targets: Vec<(fn() -> String, &str)> =
|
||||||
|
vec![(item::build, "item.rs"), (block::build, "block.rs")];
|
||||||
|
|
||||||
|
for (build_fn, file) in targets {
|
||||||
|
println!("Generating SDK for {}", file);
|
||||||
|
let code = build_fn();
|
||||||
|
let path = Path::new(SDK_OUT_DIR).join(file);
|
||||||
|
fs::write(&path, code).unwrap_or_else(|_| panic!("Failed to write {file}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,6 +1,7 @@
|
|||||||
use crate::wit::utils::map_type;
|
use crate::wit::utils::map_type_with_defined;
|
||||||
use heck::ToKebabCase;
|
use heck::ToKebabCase;
|
||||||
use semver::Version;
|
use semver::Version;
|
||||||
|
use std::collections::HashSet;
|
||||||
use std::{fs, path::Path};
|
use std::{fs, path::Path};
|
||||||
use syn::{Fields, Item};
|
use syn::{Fields, Item};
|
||||||
use wit_encoder::{
|
use wit_encoder::{
|
||||||
@ -20,18 +21,39 @@ pub fn build() -> String {
|
|||||||
|
|
||||||
let mut serverbound_variant = Variant::empty();
|
let mut serverbound_variant = Variant::empty();
|
||||||
let mut clientbound_variant = Variant::empty();
|
let mut clientbound_variant = Variant::empty();
|
||||||
|
let mut defined_cases = HashSet::new();
|
||||||
|
|
||||||
|
let dirs = [
|
||||||
|
"../../crates/pumpkin-protocol/src/bedrock/server",
|
||||||
|
"../../crates/pumpkin-protocol/src/bedrock/client",
|
||||||
|
"../../crates/pumpkin-protocol/src/bedrock/network_item.rs",
|
||||||
|
];
|
||||||
|
|
||||||
|
let defined_types = collect_defined_types(&dirs);
|
||||||
|
|
||||||
// Process serverbound packets
|
// Process serverbound packets
|
||||||
process_packets(
|
process_packets(
|
||||||
"../../crates/pumpkin-protocol/src/bedrock/server",
|
"../../crates/pumpkin-protocol/src/bedrock/server",
|
||||||
&mut interface,
|
&mut interface,
|
||||||
&mut serverbound_variant,
|
&mut serverbound_variant,
|
||||||
|
&mut defined_cases,
|
||||||
|
&defined_types,
|
||||||
);
|
);
|
||||||
// Process clientbound packets
|
// Process clientbound packets
|
||||||
process_packets(
|
process_packets(
|
||||||
"../../crates/pumpkin-protocol/src/bedrock/client",
|
"../../crates/pumpkin-protocol/src/bedrock/client",
|
||||||
&mut interface,
|
&mut interface,
|
||||||
&mut clientbound_variant,
|
&mut clientbound_variant,
|
||||||
|
&mut defined_cases,
|
||||||
|
&defined_types,
|
||||||
|
);
|
||||||
|
// Process network_item helper types
|
||||||
|
process_packets(
|
||||||
|
"../../crates/pumpkin-protocol/src/bedrock/network_item.rs",
|
||||||
|
&mut interface,
|
||||||
|
&mut clientbound_variant,
|
||||||
|
&mut defined_cases,
|
||||||
|
&defined_types,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Add an 'unknown' fallback variant (no payload) — raw payload is carried on the event record
|
// Add an 'unknown' fallback variant (no payload) — raw payload is carried on the event record
|
||||||
@ -51,7 +73,75 @@ pub fn build() -> String {
|
|||||||
package.to_string()
|
package.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_packets(dir: &str, interface: &mut Interface, variant: &mut Variant) {
|
fn collect_defined_types(dirs: &[&str]) -> HashSet<String> {
|
||||||
|
let mut defined = HashSet::new();
|
||||||
|
for &dir in dirs {
|
||||||
|
collect_defined_types_in_path(Path::new(dir), &mut defined);
|
||||||
|
}
|
||||||
|
defined
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_defined_types_in_path(path: &Path, defined: &mut HashSet<String>) {
|
||||||
|
if path.is_file() {
|
||||||
|
if path.extension().is_some_and(|ext| ext == "rs")
|
||||||
|
&& path.file_name().is_some_and(|name| name != "mod.rs")
|
||||||
|
&& let Ok(content) = fs::read_to_string(path)
|
||||||
|
&& let Ok(file) = syn::parse_file(&content)
|
||||||
|
{
|
||||||
|
for item in file.items {
|
||||||
|
match item {
|
||||||
|
Item::Struct(s)
|
||||||
|
if has_static_packet_attr(&s.attrs) || is_valid_helper_struct(&s) =>
|
||||||
|
{
|
||||||
|
defined.insert(s.ident.to_string().to_kebab_case());
|
||||||
|
}
|
||||||
|
Item::Enum(e)
|
||||||
|
if has_static_packet_attr(&e.attrs) || is_valid_helper_enum(&e) =>
|
||||||
|
{
|
||||||
|
defined.insert(e.ident.to_string().to_kebab_case());
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Ok(paths) = fs::read_dir(path) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
for entry in paths.flatten() {
|
||||||
|
let p = entry.path();
|
||||||
|
collect_defined_types_in_path(&p, defined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_valid_helper_struct(s: &syn::ItemStruct) -> bool {
|
||||||
|
matches!(s.vis, syn::Visibility::Public(_))
|
||||||
|
&& matches!(s.fields, Fields::Named(_))
|
||||||
|
&& s.generics.type_params().next().is_none()
|
||||||
|
&& s.generics.const_params().next().is_none()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_valid_helper_enum(e: &syn::ItemEnum) -> bool {
|
||||||
|
matches!(e.vis, syn::Visibility::Public(_))
|
||||||
|
&& e.generics.type_params().next().is_none()
|
||||||
|
&& e.generics.const_params().next().is_none()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_packets(
|
||||||
|
dir: &str,
|
||||||
|
interface: &mut Interface,
|
||||||
|
variant: &mut Variant,
|
||||||
|
defined_cases: &mut HashSet<String>,
|
||||||
|
defined_types: &HashSet<String>,
|
||||||
|
) {
|
||||||
|
let path = Path::new(dir);
|
||||||
|
if path.is_file() {
|
||||||
|
parse_packet_file(path, interface, variant, defined_cases, defined_types);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let paths = fs::read_dir(dir).expect("Failed to read packet directory");
|
let paths = fs::read_dir(dir).expect("Failed to read packet directory");
|
||||||
let mut sorted_paths: Vec<_> = paths
|
let mut sorted_paths: Vec<_> = paths
|
||||||
.map(|e| e.expect("Failed to read entry").path())
|
.map(|e| e.expect("Failed to read entry").path())
|
||||||
@ -60,13 +150,19 @@ fn process_packets(dir: &str, interface: &mut Interface, variant: &mut Variant)
|
|||||||
|
|
||||||
for path in sorted_paths {
|
for path in sorted_paths {
|
||||||
if path.is_dir() {
|
if path.is_dir() {
|
||||||
process_packets(path.to_str().unwrap(), interface, variant);
|
process_packets(
|
||||||
|
path.to_str().unwrap(),
|
||||||
|
interface,
|
||||||
|
variant,
|
||||||
|
defined_cases,
|
||||||
|
defined_types,
|
||||||
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if path.extension().is_some_and(|ext| ext == "rs")
|
if path.extension().is_some_and(|ext| ext == "rs")
|
||||||
&& path.file_name().is_some_and(|name| name != "mod.rs")
|
&& path.file_name().is_some_and(|name| name != "mod.rs")
|
||||||
{
|
{
|
||||||
parse_packet_file(&path, interface, variant);
|
parse_packet_file(&path, interface, variant, defined_cases, defined_types);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -104,39 +200,83 @@ fn has_static_packet_attr(attrs: &[syn::Attribute]) -> bool {
|
|||||||
|
|
||||||
fn collect_types(
|
fn collect_types(
|
||||||
fields: syn::punctuated::Punctuated<syn::Field, syn::token::Comma>,
|
fields: syn::punctuated::Punctuated<syn::Field, syn::token::Comma>,
|
||||||
|
defined_types: &HashSet<String>,
|
||||||
) -> Vec<WitType> {
|
) -> Vec<WitType> {
|
||||||
fields
|
fields
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|field| map_type(&field.ty))
|
.map(|field| map_type_with_defined(&field.ty, Some(defined_types)))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn collect_fields(
|
fn collect_fields(
|
||||||
named_fields: syn::punctuated::Punctuated<syn::Field, syn::token::Comma>,
|
named_fields: syn::punctuated::Punctuated<syn::Field, syn::token::Comma>,
|
||||||
|
defined_types: &HashSet<String>,
|
||||||
) -> Vec<Field> {
|
) -> Vec<Field> {
|
||||||
named_fields
|
named_fields
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|field| {
|
.map(|field| {
|
||||||
let field_name = field.ident.as_ref().unwrap().to_string().to_kebab_case();
|
let field_name = field.ident.as_ref().unwrap().to_string().to_kebab_case();
|
||||||
let field_type = map_type(&field.ty);
|
let field_type = map_type_with_defined(&field.ty, Some(defined_types));
|
||||||
Field::new(field_name, field_type)
|
Field::new(field_name, field_type)
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_struct(s: syn::ItemStruct, interface: &mut Interface, variant: &mut Variant) {
|
fn process_struct(
|
||||||
|
s: syn::ItemStruct,
|
||||||
|
interface: &mut Interface,
|
||||||
|
variant: &mut Variant,
|
||||||
|
defined_cases: &mut HashSet<String>,
|
||||||
|
defined_types: &HashSet<String>,
|
||||||
|
) {
|
||||||
let wit_name = s.ident.to_string().to_kebab_case();
|
let wit_name = s.ident.to_string().to_kebab_case();
|
||||||
|
if !defined_cases.insert(wit_name.clone()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let fields_list = match s.fields {
|
let fields_list = match s.fields {
|
||||||
Fields::Named(fields) => collect_fields(fields.named),
|
Fields::Named(fields) => collect_fields(fields.named, defined_types),
|
||||||
_ => Vec::new(),
|
_ => Vec::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
register_wit_type(wit_name, fields_list, interface, variant, None);
|
register_wit_type(wit_name, fields_list, interface, variant, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_enum(e: syn::ItemEnum, interface: &mut Interface, variant: &mut Variant) {
|
fn process_helper_struct(
|
||||||
|
s: syn::ItemStruct,
|
||||||
|
interface: &mut Interface,
|
||||||
|
defined_cases: &mut HashSet<String>,
|
||||||
|
defined_types: &HashSet<String>,
|
||||||
|
) {
|
||||||
|
let wit_name = s.ident.to_string().to_kebab_case();
|
||||||
|
if !defined_cases.insert(wit_name.clone()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let fields_list = match s.fields {
|
||||||
|
Fields::Named(fields) => collect_fields(fields.named, defined_types),
|
||||||
|
_ => Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if !fields_list.is_empty() {
|
||||||
|
interface.type_def(TypeDef::new(
|
||||||
|
wit_name,
|
||||||
|
TypeDefKind::Record(Record::new(fields_list)),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_enum(
|
||||||
|
e: syn::ItemEnum,
|
||||||
|
interface: &mut Interface,
|
||||||
|
variant: &mut Variant,
|
||||||
|
defined_cases: &mut HashSet<String>,
|
||||||
|
defined_types: &HashSet<String>,
|
||||||
|
) {
|
||||||
let enum_wit_name = e.ident.to_string().to_kebab_case();
|
let enum_wit_name = e.ident.to_string().to_kebab_case();
|
||||||
|
if !defined_cases.insert(enum_wit_name.clone()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let mut cases = Vec::new();
|
let mut cases = Vec::new();
|
||||||
|
|
||||||
for v in e.variants {
|
for v in e.variants {
|
||||||
@ -144,18 +284,22 @@ fn process_enum(e: syn::ItemEnum, interface: &mut Interface, variant: &mut Varia
|
|||||||
|
|
||||||
match v.fields {
|
match v.fields {
|
||||||
Fields::Named(fields) => {
|
Fields::Named(fields) => {
|
||||||
let fields_list = collect_fields(fields.named);
|
let sub_record_name = format!("{}-{}", enum_wit_name, variant_wit_name);
|
||||||
register_wit_type(
|
let fields_list = collect_fields(fields.named, defined_types);
|
||||||
|
if !fields_list.is_empty() && defined_cases.insert(sub_record_name.clone()) {
|
||||||
|
interface.type_def(TypeDef::new(
|
||||||
|
sub_record_name.clone(),
|
||||||
|
TypeDefKind::Record(Record::new(fields_list)),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
cases.push(VariantCase::value(
|
||||||
variant_wit_name,
|
variant_wit_name,
|
||||||
fields_list,
|
WitType::named(sub_record_name),
|
||||||
interface,
|
));
|
||||||
variant,
|
|
||||||
Some(enum_wit_name.clone()),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Fields::Unnamed(fields) => {
|
Fields::Unnamed(fields) => {
|
||||||
let types = collect_types(fields.unnamed);
|
let types = collect_types(fields.unnamed, defined_types);
|
||||||
match types.len() {
|
match types.len() {
|
||||||
0 => cases.push(VariantCase::empty(variant_wit_name)),
|
0 => cases.push(VariantCase::empty(variant_wit_name)),
|
||||||
1 => cases.push(VariantCase::value(
|
1 => cases.push(VariantCase::value(
|
||||||
@ -177,7 +321,7 @@ fn process_enum(e: syn::ItemEnum, interface: &mut Interface, variant: &mut Varia
|
|||||||
WitType::named(enum_wit_name.clone()),
|
WitType::named(enum_wit_name.clone()),
|
||||||
));
|
));
|
||||||
|
|
||||||
// define weather to use Enum or Variant
|
// define whether to use Enum or Variant
|
||||||
let all_empty = cases.iter().all(|c| c.type_().is_none());
|
let all_empty = cases.iter().all(|c| c.type_().is_none());
|
||||||
if all_empty {
|
if all_empty {
|
||||||
interface.type_def(TypeDef::new(
|
interface.type_def(TypeDef::new(
|
||||||
@ -194,17 +338,94 @@ fn process_enum(e: syn::ItemEnum, interface: &mut Interface, variant: &mut Varia
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_packet_file(path: &Path, interface: &mut Interface, variant: &mut Variant) {
|
fn process_helper_enum(
|
||||||
|
e: syn::ItemEnum,
|
||||||
|
interface: &mut Interface,
|
||||||
|
defined_cases: &mut HashSet<String>,
|
||||||
|
defined_types: &HashSet<String>,
|
||||||
|
) {
|
||||||
|
let enum_wit_name = e.ident.to_string().to_kebab_case();
|
||||||
|
if !defined_cases.insert(enum_wit_name.clone()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let mut cases = Vec::new();
|
||||||
|
|
||||||
|
for v in e.variants {
|
||||||
|
let variant_wit_name = v.ident.to_string().to_kebab_case();
|
||||||
|
|
||||||
|
match v.fields {
|
||||||
|
Fields::Named(fields) => {
|
||||||
|
let sub_record_name = format!("{}-{}", enum_wit_name, variant_wit_name);
|
||||||
|
let fields_list = collect_fields(fields.named, defined_types);
|
||||||
|
if !fields_list.is_empty() && defined_cases.insert(sub_record_name.clone()) {
|
||||||
|
interface.type_def(TypeDef::new(
|
||||||
|
sub_record_name.clone(),
|
||||||
|
TypeDefKind::Record(Record::new(fields_list)),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
cases.push(VariantCase::value(
|
||||||
|
variant_wit_name,
|
||||||
|
WitType::named(sub_record_name),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Fields::Unnamed(fields) => {
|
||||||
|
let types = collect_types(fields.unnamed, defined_types);
|
||||||
|
match types.len() {
|
||||||
|
0 => cases.push(VariantCase::empty(variant_wit_name)),
|
||||||
|
1 => cases.push(VariantCase::value(
|
||||||
|
variant_wit_name,
|
||||||
|
types.into_iter().next().unwrap(),
|
||||||
|
)),
|
||||||
|
_ => cases.push(VariantCase::value(variant_wit_name, WitType::tuple(types))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Fields::Unit => {
|
||||||
|
cases.push(VariantCase::empty(variant_wit_name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let all_empty = cases.iter().all(|c| c.type_().is_none());
|
||||||
|
if all_empty {
|
||||||
|
interface.type_def(TypeDef::new(
|
||||||
|
enum_wit_name,
|
||||||
|
TypeDefKind::Enum(Enum::from_iter(
|
||||||
|
cases.into_iter().map(|c| EnumCase::new(c.name().clone())),
|
||||||
|
)),
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
interface.type_def(TypeDef::new(
|
||||||
|
enum_wit_name,
|
||||||
|
TypeDefKind::Variant(Variant::from(cases)),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_packet_file(
|
||||||
|
path: &Path,
|
||||||
|
interface: &mut Interface,
|
||||||
|
variant: &mut Variant,
|
||||||
|
defined_cases: &mut HashSet<String>,
|
||||||
|
defined_types: &HashSet<String>,
|
||||||
|
) {
|
||||||
let content = fs::read_to_string(path).expect("Failed to read file");
|
let content = fs::read_to_string(path).expect("Failed to read file");
|
||||||
let file = syn::parse_file(&content).expect("Failed to parse file");
|
let file = syn::parse_file(&content).expect("Failed to parse file");
|
||||||
|
|
||||||
for item in file.items {
|
for item in file.items {
|
||||||
match item {
|
match item {
|
||||||
Item::Struct(s) if has_static_packet_attr(&s.attrs) => {
|
Item::Struct(s) if has_static_packet_attr(&s.attrs) => {
|
||||||
process_struct(s, interface, variant);
|
process_struct(s, interface, variant, defined_cases, defined_types);
|
||||||
}
|
}
|
||||||
Item::Enum(e) if has_static_packet_attr(&e.attrs) => {
|
Item::Enum(e) if has_static_packet_attr(&e.attrs) => {
|
||||||
process_enum(e, interface, variant);
|
process_enum(e, interface, variant, defined_cases, defined_types);
|
||||||
|
}
|
||||||
|
Item::Struct(s) if is_valid_helper_struct(&s) => {
|
||||||
|
process_helper_struct(s, interface, defined_cases, defined_types);
|
||||||
|
}
|
||||||
|
Item::Enum(e) if is_valid_helper_enum(&e) => {
|
||||||
|
process_helper_enum(e, interface, defined_cases, defined_types);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|||||||
30
tools/pumpkin-codegen/src/wit/entity_status.rs
Normal file
30
tools/pumpkin-codegen/src/wit/entity_status.rs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
use heck::ToKebabCase;
|
||||||
|
use semver::Version;
|
||||||
|
use std::{collections::BTreeMap, fs};
|
||||||
|
use wit_encoder::{Enum, Interface, Package, PackageName, TypeDef, TypeDefKind};
|
||||||
|
|
||||||
|
pub fn build() -> String {
|
||||||
|
let events: BTreeMap<String, u8> =
|
||||||
|
serde_json::from_str(&fs::read_to_string("../../assets/entity_statuses.json").unwrap())
|
||||||
|
.expect("Failed to parse entity_statuses.json");
|
||||||
|
|
||||||
|
let mut package = Package::new(PackageName::new(
|
||||||
|
"pumpkin",
|
||||||
|
"plugin",
|
||||||
|
Some(Version::new(0, 1, 0)),
|
||||||
|
));
|
||||||
|
let mut interface = Interface::new("entity-statuses");
|
||||||
|
|
||||||
|
let mut status_enum = Enum::empty();
|
||||||
|
for (event_name, _) in events {
|
||||||
|
status_enum.case(event_name.to_kebab_case());
|
||||||
|
}
|
||||||
|
|
||||||
|
interface.type_def(TypeDef::new(
|
||||||
|
"entity-status",
|
||||||
|
TypeDefKind::Enum(status_enum),
|
||||||
|
));
|
||||||
|
package.interface(interface);
|
||||||
|
|
||||||
|
package.to_string()
|
||||||
|
}
|
||||||
33
tools/pumpkin-codegen/src/wit/game_event.rs
Normal file
33
tools/pumpkin-codegen/src/wit/game_event.rs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
use semver::Version;
|
||||||
|
use std::fs;
|
||||||
|
use wit_encoder::{Enum, Interface, Package, PackageName, TypeDef, TypeDefKind};
|
||||||
|
|
||||||
|
pub fn build() -> String {
|
||||||
|
let game_events: Vec<String> =
|
||||||
|
serde_json::from_str(&fs::read_to_string("../../assets/game_event.json").unwrap())
|
||||||
|
.expect("Failed to parse game_event.json");
|
||||||
|
|
||||||
|
let mut package = Package::new(PackageName::new(
|
||||||
|
"pumpkin",
|
||||||
|
"plugin",
|
||||||
|
Some(Version::new(0, 1, 0)),
|
||||||
|
));
|
||||||
|
let mut interface = Interface::new("game-events");
|
||||||
|
|
||||||
|
let mut game_event_enum = Enum::empty();
|
||||||
|
for game_event in game_events {
|
||||||
|
let name = game_event
|
||||||
|
.strip_prefix("minecraft:")
|
||||||
|
.unwrap_or(&game_event)
|
||||||
|
.replace('_', "-");
|
||||||
|
game_event_enum.case(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface.type_def(TypeDef::new(
|
||||||
|
"game-event",
|
||||||
|
TypeDefKind::Enum(game_event_enum),
|
||||||
|
));
|
||||||
|
package.interface(interface);
|
||||||
|
|
||||||
|
package.to_string()
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
use crate::wit::utils::map_type;
|
use crate::wit::utils::map_type_with_defined;
|
||||||
use heck::ToKebabCase;
|
use heck::ToKebabCase;
|
||||||
use semver::Version;
|
use semver::Version;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
@ -22,29 +22,47 @@ pub fn build() -> String {
|
|||||||
|
|
||||||
let mut serverbound_variant = Variant::empty();
|
let mut serverbound_variant = Variant::empty();
|
||||||
let mut clientbound_variant = Variant::empty();
|
let mut clientbound_variant = Variant::empty();
|
||||||
let mut serverbound_cases = HashSet::new();
|
let mut defined_cases = HashSet::new();
|
||||||
let mut clientbound_cases = HashSet::new();
|
|
||||||
|
let server_states = &["config", "handshake", "login", "play", "status"];
|
||||||
|
let client_states = &["config", "login", "play", "status"];
|
||||||
|
|
||||||
|
let mut dirs = Vec::new();
|
||||||
|
for state in server_states {
|
||||||
|
dirs.push((
|
||||||
|
format!("../../crates/pumpkin-protocol/src/java/server/{}", state),
|
||||||
|
*state,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
for state in client_states {
|
||||||
|
dirs.push((
|
||||||
|
format!("../../crates/pumpkin-protocol/src/java/client/{}", state),
|
||||||
|
*state,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let defined_types = collect_defined_types(&dirs);
|
||||||
|
|
||||||
// Process serverbound packets
|
// Process serverbound packets
|
||||||
let server_states = &["config", "handshake", "login", "play", "status"];
|
|
||||||
for state in server_states {
|
for state in server_states {
|
||||||
process_packets(
|
process_packets(
|
||||||
&format!("../../crates/pumpkin-protocol/src/java/server/{}", state),
|
&format!("../../crates/pumpkin-protocol/src/java/server/{}", state),
|
||||||
state,
|
state,
|
||||||
&mut interface,
|
&mut interface,
|
||||||
&mut serverbound_variant,
|
&mut serverbound_variant,
|
||||||
&mut serverbound_cases,
|
&mut defined_cases,
|
||||||
|
&defined_types,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// Process clientbound packets
|
// Process clientbound packets
|
||||||
let client_states = &["config", "login", "play", "status"];
|
|
||||||
for state in client_states {
|
for state in client_states {
|
||||||
process_packets(
|
process_packets(
|
||||||
&format!("../../crates/pumpkin-protocol/src/java/client/{}", state),
|
&format!("../../crates/pumpkin-protocol/src/java/client/{}", state),
|
||||||
state,
|
state,
|
||||||
&mut interface,
|
&mut interface,
|
||||||
&mut clientbound_variant,
|
&mut clientbound_variant,
|
||||||
&mut clientbound_cases,
|
&mut defined_cases,
|
||||||
|
&defined_types,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,12 +83,64 @@ pub fn build() -> String {
|
|||||||
package.to_string()
|
package.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn collect_defined_types(dirs: &[(String, &str)]) -> HashSet<String> {
|
||||||
|
let mut defined = HashSet::new();
|
||||||
|
for (dir, state) in dirs {
|
||||||
|
let Ok(paths) = fs::read_dir(dir) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
for entry in paths.flatten() {
|
||||||
|
let path = entry.path();
|
||||||
|
if path.extension().is_some_and(|ext| ext == "rs")
|
||||||
|
&& path.file_name().is_some_and(|name| name != "mod.rs")
|
||||||
|
{
|
||||||
|
if let Ok(content) = fs::read_to_string(&path)
|
||||||
|
&& let Ok(file) = syn::parse_file(&content)
|
||||||
|
{
|
||||||
|
for item in file.items {
|
||||||
|
match item {
|
||||||
|
Item::Struct(s) if has_java_packet_attr(&s.attrs) => {
|
||||||
|
defined.insert(wit_name(s.ident.to_string(), state));
|
||||||
|
}
|
||||||
|
Item::Enum(e) if has_java_packet_attr(&e.attrs) => {
|
||||||
|
defined.insert(wit_name(e.ident.to_string(), state));
|
||||||
|
}
|
||||||
|
Item::Struct(s) if is_valid_helper_struct(&s) => {
|
||||||
|
defined.insert(s.ident.to_string().to_kebab_case());
|
||||||
|
}
|
||||||
|
Item::Enum(e) if is_valid_helper_enum(&e) => {
|
||||||
|
defined.insert(e.ident.to_string().to_kebab_case());
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defined
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_valid_helper_struct(s: &syn::ItemStruct) -> bool {
|
||||||
|
matches!(s.vis, syn::Visibility::Public(_))
|
||||||
|
&& matches!(s.fields, Fields::Named(_))
|
||||||
|
&& s.generics.type_params().next().is_none()
|
||||||
|
&& s.generics.const_params().next().is_none()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_valid_helper_enum(e: &syn::ItemEnum) -> bool {
|
||||||
|
matches!(e.vis, syn::Visibility::Public(_))
|
||||||
|
&& e.generics.type_params().next().is_none()
|
||||||
|
&& e.generics.const_params().next().is_none()
|
||||||
|
}
|
||||||
|
|
||||||
fn process_packets(
|
fn process_packets(
|
||||||
dir: &str,
|
dir: &str,
|
||||||
state: &str,
|
state: &str,
|
||||||
interface: &mut Interface,
|
interface: &mut Interface,
|
||||||
variant: &mut Variant,
|
variant: &mut Variant,
|
||||||
defined_cases: &mut HashSet<String>,
|
defined_cases: &mut HashSet<String>,
|
||||||
|
defined_types: &HashSet<String>,
|
||||||
) {
|
) {
|
||||||
let paths = fs::read_dir(dir).expect("Failed to read packet directory");
|
let paths = fs::read_dir(dir).expect("Failed to read packet directory");
|
||||||
let mut sorted_paths: Vec<_> = paths
|
let mut sorted_paths: Vec<_> = paths
|
||||||
@ -79,10 +149,28 @@ fn process_packets(
|
|||||||
sorted_paths.sort();
|
sorted_paths.sort();
|
||||||
|
|
||||||
for path in sorted_paths {
|
for path in sorted_paths {
|
||||||
|
if path.is_dir() {
|
||||||
|
process_packets(
|
||||||
|
path.to_str().unwrap(),
|
||||||
|
state,
|
||||||
|
interface,
|
||||||
|
variant,
|
||||||
|
defined_cases,
|
||||||
|
defined_types,
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if path.extension().is_some_and(|ext| ext == "rs")
|
if path.extension().is_some_and(|ext| ext == "rs")
|
||||||
&& path.file_name().is_some_and(|name| name != "mod.rs")
|
&& path.file_name().is_some_and(|name| name != "mod.rs")
|
||||||
{
|
{
|
||||||
parse_packet_file(&path, state, interface, variant, defined_cases);
|
parse_packet_file(
|
||||||
|
&path,
|
||||||
|
state,
|
||||||
|
interface,
|
||||||
|
variant,
|
||||||
|
defined_cases,
|
||||||
|
defined_types,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -93,6 +181,7 @@ fn parse_packet_file(
|
|||||||
interface: &mut Interface,
|
interface: &mut Interface,
|
||||||
variant: &mut Variant,
|
variant: &mut Variant,
|
||||||
defined_cases: &mut HashSet<String>,
|
defined_cases: &mut HashSet<String>,
|
||||||
|
defined_types: &HashSet<String>,
|
||||||
) {
|
) {
|
||||||
let content = fs::read_to_string(path).expect("Failed to read file");
|
let content = fs::read_to_string(path).expect("Failed to read file");
|
||||||
let file = syn::parse_file(&content).expect("Failed to parse file");
|
let file = syn::parse_file(&content).expect("Failed to parse file");
|
||||||
@ -100,10 +189,16 @@ fn parse_packet_file(
|
|||||||
for item in file.items {
|
for item in file.items {
|
||||||
match item {
|
match item {
|
||||||
Item::Struct(s) if has_java_packet_attr(&s.attrs) => {
|
Item::Struct(s) if has_java_packet_attr(&s.attrs) => {
|
||||||
process_struct(s, state, interface, variant, defined_cases);
|
process_struct(s, state, interface, variant, defined_cases, defined_types);
|
||||||
}
|
}
|
||||||
Item::Enum(e) if has_java_packet_attr(&e.attrs) => {
|
Item::Enum(e) if has_java_packet_attr(&e.attrs) => {
|
||||||
process_enum(e, state, interface, variant, defined_cases);
|
process_enum(e, state, interface, variant, defined_cases, defined_types);
|
||||||
|
}
|
||||||
|
Item::Struct(s) if is_valid_helper_struct(&s) => {
|
||||||
|
process_helper_struct(s, interface, defined_cases, defined_types);
|
||||||
|
}
|
||||||
|
Item::Enum(e) if is_valid_helper_enum(&e) => {
|
||||||
|
process_helper_enum(e, interface, defined_cases, defined_types);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
@ -143,23 +238,25 @@ fn register_wit_type(
|
|||||||
|
|
||||||
fn collect_types(
|
fn collect_types(
|
||||||
fields: syn::punctuated::Punctuated<syn::Field, syn::token::Comma>,
|
fields: syn::punctuated::Punctuated<syn::Field, syn::token::Comma>,
|
||||||
|
defined_types: &HashSet<String>,
|
||||||
) -> Vec<WitType> {
|
) -> Vec<WitType> {
|
||||||
fields
|
fields
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|field| extract_type_name(&field.ty) != "DynamicRecipe")
|
.filter(|field| extract_type_name(&field.ty) != "DynamicRecipe")
|
||||||
.map(|field| map_type(&field.ty))
|
.map(|field| map_type_with_defined(&field.ty, Some(defined_types)))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn collect_fields(
|
fn collect_fields(
|
||||||
named_fields: syn::punctuated::Punctuated<syn::Field, syn::token::Comma>,
|
named_fields: syn::punctuated::Punctuated<syn::Field, syn::token::Comma>,
|
||||||
|
defined_types: &HashSet<String>,
|
||||||
) -> Vec<Field> {
|
) -> Vec<Field> {
|
||||||
named_fields
|
named_fields
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|field| extract_type_name(&field.ty) != "DynamicRecipe")
|
.filter(|field| extract_type_name(&field.ty) != "DynamicRecipe")
|
||||||
.map(|field| {
|
.map(|field| {
|
||||||
let field_name = field.ident.as_ref().unwrap().to_string().to_kebab_case();
|
let field_name = field.ident.as_ref().unwrap().to_string().to_kebab_case();
|
||||||
let field_type = map_type(&field.ty);
|
let field_type = map_type_with_defined(&field.ty, Some(defined_types));
|
||||||
Field::new(field_name, field_type)
|
Field::new(field_name, field_type)
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
@ -179,25 +276,50 @@ fn process_struct(
|
|||||||
interface: &mut Interface,
|
interface: &mut Interface,
|
||||||
variant: &mut Variant,
|
variant: &mut Variant,
|
||||||
defined_cases: &mut HashSet<String>,
|
defined_cases: &mut HashSet<String>,
|
||||||
|
defined_types: &HashSet<String>,
|
||||||
) {
|
) {
|
||||||
let wit_name = wit_name(s.ident.to_string(), state);
|
let wit_name = wit_name(s.ident.to_string(), state);
|
||||||
if !defined_cases.insert(wit_name.clone()) {
|
if !defined_cases.insert(wit_name.clone()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let fields_list = match s.fields {
|
let fields_list = match s.fields {
|
||||||
Fields::Named(fields) => collect_fields(fields.named),
|
Fields::Named(fields) => collect_fields(fields.named, defined_types),
|
||||||
_ => Vec::new(),
|
_ => Vec::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
register_wit_type(wit_name, fields_list, interface, variant, None);
|
register_wit_type(wit_name, fields_list, interface, variant, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn process_helper_struct(
|
||||||
|
s: syn::ItemStruct,
|
||||||
|
interface: &mut Interface,
|
||||||
|
defined_cases: &mut HashSet<String>,
|
||||||
|
defined_types: &HashSet<String>,
|
||||||
|
) {
|
||||||
|
let wit_name = s.ident.to_string().to_kebab_case();
|
||||||
|
if !defined_cases.insert(wit_name.clone()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let fields_list = match s.fields {
|
||||||
|
Fields::Named(fields) => collect_fields(fields.named, defined_types),
|
||||||
|
_ => Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if !fields_list.is_empty() {
|
||||||
|
interface.type_def(TypeDef::new(
|
||||||
|
wit_name,
|
||||||
|
TypeDefKind::Record(Record::new(fields_list)),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn process_enum(
|
fn process_enum(
|
||||||
e: syn::ItemEnum,
|
e: syn::ItemEnum,
|
||||||
state: &str,
|
state: &str,
|
||||||
interface: &mut Interface,
|
interface: &mut Interface,
|
||||||
variant: &mut Variant,
|
variant: &mut Variant,
|
||||||
defined_cases: &mut HashSet<String>,
|
defined_cases: &mut HashSet<String>,
|
||||||
|
defined_types: &HashSet<String>,
|
||||||
) {
|
) {
|
||||||
let enum_wit_name = wit_name(e.ident.to_string(), state);
|
let enum_wit_name = wit_name(e.ident.to_string(), state);
|
||||||
if !defined_cases.insert(enum_wit_name.clone()) {
|
if !defined_cases.insert(enum_wit_name.clone()) {
|
||||||
@ -210,18 +332,22 @@ fn process_enum(
|
|||||||
|
|
||||||
match v.fields {
|
match v.fields {
|
||||||
Fields::Named(fields) => {
|
Fields::Named(fields) => {
|
||||||
let fields_list = collect_fields(fields.named);
|
let sub_record_name = format!("{}-{}", enum_wit_name, variant_wit_name);
|
||||||
register_wit_type(
|
let fields_list = collect_fields(fields.named, defined_types);
|
||||||
|
if !fields_list.is_empty() && defined_cases.insert(sub_record_name.clone()) {
|
||||||
|
interface.type_def(TypeDef::new(
|
||||||
|
sub_record_name.clone(),
|
||||||
|
TypeDefKind::Record(Record::new(fields_list)),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
cases.push(VariantCase::value(
|
||||||
variant_wit_name,
|
variant_wit_name,
|
||||||
fields_list,
|
WitType::named(sub_record_name),
|
||||||
interface,
|
));
|
||||||
variant,
|
|
||||||
Some(enum_wit_name.clone()),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Fields::Unnamed(fields) => {
|
Fields::Unnamed(fields) => {
|
||||||
let types = collect_types(fields.unnamed);
|
let types = collect_types(fields.unnamed, defined_types);
|
||||||
match types.len() {
|
match types.len() {
|
||||||
0 => cases.push(VariantCase::empty(variant_wit_name)),
|
0 => cases.push(VariantCase::empty(variant_wit_name)),
|
||||||
1 => cases.push(VariantCase::value(
|
1 => cases.push(VariantCase::value(
|
||||||
@ -243,7 +369,72 @@ fn process_enum(
|
|||||||
WitType::named(enum_wit_name.clone()),
|
WitType::named(enum_wit_name.clone()),
|
||||||
));
|
));
|
||||||
|
|
||||||
// define weather to use Enum or Variant
|
// define whether to use Enum or Variant
|
||||||
|
let all_empty = cases.iter().all(|c| c.type_().is_none());
|
||||||
|
if all_empty {
|
||||||
|
interface.type_def(TypeDef::new(
|
||||||
|
enum_wit_name,
|
||||||
|
TypeDefKind::Enum(Enum::from_iter(
|
||||||
|
cases.into_iter().map(|c| EnumCase::new(c.name().clone())),
|
||||||
|
)),
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
interface.type_def(TypeDef::new(
|
||||||
|
enum_wit_name,
|
||||||
|
TypeDefKind::Variant(Variant::from(cases)),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_helper_enum(
|
||||||
|
e: syn::ItemEnum,
|
||||||
|
interface: &mut Interface,
|
||||||
|
defined_cases: &mut HashSet<String>,
|
||||||
|
defined_types: &HashSet<String>,
|
||||||
|
) {
|
||||||
|
let enum_wit_name = e.ident.to_string().to_kebab_case();
|
||||||
|
if !defined_cases.insert(enum_wit_name.clone()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let mut cases = Vec::new();
|
||||||
|
|
||||||
|
for v in e.variants {
|
||||||
|
let variant_wit_name = v.ident.to_string().to_kebab_case();
|
||||||
|
|
||||||
|
match v.fields {
|
||||||
|
Fields::Named(fields) => {
|
||||||
|
let sub_record_name = format!("{}-{}", enum_wit_name, variant_wit_name);
|
||||||
|
let fields_list = collect_fields(fields.named, defined_types);
|
||||||
|
if !fields_list.is_empty() && defined_cases.insert(sub_record_name.clone()) {
|
||||||
|
interface.type_def(TypeDef::new(
|
||||||
|
sub_record_name.clone(),
|
||||||
|
TypeDefKind::Record(Record::new(fields_list)),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
cases.push(VariantCase::value(
|
||||||
|
variant_wit_name,
|
||||||
|
WitType::named(sub_record_name),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Fields::Unnamed(fields) => {
|
||||||
|
let types = collect_types(fields.unnamed, defined_types);
|
||||||
|
match types.len() {
|
||||||
|
0 => cases.push(VariantCase::empty(variant_wit_name)),
|
||||||
|
1 => cases.push(VariantCase::value(
|
||||||
|
variant_wit_name,
|
||||||
|
types.into_iter().next().unwrap(),
|
||||||
|
)),
|
||||||
|
_ => cases.push(VariantCase::value(variant_wit_name, WitType::tuple(types))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Fields::Unit => {
|
||||||
|
cases.push(VariantCase::empty(variant_wit_name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let all_empty = cases.iter().all(|c| c.type_().is_none());
|
let all_empty = cases.iter().all(|c| c.type_().is_none());
|
||||||
if all_empty {
|
if all_empty {
|
||||||
interface.type_def(TypeDef::new(
|
interface.type_def(TypeDef::new(
|
||||||
|
|||||||
@ -4,11 +4,14 @@ pub mod biome;
|
|||||||
pub mod damage_type;
|
pub mod damage_type;
|
||||||
pub mod data_component;
|
pub mod data_component;
|
||||||
pub mod enchantment;
|
pub mod enchantment;
|
||||||
|
pub mod entity_status;
|
||||||
pub mod entity_type;
|
pub mod entity_type;
|
||||||
|
pub mod game_event;
|
||||||
pub mod game_rules;
|
pub mod game_rules;
|
||||||
pub mod java_packet;
|
pub mod java_packet;
|
||||||
pub mod packet_mapping;
|
pub mod packet_mapping;
|
||||||
pub mod particle;
|
pub mod particle;
|
||||||
|
pub mod potion;
|
||||||
pub mod screen;
|
pub mod screen;
|
||||||
pub mod sound;
|
pub mod sound;
|
||||||
pub mod statistic;
|
pub mod statistic;
|
||||||
@ -42,6 +45,9 @@ pub fn main() {
|
|||||||
(screen::build, "screens.wit"),
|
(screen::build, "screens.wit"),
|
||||||
(statistic::build, "statistics.wit"),
|
(statistic::build, "statistics.wit"),
|
||||||
(game_rules::build, "game-rules.wit"),
|
(game_rules::build, "game-rules.wit"),
|
||||||
|
(game_event::build, "game-events.wit"),
|
||||||
|
(potion::build, "potions.wit"),
|
||||||
|
(entity_status::build, "entity-statuses.wit"),
|
||||||
];
|
];
|
||||||
|
|
||||||
for (build_fn, file) in build_functions {
|
for (build_fn, file) in build_functions {
|
||||||
|
|||||||
36
tools/pumpkin-codegen/src/wit/potion.rs
Normal file
36
tools/pumpkin-codegen/src/wit/potion.rs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
use semver::Version;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use std::fs;
|
||||||
|
use wit_encoder::{Enum, Interface, Package, PackageName, TypeDef, TypeDefKind};
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct PotionRaw {
|
||||||
|
id: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build() -> String {
|
||||||
|
let raw_map: BTreeMap<String, PotionRaw> =
|
||||||
|
serde_json::from_str(&fs::read_to_string("../../assets/potion.json").unwrap())
|
||||||
|
.expect("Failed to parse potion.json");
|
||||||
|
|
||||||
|
let mut potions: Vec<(String, PotionRaw)> = raw_map.into_iter().collect();
|
||||||
|
potions.sort_by_key(|(_, raw)| raw.id);
|
||||||
|
|
||||||
|
let mut package = Package::new(PackageName::new(
|
||||||
|
"pumpkin",
|
||||||
|
"plugin",
|
||||||
|
Some(Version::new(0, 1, 0)),
|
||||||
|
));
|
||||||
|
let mut interface = Interface::new("potions");
|
||||||
|
let mut potion_enum = Enum::empty();
|
||||||
|
|
||||||
|
for (name, _) in potions {
|
||||||
|
potion_enum.case(name.replace('_', "-"));
|
||||||
|
}
|
||||||
|
|
||||||
|
interface.type_def(TypeDef::new("potion-type", TypeDefKind::Enum(potion_enum)));
|
||||||
|
package.interface(interface);
|
||||||
|
|
||||||
|
package.to_string()
|
||||||
|
}
|
||||||
@ -14,6 +14,24 @@ pub fn build() -> String {
|
|||||||
));
|
));
|
||||||
let mut interface = Interface::new("sounds");
|
let mut interface = Interface::new("sounds");
|
||||||
|
|
||||||
|
let mut category_enum = Enum::empty();
|
||||||
|
category_enum.case("master");
|
||||||
|
category_enum.case("music");
|
||||||
|
category_enum.case("records");
|
||||||
|
category_enum.case("weather");
|
||||||
|
category_enum.case("blocks");
|
||||||
|
category_enum.case("hostile");
|
||||||
|
category_enum.case("neutral");
|
||||||
|
category_enum.case("players");
|
||||||
|
category_enum.case("ambient");
|
||||||
|
category_enum.case("voice");
|
||||||
|
category_enum.case("ui");
|
||||||
|
|
||||||
|
interface.type_def(TypeDef::new(
|
||||||
|
"sound-category",
|
||||||
|
TypeDefKind::Enum(category_enum),
|
||||||
|
));
|
||||||
|
|
||||||
let mut sound_enum = Enum::empty();
|
let mut sound_enum = Enum::empty();
|
||||||
for sound in sounds {
|
for sound in sounds {
|
||||||
sound_enum.case(sound.replace(['_', '.'], "-"));
|
sound_enum.case(sound.replace(['_', '.'], "-"));
|
||||||
|
|||||||
@ -1,48 +1,112 @@
|
|||||||
use syn::{Type, TypePath};
|
use heck::ToKebabCase;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use syn::{GenericArgument, PathArguments, Type, TypePath};
|
||||||
use wit_encoder::Type as WitType;
|
use wit_encoder::Type as WitType;
|
||||||
|
|
||||||
pub fn map_type(ty: &Type) -> WitType {
|
pub fn map_type(ty: &Type) -> WitType {
|
||||||
|
map_type_with_defined(ty, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn map_type_with_defined(ty: &Type, defined_types: Option<&HashSet<String>>) -> WitType {
|
||||||
match ty {
|
match ty {
|
||||||
Type::Path(TypePath { path, .. }) => {
|
Type::Path(TypePath { path, .. }) => {
|
||||||
let last_segment = path.segments.last().unwrap();
|
let last_segment = path.segments.last().unwrap();
|
||||||
let ident_str = last_segment.ident.to_string();
|
let ident_str = last_segment.ident.to_string();
|
||||||
match ident_str.as_str() {
|
match ident_str.as_str() {
|
||||||
"String" | "TextComponent" => WitType::String,
|
"String" | "TextComponent" | "str" | "Identifier" | "ResourceLocation"
|
||||||
|
| "LpVector3d" => WitType::String,
|
||||||
"Uuid" => WitType::Named("uuid".into()),
|
"Uuid" => WitType::Named("uuid".into()),
|
||||||
"i32" | "VarInt" | "u32" | "VarUInt" | "usize" => WitType::S32,
|
"i32" | "VarInt" | "u32" | "VarUInt" | "usize" | "u24" => WitType::S32,
|
||||||
"i64" | "u64" | "VarLong" | "VarULong" => WitType::S64,
|
"i64" | "u64" | "VarLong" | "VarULong" => WitType::S64,
|
||||||
"bool" => WitType::Bool,
|
"bool" => WitType::Bool,
|
||||||
"f32" => WitType::F32,
|
"f32" => WitType::F32,
|
||||||
"f64" => WitType::F64,
|
"f64" => WitType::F64,
|
||||||
"u8" | "i8" => WitType::U8,
|
"u8" | "i8" => WitType::U8,
|
||||||
"u16" | "i16" => WitType::S32,
|
"u16" | "i16" => WitType::S32,
|
||||||
|
"Bytes" | "BoxedU8Slice" | "ChunkData" => WitType::list(WitType::U8),
|
||||||
|
"BitSet" | "Bitset" => WitType::list(WitType::S64),
|
||||||
|
"NbtCompound" | "Nbt" | "DynamicRecipe" => WitType::String,
|
||||||
"Option" => {
|
"Option" => {
|
||||||
if let syn::PathArguments::AngleBracketed(args) = &last_segment.arguments
|
if let PathArguments::AngleBracketed(args) = &last_segment.arguments
|
||||||
&& let Some(syn::GenericArgument::Type(inner_ty)) = args.args.first()
|
&& let Some(GenericArgument::Type(inner_ty)) = args.args.first()
|
||||||
{
|
{
|
||||||
return WitType::option(map_type(inner_ty));
|
return WitType::option(map_type_with_defined(inner_ty, defined_types));
|
||||||
}
|
}
|
||||||
WitType::String
|
WitType::String
|
||||||
}
|
}
|
||||||
"Box" => {
|
"Box" => {
|
||||||
if let syn::PathArguments::AngleBracketed(args) = &last_segment.arguments
|
if let PathArguments::AngleBracketed(args) = &last_segment.arguments
|
||||||
&& let Some(syn::GenericArgument::Type(inner_ty)) = args.args.first()
|
&& let Some(GenericArgument::Type(inner_ty)) = args.args.first()
|
||||||
{
|
{
|
||||||
return map_type(inner_ty);
|
if let Type::Slice(ts) = inner_ty {
|
||||||
|
if let Type::Path(tp) = &*ts.elem
|
||||||
|
&& tp.path.segments.last().unwrap().ident == "u8"
|
||||||
|
{
|
||||||
|
return WitType::list(WitType::U8);
|
||||||
|
}
|
||||||
|
return WitType::list(map_type_with_defined(&ts.elem, defined_types));
|
||||||
|
}
|
||||||
|
if let Type::Path(tp) = inner_ty
|
||||||
|
&& tp.path.segments.last().unwrap().ident == "str"
|
||||||
|
{
|
||||||
|
return WitType::String;
|
||||||
|
}
|
||||||
|
return map_type_with_defined(inner_ty, defined_types);
|
||||||
}
|
}
|
||||||
WitType::String
|
WitType::String
|
||||||
}
|
}
|
||||||
"Vec" => {
|
"Vec" => {
|
||||||
if let syn::PathArguments::AngleBracketed(args) = &last_segment.arguments
|
if let PathArguments::AngleBracketed(args) = &last_segment.arguments
|
||||||
&& let Some(syn::GenericArgument::Type(inner_ty)) = args.args.first()
|
&& let Some(GenericArgument::Type(inner_ty)) = args.args.first()
|
||||||
{
|
{
|
||||||
// Check if the inner type is u8
|
|
||||||
if let Type::Path(tp) = inner_ty
|
if let Type::Path(tp) = inner_ty
|
||||||
&& tp.path.segments.last().unwrap().ident == "u8"
|
&& tp.path.segments.last().unwrap().ident == "u8"
|
||||||
{
|
{
|
||||||
return WitType::list(WitType::U8);
|
return WitType::list(WitType::U8);
|
||||||
}
|
}
|
||||||
return WitType::list(map_type(inner_ty));
|
return WitType::list(map_type_with_defined(inner_ty, defined_types));
|
||||||
|
}
|
||||||
|
WitType::String
|
||||||
|
}
|
||||||
|
"Cow" => {
|
||||||
|
if let PathArguments::AngleBracketed(args) = &last_segment.arguments {
|
||||||
|
for arg in &args.args {
|
||||||
|
if let GenericArgument::Type(inner_ty) = arg {
|
||||||
|
if let Type::Path(tp) = inner_ty
|
||||||
|
&& tp.path.segments.last().unwrap().ident == "str"
|
||||||
|
{
|
||||||
|
return WitType::String;
|
||||||
|
}
|
||||||
|
return map_type_with_defined(inner_ty, defined_types);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
WitType::String
|
||||||
|
}
|
||||||
|
"NonZero" | "EnumAsStr" | "IdOr" => {
|
||||||
|
if let PathArguments::AngleBracketed(args) = &last_segment.arguments
|
||||||
|
&& let Some(GenericArgument::Type(inner_ty)) = args.args.first()
|
||||||
|
{
|
||||||
|
return map_type_with_defined(inner_ty, defined_types);
|
||||||
|
}
|
||||||
|
WitType::String
|
||||||
|
}
|
||||||
|
"HashMap" => {
|
||||||
|
if let PathArguments::AngleBracketed(args) = &last_segment.arguments {
|
||||||
|
let type_args: Vec<_> = args
|
||||||
|
.args
|
||||||
|
.iter()
|
||||||
|
.filter_map(|a| match a {
|
||||||
|
GenericArgument::Type(t) => Some(t),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
if type_args.len() == 2 {
|
||||||
|
return WitType::list(WitType::tuple(vec![
|
||||||
|
map_type_with_defined(type_args[0], defined_types),
|
||||||
|
map_type_with_defined(type_args[1], defined_types),
|
||||||
|
]));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
WitType::String
|
WitType::String
|
||||||
}
|
}
|
||||||
@ -53,19 +117,54 @@ pub fn map_type(ty: &Type) -> WitType {
|
|||||||
WitType::tuple(vec![WitType::S32, WitType::S32, WitType::S32])
|
WitType::tuple(vec![WitType::S32, WitType::S32, WitType::S32])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => WitType::String,
|
"Vector2" => WitType::tuple(vec![WitType::F64, WitType::F64]),
|
||||||
|
_ => {
|
||||||
|
let kebab = ident_str.to_kebab_case();
|
||||||
|
if let Some(defined) = defined_types {
|
||||||
|
if defined.contains(&kebab) {
|
||||||
|
WitType::named(kebab)
|
||||||
|
} else {
|
||||||
|
WitType::String
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
WitType::named(kebab)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Type::Reference(tr) => map_type(&tr.elem),
|
}
|
||||||
|
}
|
||||||
|
Type::Reference(tr) => map_type_with_defined(&tr.elem, defined_types),
|
||||||
Type::Slice(ts) => {
|
Type::Slice(ts) => {
|
||||||
if let Type::Path(tp) = &*ts.elem
|
if let Type::Path(tp) = &*ts.elem
|
||||||
&& tp.path.segments.last().unwrap().ident == "u8"
|
&& tp.path.segments.last().unwrap().ident == "u8"
|
||||||
{
|
{
|
||||||
WitType::list(WitType::U8)
|
WitType::list(WitType::U8)
|
||||||
} else {
|
} else {
|
||||||
WitType::list(map_type(&ts.elem))
|
WitType::list(map_type_with_defined(&ts.elem, defined_types))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Type::Array(ta) => {
|
||||||
|
if let Type::Path(tp) = &*ta.elem
|
||||||
|
&& tp.path.segments.last().unwrap().ident == "u8"
|
||||||
|
{
|
||||||
|
WitType::list(WitType::U8)
|
||||||
|
} else {
|
||||||
|
WitType::list(map_type_with_defined(&ta.elem, defined_types))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Type::Tuple(tt) => {
|
||||||
|
if tt.elems.is_empty() {
|
||||||
|
WitType::String
|
||||||
|
} else {
|
||||||
|
WitType::tuple(
|
||||||
|
tt.elems
|
||||||
|
.iter()
|
||||||
|
.map(|e| map_type_with_defined(e, defined_types))
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Type::Paren(tp) => map_type_with_defined(&tp.elem, defined_types),
|
||||||
|
Type::Group(tg) => map_type_with_defined(&tg.elem, defined_types),
|
||||||
_ => WitType::String,
|
_ => WitType::String,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user