Load configured spawner passengers with default entity fields

Normalize entity type identifiers and place every configured passenger before loading a spawner entity stack. Cover namespace resolution and actual passenger selection and vehicle relations.

Co-Authored-By: Codex <[email protected]>
This commit is contained in:
Florian ERNSTandCodex committed 2026-11-02 14:51:31 +01:00
1 parent 4f4f1db13c
commit 2d73371393
3 files changed
+63 -2

No files matched your search

+26 -1
View File
@@ -27,7 +27,7 @@ fn string(s: &str) -> Tag {
}
/// Entity type ID (`id`).
pub fn kind(tag: &Tag) -> Option<String> {
text(tag.get("id"))
horizon_client_core::identifier::normalize_id(&text(tag.get("id"))?)
}
pub fn position(tag: &Tag) -> Option<Vec3> {
read_doubles::<3>(tag.get("Pos")).map(|p| Vec3::new(p[0], p[1], p[2]))
@@ -3311,6 +3311,31 @@ mod tests {
use super::*;
use crate::items::ItemStack;
#[test]
fn mob_loading_accepts_default_namespace_and_preserves_custom_namespaces() {
let cow = crate::mobs::sensor_scan_tests::kind();
let types = [
("minecraft:cow".into(), cow.clone()),
("test:cow".into(), cow),
]
.into();
let pos = || {
Tag::List(
[4.5_f64, 220.0, 6.5]
.map(|v| Tag::Double(v.to_bits()))
.to_vec(),
)
};
for id in ["cow", ":cow", "minecraft:cow", "test:cow"] {
let tag = Tag::compound([("id", string(id)), ("Pos", pos()), ("NoAI", Tag::Byte(1))]);
let mob = mob_from_nbt(&tag, &types, 7, None).expect(id);
assert_eq!(mob.position, Vec3::new(4.5, 220.0, 6.5));
assert!(mob.no_ai);
}
let unknown = Tag::compound([("id", string("other:cow")), ("Pos", pos())]);
assert!(mob_from_nbt(&unknown, &types, 8, None).is_none());
}
/// A saved rocket and trident come back with their own Life, LifeTime,
/// flags and position (the keys vanilla's addAdditionalSaveData writes).
#[test]
+4 -1
View File
@@ -222,7 +222,10 @@ impl Game<'_> {
if r.trial {
tag.insert("PersistenceRequired".into(), Tag::Byte(1));
}
let tag = Tag::Compound(tag);
let mut tag = Tag::Compound(tag);
// EntityType.loadEntityRecursive applies the spawner's placement
// callback to every passenger as well as the root.
super::commands::place_summoned(&mut tag, r.position);
let mob = horizon_client::entity_nbt::mob_from_nbt(
&tag,
self.res.mob_types(),
+33
View File
@@ -1686,6 +1686,38 @@ def summoned_potions():
return ops
def trial_spawner_passengers():
"""Configured passengers must exist as entities with a vehicle (#7254)."""
ops = [*setup('/fill 1 219 1 13 219 8 stone',
'/fill 1 220 1 13 224 8 air', '/gamerule spawn_mobs true')]
# Exercise namespace and position independently, plus the explicit control.
for i, (kind, pos) in enumerate([
('chicken', ''), ('minecraft:chicken', ''),
('chicken', ',Pos:[4.5d,220.0d,6.5d]'),
('minecraft:chicken', ',Pos:[4.5d,220.0d,6.5d]'),
]):
parent, rider = f'trial_parent_{i}', f'trial_rider_{i}'
ops += setup(
'/setblock 4 220 4 trial_spawner[trial_spawner_state=active]'
'{normal_config:{spawn_range:3,total_mobs:1.0f,simultaneous_mobs:1.0f,'
'total_mobs_added_per_player:0.0f,simultaneous_mobs_added_per_player:0.0f,'
'ticks_between_spawn:1000},spawn_data:{entity:{id:"zombie",'
f'Tags:["{parent}"],Pos:[4.5d,220.0d,6.5d],NoAI:1b,'
f'Passengers:[{{id:"{kind}",NoAI:1b,Tags:["{rider}"]{pos}}}]'
'}},next_mob_spawns_at:0L}',
'/data modify block 4 220 4 registered_players append from entity @s UUID')
ops += [
{'op': 'tick', 'n': 5},
cmd(f'/execute if entity @e[tag={parent}]'),
cmd(f'/execute if entity @e[tag={rider}]'),
cmd(f'/execute as @e[tag={rider}] on vehicle if entity @s[tag={parent}]'),
cmd(f'/data get entity @e[tag={parent},limit=1] Passengers[0].Tags'),
*setup(f'/kill @e[tag={parent}]', f'/kill @e[tag={rider}]',
'/setblock 4 220 4 air'),
]
return ops + setup('/fill 1 219 1 13 224 8 air', '/gamerule spawn_mobs false')
def groups():
return [
('setup', group_setup()),
@@ -1698,6 +1730,7 @@ def groups():
('wither spawn', wither_spawn()),
('riding teleport', riding_teleport()),
('mob riding', mob_riding()),
('trial spawner passengers', trial_spawner_passengers()),
('camel riders', camel_riders()),
('scores, teams and bossbars', scores()),
('execute', execute()),