feat(bedrock): add resource pack support
This commit is contained in:
parent
f1263fff21
commit
3b2eaf9467
@ -319,7 +319,7 @@ impl LoadConfiguration for AdvancedConfiguration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn validate(&self) {
|
fn validate(&self) {
|
||||||
self.resource_pack.validate();
|
//self.resource_pack.validate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,12 +1,17 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
/// Configuration for server resource pack distribution.
|
|
||||||
///
|
|
||||||
/// Controls whether a resource pack is offered or enforced,
|
|
||||||
/// along with its metadata and client prompt behaviour.
|
|
||||||
#[derive(Deserialize, Serialize, Default)]
|
#[derive(Deserialize, Serialize, Default)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct ResourcePackConfig {
|
pub struct ResourcePackConfig {
|
||||||
|
pub java: JavaResourcePackConfig,
|
||||||
|
pub bedrock: BedrockResourcePackConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Java-specific resource pack configuration (Single URL/Hash)
|
||||||
|
#[derive(Deserialize, Serialize, Default)]
|
||||||
|
#[serde(default)]
|
||||||
|
pub struct JavaResourcePackConfig {
|
||||||
/// Whether the resource pack system is enabled.
|
/// Whether the resource pack system is enabled.
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
/// The URL to the resource pack.
|
/// The URL to the resource pack.
|
||||||
@ -19,23 +24,33 @@ pub struct ResourcePackConfig {
|
|||||||
pub force: bool,
|
pub force: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResourcePackConfig {
|
/// Bedrock-specific configuration (Supports multiple local/remote packs)
|
||||||
pub fn validate(&self) {
|
#[derive(Deserialize, Serialize, Default)]
|
||||||
if !self.enabled {
|
#[serde(default)]
|
||||||
return;
|
pub struct BedrockResourcePackConfig {
|
||||||
}
|
pub enabled: bool,
|
||||||
|
/// If true, players cannot join without accepting packs.
|
||||||
assert_eq!(
|
pub force: bool,
|
||||||
!self.url.is_empty(),
|
/// List of packs to be sent to the client.
|
||||||
!self.sha1.is_empty(),
|
pub packs: Vec<BedrockPack>,
|
||||||
"Resource pack path or SHA1 hash is missing"
|
}
|
||||||
);
|
|
||||||
|
#[derive(Deserialize, Serialize)]
|
||||||
let hash_len = self.sha1.len();
|
pub struct BedrockPack {
|
||||||
|
pub uuid: Uuid,
|
||||||
assert_eq!(
|
pub version: String,
|
||||||
hash_len, 40,
|
pub size: u64,
|
||||||
"Resource pack SHA1 hash is the wrong length (should be 40, is {hash_len})"
|
pub download_url: String,
|
||||||
);
|
#[serde(default)]
|
||||||
}
|
pub content_key: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub sub_pack_name: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub content_id: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub has_scripts: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
pub addon_pack: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
pub rtx_enabled: bool,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,4 +25,4 @@ pub mod set_time;
|
|||||||
pub mod set_title;
|
pub mod set_title;
|
||||||
pub mod start_game;
|
pub mod start_game;
|
||||||
pub mod update_abilities;
|
pub mod update_abilities;
|
||||||
pub mod update_artributes;
|
pub mod update_attributes;
|
||||||
|
|||||||
@ -1,33 +1,35 @@
|
|||||||
|
use crate::{bedrock::client::start_game::Experiments, serial::PacketWrite};
|
||||||
use pumpkin_macros::packet;
|
use pumpkin_macros::packet;
|
||||||
|
|
||||||
use crate::{
|
#[derive(PacketWrite)]
|
||||||
bedrock::client::start_game::Experiments, codec::var_uint::VarUInt, serial::PacketWrite,
|
pub struct ResourcePackStackEntry {
|
||||||
};
|
pub uuid: String,
|
||||||
|
pub version: String,
|
||||||
|
pub sub_pack_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(PacketWrite)]
|
#[derive(PacketWrite)]
|
||||||
#[packet(7)]
|
#[packet(7)]
|
||||||
pub struct CResourcePackStackPacket {
|
pub struct CResourcePackStackPacket {
|
||||||
// https://mojang.github.io/bedrock-protocol-docs/html/ResourcePackStackPacket.html
|
pub resource_pack_required: bool,
|
||||||
resource_pack_required: bool,
|
pub resource_packs: Vec<ResourcePackStackEntry>,
|
||||||
texture_pack_list_size: VarUInt,
|
pub game_version: String,
|
||||||
game_version: String,
|
pub experiments: Experiments,
|
||||||
experiments: Experiments,
|
pub include_editor_packs: bool,
|
||||||
/// When connecting to an Editor world, include the vanilla editor packs in the stack
|
|
||||||
include_editor_packs: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CResourcePackStackPacket {
|
impl CResourcePackStackPacket {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn new(
|
pub const fn new(
|
||||||
resource_pack_required: bool,
|
resource_pack_required: bool,
|
||||||
texture_pack_list_size: VarUInt,
|
resource_packs: Vec<ResourcePackStackEntry>,
|
||||||
game_version: String,
|
game_version: String,
|
||||||
experiments: Experiments,
|
experiments: Experiments,
|
||||||
include_editor_packs: bool,
|
include_editor_packs: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
resource_pack_required,
|
resource_pack_required,
|
||||||
texture_pack_list_size,
|
resource_packs,
|
||||||
game_version,
|
game_version,
|
||||||
experiments,
|
experiments,
|
||||||
include_editor_packs,
|
include_editor_packs,
|
||||||
|
|||||||
@ -1,39 +1,62 @@
|
|||||||
use pumpkin_macros::packet;
|
|
||||||
|
|
||||||
use crate::serial::PacketWrite;
|
use crate::serial::PacketWrite;
|
||||||
|
use pumpkin_macros::packet;
|
||||||
#[derive(PacketWrite)]
|
use std::io::{Error, Write};
|
||||||
#[packet(6)]
|
pub struct ResourcePackEntry {
|
||||||
pub struct CResourcePacksInfo {
|
pub uuid: String,
|
||||||
resource_pack_required: bool,
|
pub version: String,
|
||||||
has_addon_packs: bool,
|
pub size: u64,
|
||||||
has_scripts: bool,
|
pub content_key: String,
|
||||||
is_vibrant_visuals_force_disabled: bool,
|
pub sub_pack_name: String,
|
||||||
world_template_id: uuid::Uuid,
|
pub content_id: String,
|
||||||
world_template_version: String,
|
pub has_scripts: bool,
|
||||||
resource_packs_size: u16, // TODO: Add more
|
pub addon_pack: bool,
|
||||||
|
pub rtx_enabled: bool,
|
||||||
|
pub download_url: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CResourcePacksInfo {
|
impl PacketWrite for ResourcePackEntry {
|
||||||
#[must_use]
|
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
|
||||||
#[expect(clippy::fn_params_excessive_bools)]
|
self.uuid.write(writer)?;
|
||||||
pub const fn new(
|
self.version.write(writer)?;
|
||||||
resource_pack_required: bool,
|
// Bedrock uses Little Endian u64 for size
|
||||||
has_addon_packs: bool,
|
writer.write_all(&self.size.to_le_bytes())?;
|
||||||
has_scripts: bool,
|
self.content_key.write(writer)?;
|
||||||
is_vibrant_visuals_force_disabled: bool,
|
self.sub_pack_name.write(writer)?;
|
||||||
world_template_id: uuid::Uuid,
|
self.content_id.write(writer)?;
|
||||||
world_template_version: String,
|
self.has_scripts.write(writer)?;
|
||||||
) -> Self {
|
Ok(())
|
||||||
Self {
|
}
|
||||||
resource_pack_required,
|
}
|
||||||
has_addon_packs,
|
|
||||||
has_scripts,
|
#[packet(6)]
|
||||||
is_vibrant_visuals_force_disabled,
|
pub struct CResourcePacksInfo {
|
||||||
world_template_id,
|
pub resource_pack_required: bool,
|
||||||
world_template_version,
|
pub has_addon_packs: bool,
|
||||||
// TODO
|
pub has_scripts: bool,
|
||||||
resource_packs_size: 0,
|
pub is_vibrant_visuals_force_disabled: bool,
|
||||||
}
|
pub world_template_id: uuid::Uuid,
|
||||||
|
pub world_template_version: String,
|
||||||
|
pub resource_packs: Vec<ResourcePackEntry>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PacketWrite for CResourcePacksInfo {
|
||||||
|
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
|
||||||
|
self.resource_pack_required.write(writer)?;
|
||||||
|
self.has_addon_packs.write(writer)?;
|
||||||
|
self.has_scripts.write(writer)?;
|
||||||
|
self.is_vibrant_visuals_force_disabled.write(writer)?;
|
||||||
|
|
||||||
|
self.world_template_id.write(writer)?;
|
||||||
|
|
||||||
|
self.world_template_version.write(writer)?;
|
||||||
|
|
||||||
|
let len = self.resource_packs.len() as u16;
|
||||||
|
writer.write_all(&len.to_le_bytes())?;
|
||||||
|
|
||||||
|
for entry in &self.resource_packs {
|
||||||
|
entry.write(writer)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -94,7 +94,7 @@ pub async fn send_attribute_updates_for_living(
|
|||||||
living: &crate::entity::living::LivingEntity,
|
living: &crate::entity::living::LivingEntity,
|
||||||
attributes: Vec<Attributes>,
|
attributes: Vec<Attributes>,
|
||||||
) {
|
) {
|
||||||
use pumpkin_protocol::bedrock::client::update_artributes::{
|
use pumpkin_protocol::bedrock::client::update_attributes::{
|
||||||
Attribute as BeAttribute, CUpdateAttributes as BePacket,
|
Attribute as BeAttribute, CUpdateAttributes as BePacket,
|
||||||
};
|
};
|
||||||
use pumpkin_protocol::codec::var_int::VarInt;
|
use pumpkin_protocol::codec::var_int::VarInt;
|
||||||
|
|||||||
@ -2,20 +2,18 @@ use crate::{
|
|||||||
net::{ClientPlatform, DisconnectReason, GameProfile, PlayerConfig, bedrock::BedrockClient},
|
net::{ClientPlatform, DisconnectReason, GameProfile, PlayerConfig, bedrock::BedrockClient},
|
||||||
server::Server,
|
server::Server,
|
||||||
};
|
};
|
||||||
use pumpkin_protocol::bedrock::server::{
|
use pumpkin_protocol::bedrock::{
|
||||||
login::ClientData, resource_pack_response::SResourcePackResponse,
|
client::{
|
||||||
};
|
network_settings::CNetworkSettings, play_status::CPlayStatus,
|
||||||
use pumpkin_protocol::{
|
resource_pack_stack::CResourcePackStackPacket, resource_packs_info::CResourcePacksInfo,
|
||||||
bedrock::{
|
start_game::Experiments,
|
||||||
client::{
|
|
||||||
network_settings::CNetworkSettings, play_status::CPlayStatus,
|
|
||||||
resource_pack_stack::CResourcePackStackPacket, resource_packs_info::CResourcePacksInfo,
|
|
||||||
start_game::Experiments,
|
|
||||||
},
|
|
||||||
frame_set::FrameSet,
|
|
||||||
server::{login::SLogin, request_network_settings::SRequestNetworkSettings},
|
|
||||||
},
|
},
|
||||||
codec::var_uint::VarUInt,
|
frame_set::FrameSet,
|
||||||
|
server::{login::SLogin, request_network_settings::SRequestNetworkSettings},
|
||||||
|
};
|
||||||
|
use pumpkin_protocol::bedrock::{
|
||||||
|
client::{resource_pack_stack::ResourcePackStackEntry, resource_packs_info::ResourcePackEntry},
|
||||||
|
server::{login::ClientData, resource_pack_response::SResourcePackResponse},
|
||||||
};
|
};
|
||||||
use pumpkin_util::jwt::AuthError;
|
use pumpkin_util::jwt::AuthError;
|
||||||
use pumpkin_world::{CURRENT_BEDROCK_MC_PROTOCOL, CURRENT_BEDROCK_MC_VERSION};
|
use pumpkin_world::{CURRENT_BEDROCK_MC_PROTOCOL, CURRENT_BEDROCK_MC_VERSION};
|
||||||
@ -183,11 +181,37 @@ impl BedrockClient {
|
|||||||
|
|
||||||
self.write_game_packet_to_set(&CPlayStatus::LoginSuccess, &mut frame_set)
|
self.write_game_packet_to_set(&CPlayStatus::LoginSuccess, &mut frame_set)
|
||||||
.await;
|
.await;
|
||||||
self.write_game_packet_to_set(
|
let br_config = &server.advanced_config.resource_pack.bedrock;
|
||||||
&CResourcePacksInfo::new(false, false, false, false, Uuid::default(), String::new()),
|
|
||||||
&mut frame_set,
|
let mut entries = Vec::new();
|
||||||
)
|
if br_config.enabled {
|
||||||
.await;
|
for pack in &br_config.packs {
|
||||||
|
entries.push(ResourcePackEntry {
|
||||||
|
uuid: pack.uuid.to_string(),
|
||||||
|
version: pack.version.clone(),
|
||||||
|
size: pack.size,
|
||||||
|
download_url: pack.download_url.clone(),
|
||||||
|
content_key: pack.content_key.clone(),
|
||||||
|
sub_pack_name: pack.sub_pack_name.clone(),
|
||||||
|
content_id: pack.content_id.clone(),
|
||||||
|
has_scripts: pack.has_scripts,
|
||||||
|
addon_pack: pack.addon_pack,
|
||||||
|
rtx_enabled: pack.rtx_enabled,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let packs_info = CResourcePacksInfo {
|
||||||
|
resource_pack_required: br_config.force,
|
||||||
|
has_addon_packs: false,
|
||||||
|
has_scripts: false,
|
||||||
|
is_vibrant_visuals_force_disabled: false,
|
||||||
|
world_template_id: uuid::Uuid::nil(),
|
||||||
|
world_template_version: String::new(),
|
||||||
|
resource_packs: entries,
|
||||||
|
};
|
||||||
|
self.write_game_packet_to_set(&packs_info, &mut frame_set)
|
||||||
|
.await;
|
||||||
|
|
||||||
self.send_frame_set(frame_set, 0x84).await;
|
self.send_frame_set(frame_set, 0x84).await;
|
||||||
|
|
||||||
@ -234,10 +258,27 @@ impl BedrockClient {
|
|||||||
debug!("Bedrock: SResourcePackResponse::STATUS_HAVE_ALL_PACKS");
|
debug!("Bedrock: SResourcePackResponse::STATUS_HAVE_ALL_PACKS");
|
||||||
let mut frame_set = FrameSet::default();
|
let mut frame_set = FrameSet::default();
|
||||||
|
|
||||||
|
let br_config = &server.advanced_config.resource_pack.bedrock;
|
||||||
|
|
||||||
|
// Convert your config packs into protocol stack entries
|
||||||
|
let resource_packs = if br_config.enabled {
|
||||||
|
br_config
|
||||||
|
.packs
|
||||||
|
.iter()
|
||||||
|
.map(|pack| ResourcePackStackEntry {
|
||||||
|
uuid: pack.uuid.to_string(),
|
||||||
|
version: pack.version.clone(),
|
||||||
|
sub_pack_name: String::new(),
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
};
|
||||||
|
|
||||||
self.write_game_packet_to_set(
|
self.write_game_packet_to_set(
|
||||||
&CResourcePackStackPacket::new(
|
&CResourcePackStackPacket::new(
|
||||||
false,
|
br_config.force,
|
||||||
VarUInt(0),
|
resource_packs,
|
||||||
CURRENT_BEDROCK_MC_VERSION.to_string(),
|
CURRENT_BEDROCK_MC_VERSION.to_string(),
|
||||||
Experiments {
|
Experiments {
|
||||||
names_size: 0,
|
names_size: 0,
|
||||||
@ -248,6 +289,7 @@ impl BedrockClient {
|
|||||||
&mut frame_set,
|
&mut frame_set,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
self.send_frame_set(frame_set, 0x84).await;
|
self.send_frame_set(frame_set, 0x84).await;
|
||||||
}
|
}
|
||||||
SResourcePackResponse::STATUS_COMPLETED => {
|
SResourcePackResponse::STATUS_COMPLETED => {
|
||||||
|
|||||||
@ -76,7 +76,7 @@ impl JavaClient {
|
|||||||
server: &Server,
|
server: &Server,
|
||||||
packet: SConfigResourcePack,
|
packet: SConfigResourcePack,
|
||||||
) {
|
) {
|
||||||
let resource_config = &server.advanced_config.resource_pack;
|
let resource_config = &server.advanced_config.resource_pack.java;
|
||||||
if resource_config.enabled {
|
if resource_config.enabled {
|
||||||
let expected_uuid =
|
let expected_uuid =
|
||||||
uuid::Uuid::new_v3(&uuid::Uuid::NAMESPACE_DNS, resource_config.url.as_bytes());
|
uuid::Uuid::new_v3(&uuid::Uuid::NAMESPACE_DNS, resource_config.url.as_bytes());
|
||||||
|
|||||||
@ -368,7 +368,7 @@ impl JavaClient {
|
|||||||
self.send_packet_now(&CConfigServerLinks::new(&links)).await;
|
self.send_packet_now(&CConfigServerLinks::new(&links)).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
let resource_config = &server.advanced_config.resource_pack;
|
let resource_config = &server.advanced_config.resource_pack.java;
|
||||||
if resource_config.enabled {
|
if resource_config.enabled {
|
||||||
let uuid = Uuid::new_v3(&uuid::Uuid::NAMESPACE_DNS, resource_config.url.as_bytes());
|
let uuid = Uuid::new_v3(&uuid::Uuid::NAMESPACE_DNS, resource_config.url.as_bytes());
|
||||||
let resource_pack = CConfigAddResourcePack::new(
|
let resource_pack = CConfigAddResourcePack::new(
|
||||||
|
|||||||
@ -80,7 +80,7 @@ use pumpkin_protocol::{
|
|||||||
creative_content::{CreativeContent, Group},
|
creative_content::{CreativeContent, Group},
|
||||||
gamerules_changed::GameRules,
|
gamerules_changed::GameRules,
|
||||||
start_game::{Experiments, GamePublishSetting, LevelSettings},
|
start_game::{Experiments, GamePublishSetting, LevelSettings},
|
||||||
update_artributes::{Attribute, CUpdateAttributes},
|
update_attributes::{Attribute, CUpdateAttributes},
|
||||||
},
|
},
|
||||||
network_item::NetworkItemDescriptor,
|
network_item::NetworkItemDescriptor,
|
||||||
server::text::SText,
|
server::text::SText,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user