fix: edit game rule screen

This commit is contained in:
Alexander Medvedev 2026-09-10 21:04:33 +02:00
parent 7f369c4b5e
commit 9c89f60619
17 changed files with 506 additions and 97 deletions

98
Cargo.lock generated
View File

@ -263,9 +263,9 @@ dependencies = [
[[package]]
name = "async-compression"
version = "0.4.44"
version = "0.4.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "515a1f282e33d55983c499d7e9e87082e81cbc32974825bf9032f928392d5844"
checksum = "4f10dafd0c8d2e51ae9a748805777613ed0bbe17bf586b76c8311f45c020a32f"
dependencies = [
"compression-codecs",
"compression-core",
@ -398,9 +398,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.13.1"
version = "2.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06"
[[package]]
name = "block-buffer"
@ -746,9 +746,9 @@ dependencies = [
[[package]]
name = "compression-codecs"
version = "0.4.39"
version = "0.4.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fe67f2944eef52fc7b106b8c9450d243a88701a0c065f7f57235e76abaed7df"
checksum = "58a6d0db8759036a783bc7c3f7a07f8cef3bf9470eb1db3bc86e8bcd1c5d0fe8"
dependencies = [
"compression-core",
"flate2",
@ -2242,7 +2242,7 @@ version = "0.11.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cc00ea907cab49550b7da656f80ebb97be1b997d931fbcd28d39734e17ce592"
dependencies = [
"bitflags 2.13.1",
"bitflags 2.13.2",
"inotify-sys",
"libc",
]
@ -2299,9 +2299,9 @@ checksum = "2f0fb0570afe1fed943c5c3d4102d5358592d8625fda6a0007fdbe65a92fba96"
[[package]]
name = "ipnet"
version = "2.12.1"
version = "2.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78"
checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0"
[[package]]
name = "is_terminal_polyfill"
@ -2403,9 +2403,9 @@ dependencies = [
[[package]]
name = "js-sys"
version = "0.3.104"
version = "0.3.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a"
checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e"
dependencies = [
"cfg-if",
"futures-util",
@ -2428,7 +2428,7 @@ version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07293a4e297ac234359b510362495713f75ea345d5307140414f20c69ffeb087"
dependencies = [
"bitflags 2.13.1",
"bitflags 2.13.2",
"libc",
]
@ -2665,7 +2665,7 @@ version = "0.31.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d"
dependencies = [
"bitflags 2.13.1",
"bitflags 2.13.2",
"cfg-if",
"cfg_aliases",
"libc",
@ -2697,7 +2697,7 @@ version = "8.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3"
dependencies = [
"bitflags 2.13.1",
"bitflags 2.13.2",
"fsevent-sys",
"inotify",
"kqueue",
@ -2715,7 +2715,7 @@ version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a"
dependencies = [
"bitflags 2.13.1",
"bitflags 2.13.2",
]
[[package]]
@ -2806,7 +2806,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
dependencies = [
"bitflags 2.13.1",
"bitflags 2.13.2",
]
[[package]]
@ -3110,7 +3110,7 @@ version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61"
dependencies = [
"bitflags 2.13.1",
"bitflags 2.13.2",
"crc32fast",
"fdeflate",
"flate2",
@ -3335,7 +3335,7 @@ dependencies = [
"async-trait",
"axum",
"base64 0.23.1",
"bitflags 2.13.1",
"bitflags 2.13.2",
"bytes",
"colored",
"console-subscriber",
@ -3606,7 +3606,7 @@ dependencies = [
"aes 0.9.3",
"async-compression",
"base64 0.23.1",
"bitflags 2.13.1",
"bitflags 2.13.2",
"bytes",
"cesu8",
"cfb8",
@ -3650,7 +3650,7 @@ name = "pumpkin-world"
version = "0.1.0+26.2-26.45"
dependencies = [
"arc-swap",
"bitflags 2.13.1",
"bitflags 2.13.2",
"bytes",
"criterion",
"crossbeam",
@ -3789,7 +3789,7 @@ version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [
"bitflags 2.13.1",
"bitflags 2.13.2",
]
[[package]]
@ -3858,11 +3858,11 @@ dependencies = [
[[package]]
name = "reqwest"
version = "0.13.4"
version = "0.13.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3"
checksum = "16a1cfa75cc186dd73d5818e510e042e40927bccc9c236b061cea97e1eb08029"
dependencies = [
"base64 0.22.1",
"base64 0.23.1",
"bytes",
"futures-channel",
"futures-core",
@ -4299,7 +4299,7 @@ version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
dependencies = [
"bitflags 2.13.1",
"bitflags 2.13.2",
"errno",
"libc",
"linux-raw-sys",
@ -4318,9 +4318,9 @@ dependencies = [
[[package]]
name = "rustls"
version = "0.23.43"
version = "0.23.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06"
checksum = "6725596c3f2c3a0aef021139e145d4eafe314a6623e4680ca83852b2c67ab2ba"
dependencies = [
"log",
"once_cell",
@ -4402,7 +4402,7 @@ version = "18.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53f6a737db68eb1a8ccff86b584b2fc13eca6a7bb6f78ebc7c529547e3ab9684"
dependencies = [
"bitflags 2.13.1",
"bitflags 2.13.2",
"cfg-if",
"clipboard-win",
"libc",
@ -4485,7 +4485,7 @@ version = "3.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
dependencies = [
"bitflags 2.13.1",
"bitflags 2.13.2",
"core-foundation",
"core-foundation-sys",
"libc",
@ -5173,7 +5173,7 @@ version = "0.6.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840"
dependencies = [
"bitflags 2.13.1",
"bitflags 2.13.2",
"bytes",
"futures-util",
"http",
@ -5396,9 +5396,9 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "wasm-bindgen"
version = "0.2.127"
version = "0.2.128"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70"
checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf"
dependencies = [
"cfg-if",
"once_cell",
@ -5409,9 +5409,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.77"
version = "0.4.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b7777d5cc23d0e91404e53ce2d5e8ec7acae3026b16233dba62cd3246457950"
checksum = "6ef4c5d3d2cdf5c54f4231181768f5510842e350db025faf1f7163b1030ed928"
dependencies = [
"js-sys",
"wasm-bindgen",
@ -5419,9 +5419,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.127"
version = "0.2.128"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1"
checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@ -5429,22 +5429,22 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.127"
version = "0.2.128"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284"
checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a"
dependencies = [
"bumpalo",
"proc-macro2",
"quote",
"syn 2.0.119",
"syn 3.0.5",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.127"
version = "0.2.128"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf"
checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e"
dependencies = [
"unicode-ident",
]
@ -5477,7 +5477,7 @@ version = "0.258.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9a61719f93a87b16d325921e251800c4833f8fab50fa21c7de73aed50086313"
dependencies = [
"bitflags 2.13.1",
"bitflags 2.13.2",
"hashbrown 0.17.1",
"indexmap",
"semver",
@ -5502,7 +5502,7 @@ source = "git+https://github.com/bytecodealliance/wasmtime?rev=f6b3140dbb1d9b436
dependencies = [
"addr2line",
"async-trait",
"bitflags 2.13.1",
"bitflags 2.13.2",
"bumpalo",
"bytes",
"cc",
@ -5699,7 +5699,7 @@ version = "49.0.0-dev"
source = "git+https://github.com/bytecodealliance/wasmtime?rev=f6b3140dbb1d9b436ef523d14740292c956b0364#f6b3140dbb1d9b436ef523d14740292c956b0364"
dependencies = [
"anyhow",
"bitflags 2.13.1",
"bitflags 2.13.2",
"heck",
"indexmap",
"wit-component",
@ -5712,7 +5712,7 @@ version = "49.0.0-dev"
source = "git+https://github.com/bytecodealliance/wasmtime?rev=f6b3140dbb1d9b436ef523d14740292c956b0364#f6b3140dbb1d9b436ef523d14740292c956b0364"
dependencies = [
"async-trait",
"bitflags 2.13.1",
"bitflags 2.13.2",
"bytes",
"cap-primitives",
"futures",
@ -5767,9 +5767,9 @@ dependencies = [
[[package]]
name = "web-sys"
version = "0.3.104"
version = "0.3.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c435338968042f4f59a557f690a253676d47ce13ceb55d70100e7facf6620a30"
checksum = "9fbddc4a036f00ec4f18c83445bd3115cb306a91da554919a099d9222fe4a7f8"
dependencies = [
"js-sys",
"wasm-bindgen",
@ -6129,7 +6129,7 @@ version = "0.36.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f3fd376f71958b862e7afb20cfe5a22830e1963462f3a17f49d82a6c1d1f42d"
dependencies = [
"bitflags 2.13.1",
"bitflags 2.13.2",
"windows-sys 0.59.0",
]
@ -6191,7 +6191,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "481b5c47b2ecce0389b5e08a05557d6a190c9cd761773b8880a8017ee04dc7ef"
dependencies = [
"anyhow",
"bitflags 2.13.1",
"bitflags 2.13.2",
"indexmap",
"log",
"serde",

View File

@ -149,9 +149,9 @@ serde_json = { version = "1.0", default-features = false, features = ["std"] }
tempfile = { version = "3.27.0", default-features = false }
proc-macro-error2 = { version = "2", default-features = false }
aes = { version = "0.9", default-features = false }
async-compression = { version = "0.4.44", default-features = false }
async-compression = { version = "0.4.46", default-features = false }
base64 = { version = "0.23.1", default-features = false, features = ["std"] }
bitflags = { version = "2.13.1", default-features = false, features = ["std"] }
bitflags = { version = "2.13.2", default-features = false, features = ["std"] }
cesu8 = { version = "1.1", default-features = false }
cfb8 = { version = "0.9", default-features = false }
colored = { version = "3.1", default-features = false }
@ -215,7 +215,7 @@ time = { version = "0.3", default-features = false, features = ["std"] }
arc-swap = { version = "1.9", default-features = false }
tokio-util = { version = "0.7.19", default-features = false }
toml = { version = "1.1", default-features = false, features = ["parse", "display", "serde"] }
reqwest = { version = "0.13.4", default-features = false, features = ["rustls-no-provider", "json", "http2"] }
reqwest = { version = "0.13.5", default-features = false, features = ["rustls-no-provider", "json", "http2"] }
notify = { version = "8.2.0", default-features = false, features = ["macos_fsevent"] }
sysinfo = { version = "0.39", default-features = false, features = ["system"] }

View File

@ -6,6 +6,18 @@ use serde_json::Value;
pub enum TestType {
#[serde(rename = "minecraft:block_based")]
BlockBased,
#[serde(rename = "minecraft:function")]
Function,
}
impl TestType {
#[must_use]
pub const fn serialized_name(self) -> &'static str {
match self {
Self::BlockBased => "minecraft:block_based",
Self::Function => "minecraft:function",
}
}
}
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)]
@ -71,6 +83,8 @@ pub struct GameTestDefinition {
pub instance_type: TestType,
pub environment: Value,
pub structure: String,
#[serde(default)]
pub function: Option<String>,
pub max_ticks: i32,
#[serde(default)]
pub setup_ticks: i32,
@ -93,7 +107,13 @@ pub struct GameTestDefinition {
impl GameTestDefinition {
#[must_use]
pub fn is_valid(&self) -> bool {
self.max_ticks > 0
let function_valid = match self.instance_type {
TestType::BlockBased => true,
TestType::Function => self.function.as_ref().is_some_and(|f| !f.is_empty()),
};
function_valid
&& self.max_ticks > 0
&& self.setup_ticks >= 0
&& self.max_attempts > 0
&& self.required_successes > 0

View File

@ -1,17 +1,20 @@
use pumpkin_data::packet::clientbound::config::UPDATE_ENABLED_FEATURES;
use pumpkin_macros::java_packet;
use pumpkin_util::{resource_location::ResourceLocation, version::JavaMinecraftVersion};
use pumpkin_util::version::JavaMinecraftVersion;
use crate::{ClientPacket, ser::NetworkWriteExt};
#[java_packet(UPDATE_ENABLED_FEATURES)]
pub struct CFeatureFlags<'a> {
pub features: &'a [ResourceLocation],
pub features: &'a [&'a str],
}
pub type CUpdateEnabledFeatures<'a> = CFeatureFlags<'a>;
pub type ClientboundUpdateEnabledFeaturesPacket<'a> = CFeatureFlags<'a>;
impl<'a> CFeatureFlags<'a> {
#[must_use]
pub const fn new(features: &'a [ResourceLocation]) -> Self {
pub const fn new(features: &'a [&'a str]) -> Self {
Self { features }
}
}

View File

@ -20,6 +20,13 @@ use self::test_loader::{
TestInstance, TestInstanceRegistry, load_test_instances_from_dir, to_registry_entry,
};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct KnownPackData {
pub namespace: String,
pub id: String,
pub version: String,
}
#[derive(Clone, Debug)]
pub struct LoadedDatapack {
pub id: String,
@ -29,6 +36,7 @@ pub struct LoadedDatapack {
pub root_path: PathBuf,
pub recipe_count: usize,
pub function_count: usize,
pub known_packs: Vec<KnownPackData>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
@ -121,7 +129,7 @@ impl DatapackManager {
continue;
}
let (description, pack_format) = read_pack_mcmeta(&pack_path);
let (description, pack_format, known_packs) = read_pack_mcmeta(&pack_path);
let (pack_recipe_count, pack_function_count, pack_test_instance_count) =
load_pack_contents(
@ -144,6 +152,7 @@ impl DatapackManager {
root_path: pack_path,
recipe_count: pack_recipe_count,
function_count: pack_function_count,
known_packs,
});
}
}
@ -226,6 +235,119 @@ impl DatapackManager {
entries
}
/// Returns the list of known packs for client configuration / pack synchronization,
/// matching vanilla's `server.getResourceManager().listPacks().flatMap(p -> p.location().knownPackInfo().stream())`.
pub fn get_known_packs<'a>(
&self,
server: &Server,
server_version: &'a str,
loaded_packs: &'a [LoadedDatapack],
) -> Vec<pumpkin_protocol::KnownPack<'a>> {
use pumpkin_protocol::KnownPack;
let mut known_packs = Vec::new();
// 1. Primary vanilla core pack
known_packs.push(KnownPack {
namespace: "minecraft",
id: "core",
version: server_version,
});
// 2. Built-in feature packs that are enabled
let enabled_packs = Self::get_enabled_packs(server);
for pack_name in &enabled_packs {
let id: &'static str = match pack_name.as_str() {
"trade_rebalance" => "trade_rebalance",
"minecart_improvements" => "minecart_improvements",
"redstone_experiments" => "redstone_experiments",
"bundle" => "bundle",
_ => continue,
};
let pack = KnownPack {
namespace: "minecraft",
id,
version: server_version,
};
if !known_packs
.iter()
.any(|p| p.namespace == pack.namespace && p.id == pack.id)
{
known_packs.push(pack);
}
}
// 3. Loaded packs with known_pack info from pack.mcmeta
for pack in loaded_packs {
for kp in &pack.known_packs {
let p = KnownPack {
namespace: &kp.namespace,
id: &kp.id,
version: &kp.version,
};
if !known_packs.iter().any(|existing| {
existing.namespace == p.namespace
&& existing.id == p.id
&& existing.version == p.version
}) {
known_packs.push(p);
}
}
}
known_packs
}
/// Returns the enabled world feature flags (e.g. `minecraft:vanilla`, `minecraft:trade_rebalance`,
/// `minecraft:minecart_improvements`, `minecraft:redstone_experiments`, `minecraft:bundle`).
#[must_use]
pub fn get_enabled_features(&self, server: &Server) -> Vec<&'static str> {
let enabled_packs = Self::get_enabled_packs(server);
Self::resolve_enabled_features(&enabled_packs)
}
/// Resolves enabled feature flag names given a list of enabled pack names.
#[must_use]
pub fn resolve_enabled_features(enabled_packs: &[String]) -> Vec<&'static str> {
let mut features = vec!["minecraft:vanilla"];
for pack_name in enabled_packs {
let feature: &'static str = match pack_name.as_str() {
"trade_rebalance" | "file/trade_rebalance" => "minecraft:trade_rebalance",
"minecart_improvements" | "file/minecart_improvements" => {
"minecraft:minecart_improvements"
}
"redstone_experiments" | "file/redstone_experiments" => {
"minecraft:redstone_experiments"
}
"bundle" | "file/bundle" => "minecraft:bundle",
_ => {
if let Some(stripped) = pack_name.strip_prefix("file/") {
match stripped {
"trade_rebalance" => "minecraft:trade_rebalance",
"minecart_improvements" => "minecraft:minecart_improvements",
"redstone_experiments" => "minecraft:redstone_experiments",
"bundle" => "minecraft:bundle",
_ => continue,
}
} else {
continue;
}
}
};
if !features.contains(&feature) {
features.push(feature);
}
}
features
}
#[must_use]
pub fn is_feature_enabled(&self, server: &Server, feature: &str) -> bool {
self.get_enabled_features(server).contains(&feature)
}
/// Loads a Java Edition structure NBT from the currently enabled datapacks.
///
/// Structure identifiers are resource locations such as
@ -520,7 +642,7 @@ impl DatapackManager {
.get_world_path()
.join("datapacks")
.join(stripped);
let (desc, format) = read_pack_mcmeta(&pack_path);
let (desc, format, _) = read_pack_mcmeta(&pack_path);
(resolved_name.clone(), stripped.to_string(), desc, format)
} else {
(
@ -821,7 +943,7 @@ fn load_pack_contents(
)
}
fn read_pack_mcmeta(pack_path: &Path) -> (String, u32) {
fn read_pack_mcmeta(pack_path: &Path) -> (String, u32, Vec<KnownPackData>) {
let mcmeta_path = pack_path.join("pack.mcmeta");
if let Ok(content) = fs::read_to_string(mcmeta_path)
&& let Ok(val) = serde_json::from_str::<serde_json::Value>(&content)
@ -838,9 +960,49 @@ fn read_pack_mcmeta(pack_path: &Path) -> (String, u32) {
.and_then(|p| p.get("pack_format"))
.and_then(serde_json::Value::as_u64)
.unwrap_or(61) as u32;
return (description, pack_format);
let mut known_packs = Vec::new();
let parse_item = |item: &serde_json::Value| -> Option<KnownPackData> {
let ns = item.get("namespace").and_then(serde_json::Value::as_str)?;
let id = item.get("id").and_then(serde_json::Value::as_str)?;
let ver = item.get("version").and_then(serde_json::Value::as_str)?;
Some(KnownPackData {
namespace: ns.to_string(),
id: id.to_string(),
version: ver.to_string(),
})
};
if let Some(packs_array) = val.get("known_packs").and_then(serde_json::Value::as_array) {
for item in packs_array {
if let Some(kp) = parse_item(item) {
known_packs.push(kp);
}
(String::new(), 61)
}
} else if let Some(item) = val.get("known_pack") {
if let Some(kp) = parse_item(item) {
known_packs.push(kp);
}
} else if let Some(pack_obj) = pack {
if let Some(packs_array) = pack_obj
.get("known_packs")
.and_then(serde_json::Value::as_array)
{
for item in packs_array {
if let Some(kp) = parse_item(item) {
known_packs.push(kp);
}
}
} else if let Some(item) = pack_obj.get("known_pack")
&& let Some(kp) = parse_item(item)
{
known_packs.push(kp);
}
}
return (description, pack_format, known_packs);
}
(String::new(), 61, Vec::new())
}
fn load_recipes_from_dir(
@ -946,4 +1108,87 @@ mod tests {
"Unknown function tag: #test:missing"
);
}
#[test]
fn read_pack_mcmeta_parses_known_packs() {
let temp_dir = tempfile::tempdir().expect("tempdir");
let pack_path = temp_dir.path();
// 1. Test root known_packs array
let mcmeta_content = r#"{
"pack": {
"description": "Test Pack",
"pack_format": 61
},
"known_packs": [
{
"namespace": "example",
"id": "content",
"version": "1.0.0"
}
]
}"#;
std::fs::write(pack_path.join("pack.mcmeta"), mcmeta_content).unwrap();
let (desc, format, known_packs) = super::read_pack_mcmeta(pack_path);
assert_eq!(desc, "Test Pack");
assert_eq!(format, 61);
assert_eq!(known_packs.len(), 1);
assert_eq!(known_packs[0].namespace, "example");
assert_eq!(known_packs[0].id, "content");
assert_eq!(known_packs[0].version, "1.0.0");
// 2. Test nested pack.known_packs
let mcmeta_content_nested = r#"{
"pack": {
"description": "Nested Pack",
"pack_format": 61,
"known_packs": [
{
"namespace": "nested",
"id": "pack",
"version": "2.0.0"
}
]
}
}"#;
std::fs::write(pack_path.join("pack.mcmeta"), mcmeta_content_nested).unwrap();
let (_, _, known_packs) = super::read_pack_mcmeta(pack_path);
assert_eq!(known_packs.len(), 1);
assert_eq!(known_packs[0].namespace, "nested");
assert_eq!(known_packs[0].id, "pack");
assert_eq!(known_packs[0].version, "2.0.0");
}
#[test]
fn resolve_enabled_features_works() {
// Default only vanilla
let default_features = DatapackManager::resolve_enabled_features(&[]);
assert_eq!(default_features, ["minecraft:vanilla"]);
// With minecart improvements
let minecart_features =
DatapackManager::resolve_enabled_features(&["file/minecart_improvements".to_string()]);
assert_eq!(
minecart_features,
["minecraft:vanilla", "minecraft:minecart_improvements"]
);
// Multiple experimental features
let multi_features = DatapackManager::resolve_enabled_features(&[
"trade_rebalance".to_string(),
"redstone_experiments".to_string(),
"file/bundle".to_string(),
]);
assert_eq!(
multi_features,
[
"minecraft:vanilla",
"minecraft:trade_rebalance",
"minecraft:redstone_experiments",
"minecraft:bundle"
]
);
}
}

View File

@ -96,14 +96,11 @@ fn load_test_instances_recursive(
#[must_use]
pub fn to_registry_entry(entry_id: String, instance: &TestInstance) -> RegistryEntryData {
let mut nbt = NbtCompound::new();
nbt.put_string(
"type",
match instance.instance_type {
TestType::BlockBased => "minecraft:block_based",
}
.to_string(),
);
nbt.put_string("type", instance.instance_type.serialized_name().to_string());
nbt.put("environment", json_value_to_nbt(&instance.environment));
if let Some(function) = &instance.function {
nbt.put_string("function", function.clone());
}
nbt.put_string("structure", instance.structure.clone());
nbt.put_int("max_ticks", instance.max_ticks);
nbt.put_int("setup_ticks", instance.setup_ticks);
@ -203,4 +200,66 @@ mod tests {
assert!(!instance.sky_access);
assert_eq!(instance.padding, 0);
}
#[test]
fn loads_function_test_instance_with_vanilla_defaults() {
let dir = tempfile::tempdir().expect("temp dir");
fs::write(
dir.path().join("always_pass.json"),
r#"{
"type": "minecraft:function",
"environment": "minecraft:default",
"function": "minecraft:always_pass",
"max_ticks": 1,
"required": false,
"setup_ticks": 1,
"structure": "minecraft:empty"
}"#,
)
.expect("write test instance");
let mut registry = TestInstanceRegistry::new();
let loaded = load_test_instances_from_dir("minecraft", dir.path(), &mut registry);
assert_eq!(loaded, 1);
let instance = registry
.get("minecraft:always_pass")
.expect("test instance should be registered");
assert_eq!(instance.instance_type, TestType::Function);
assert_eq!(
instance.environment,
Value::String("minecraft:default".into())
);
assert_eq!(instance.function.as_deref(), Some("minecraft:always_pass"));
assert_eq!(instance.structure, "minecraft:empty");
assert_eq!(instance.max_ticks, 1);
assert_eq!(instance.setup_ticks, 1);
assert!(!instance.required);
assert_eq!(instance.rotation, GameTestRotation::None);
assert!(!instance.manual_only);
assert_eq!(instance.max_attempts, 1);
assert_eq!(instance.required_successes, 1);
assert!(!instance.sky_access);
assert_eq!(instance.padding, 0);
}
#[test]
fn loads_embedded_test_instances_including_always_pass() {
let mut registry = TestInstanceRegistry::new();
let count = load_embedded_test_instances(&mut registry);
assert!(count > 0);
let always_pass = registry
.get("minecraft:always_pass")
.expect("minecraft:always_pass should be successfully parsed and registered");
assert_eq!(always_pass.instance_type, TestType::Function);
assert_eq!(
always_pass.function.as_deref(),
Some("minecraft:always_pass")
);
assert_eq!(always_pass.structure, "minecraft:empty");
}
}

View File

@ -8,8 +8,10 @@ impl JavaClient {
let version = self.version.load();
if version.supports_configuration_state() {
self.send_packet(&CFeatureFlags::new(&["minecraft:vanilla".to_string()]))
.await;
if version < JavaMinecraftVersion::V_1_20_5 {
let features = server.get_enabled_features();
self.send_packet(&CFeatureFlags::new(&features)).await;
}
let test_instance_entries =
server.datapack_manager.get_test_instance_registry_entries();

View File

@ -11,7 +11,7 @@ use crate::{
use core::str;
use pumpkin_data::registry::Registry;
use pumpkin_protocol::{
ConnectionState, KnownPack,
ConnectionState,
java::{
client::config::{CFeatureFlags, CFinishConfig, CKnownPacks, CRegistryData, CUpdateTags},
server::config::{
@ -20,7 +20,7 @@ use pumpkin_protocol::{
},
},
};
use pumpkin_util::{Hand, text::TextComponent};
use pumpkin_util::{Hand, text::TextComponent, version::JavaMinecraftVersion};
use tracing::{debug, trace, warn};
const BRAND_CHANNEL_PREFIX: &str = "minecraft:brand";

View File

@ -69,16 +69,15 @@ impl JavaClient {
self.id
);
}
self.send_known_packs().await;
self.send_known_packs(server).await;
}
pub async fn send_known_packs(&self) {
pub async fn send_known_packs(&self, server: &Server) {
let features = server.get_enabled_features();
self.send_packet(&CFeatureFlags::new(&features)).await;
let version_str = self.version.load().to_string();
self.send_packet(&CKnownPacks::new(&[KnownPack {
namespace: "minecraft",
id: "core",
version: &version_str,
}]))
.await;
let loaded_packs = server.datapack_manager.get_loaded_packs();
let known_packs = server.get_known_packs(&version_str, &loaded_packs);
self.send_packet(&CKnownPacks::new(&known_packs)).await;
}
}

View File

@ -2,11 +2,13 @@
use super::*;
impl PendingConnection {
pub async fn handle_known_packs(&mut self) {
pub async fn handle_known_packs(&mut self, server: &Server) {
let version = self.version.load();
if version.supports_configuration_state() {
self.send_packet_now(&CFeatureFlags::new(&["minecraft:vanilla".to_string()]))
.await;
if version < JavaMinecraftVersion::V_1_20_5 {
let features = server.get_enabled_features();
self.send_packet_now(&CFeatureFlags::new(&features)).await;
}
let registry = pumpkin_data::registry::Registry::get_synced(version);
for reg in &registry {
self.send_packet_now(&CRegistryData::new(&reg.registry_id, &reg.registry_entries))

View File

@ -97,21 +97,20 @@ impl PendingConnection {
self.send_packet_now(&resource_pack).await;
} else if self.version.load() >= JavaMinecraftVersion::V_1_20_5 {
self.send_known_packs().await;
self.send_known_packs(server).await;
} else {
self.handle_known_packs().await;
self.handle_known_packs(server).await;
}
debug!("login acknowledged");
None
}
pub async fn send_known_packs(&mut self) {
pub async fn send_known_packs(&mut self, server: &Server) {
let features = server.get_enabled_features();
self.send_packet_now(&CFeatureFlags::new(&features)).await;
let version_str = self.version.load().to_string();
self.send_packet_now(&CKnownPacks::new(&[KnownPack {
namespace: "minecraft",
id: "core",
version: &version_str,
}]))
.await;
let loaded_packs = server.datapack_manager.get_loaded_packs();
let known_packs = server.get_known_packs(&version_str, &loaded_packs);
self.send_packet_now(&CKnownPacks::new(&known_packs)).await;
}
}

View File

@ -1,7 +1,7 @@
use arc_swap::ArcSwap;
use pumpkin_data::translation;
use pumpkin_protocol::{
ConnectionState, KnownPack, Label, Link, LinkType,
ConnectionState, Label, Link, LinkType,
java::client::{
config::{
CConfigAddResourcePack, CConfigServerLinks, CFeatureFlags, CFinishConfig, CKnownPacks,

View File

@ -420,7 +420,7 @@ impl PendingConnection {
}
}
id if id == SKnownPacks::to_id(version) => {
self.handle_known_packs().await;
self.handle_known_packs(server).await;
Ok(None)
}
id if id == SConfigResourcePack::to_id(version) => {
@ -509,19 +509,33 @@ impl PendingConnection {
match packet.response_result() {
ResourcePackResponseResult::Downloaded
| ResourcePackResponseResult::DownloadSuccess
| ResourcePackResponseResult::Accepted
| ResourcePackResponseResult::Discarded
| ResourcePackResponseResult::Unknown(_) => {}
| ResourcePackResponseResult::Unknown(_) => {
if self.version.load() >= JavaMinecraftVersion::V_1_20_5 {
self.send_known_packs(server).await;
} else {
self.handle_known_packs(server).await;
}
}
ResourcePackResponseResult::Accepted => {}
ResourcePackResponseResult::Declined => {
if resource_config.force {
self.kick(TextComponent::text("Required resource pack was declined"))
.await;
} else if self.version.load() >= JavaMinecraftVersion::V_1_20_5 {
self.send_known_packs(server).await;
} else {
self.handle_known_packs(server).await;
}
}
ResourcePackResponseResult::DownloadFail => {
if resource_config.force {
self.kick(TextComponent::text("Failed to download resource pack"))
.await;
} else if self.version.load() >= JavaMinecraftVersion::V_1_20_5 {
self.send_known_packs(server).await;
} else {
self.handle_known_packs(server).await;
}
}
ResourcePackResponseResult::InvalidUrl => {

View File

@ -67,9 +67,25 @@ impl JavaClient {
return;
}
let level_info = player.world().level_info.load();
let world = player.world();
let level_info = world.level_info.load();
let minecart_improvements_enabled = world.server.upgrade().map_or_else(
|| {
level_info
.data_packs
.enabled
.iter()
.any(|p| p == "minecart_improvements" || p == "file/minecart_improvements")
},
|s| s.is_feature_enabled("minecraft:minecart_improvements"),
);
let rules: Vec<(String, String)> = GameRule::all()
.iter()
.filter(|rule| match rule {
GameRule::MaxMinecartSpeed => minecart_improvements_enabled,
_ => true,
})
.map(|rule| {
(
rule.to_string(),

View File

@ -13,6 +13,20 @@ impl JavaClient {
return;
}
let world = player.world();
let minecart_improvements_enabled = world.server.upgrade().map_or_else(
|| {
world
.level_info
.load()
.data_packs
.enabled
.iter()
.any(|p| p == "minecart_improvements" || p == "file/minecart_improvements")
},
|s| s.is_feature_enabled("minecraft:minecart_improvements"),
);
for entry in &packet.entries {
let key = entry
.game_rule_key
@ -23,6 +37,14 @@ impl JavaClient {
continue;
};
if *rule == GameRule::MaxMinecartSpeed && !minecart_improvements_enabled {
warn!(
"Player {} tried to set game rule {} which is not enabled by feature flags",
player.gameprofile.name, entry.game_rule_key
);
continue;
}
let level_info = player.world().level_info.load();
let current_val = level_info.game_rules.get(rule);
match current_val {

View File

@ -564,6 +564,26 @@ impl Server {
}
}
#[must_use]
pub fn get_known_packs<'a>(
&self,
server_version: &'a str,
loaded_packs: &'a [crate::data::datapack::LoadedDatapack],
) -> Vec<pumpkin_protocol::KnownPack<'a>> {
self.datapack_manager
.get_known_packs(self, server_version, loaded_packs)
}
#[must_use]
pub fn get_enabled_features(&self) -> Vec<&'static str> {
self.datapack_manager.get_enabled_features(self)
}
#[must_use]
pub fn is_feature_enabled(&self, feature: &str) -> bool {
self.datapack_manager.is_feature_enabled(self, feature)
}
pub async fn save_all(&self) -> Result<(), String> {
if let Err(err) = self.save_world_info() {
error!("Failed to save world info: {err}");

View File

@ -7,6 +7,7 @@ use pumpkin_data::{BlockState, BlockStateId};
use pumpkin_gametest::{
BlockBasedTest, GameTestError, GameTestManager, GameTestReporter, GameTestResult,
GameTestRotation, GameTestRunner, GameTestSession, GameTestStructureTemplate, GameTestWorld,
TestType,
};
pub use pumpkin_gametest::{GameTestBatchReport, GameTestRetryOptions};
use pumpkin_nbt::NbtCompound;
@ -149,6 +150,13 @@ async fn prepare_test_run(
GameTestError::World(format!("Unknown test instance '{}'", request.test_id))
})?;
if test_instance.instance_type != TestType::BlockBased {
return Err(GameTestError::World(format!(
"Test instance '{}' has unsupported type '{:?}' (only block-based tests can be executed currently)",
request.test_id, test_instance.instance_type
)));
}
let structure = server
.datapack_manager
.load_structure(&test_instance.structure)