Ender Dragon spawn egg: vanilla random order and the dragon's own random

EntityType.create draws the wrapped spawn yaw and Mob.finalizeSpawn the
follow-range triangle and the left-handed roll from the level random;
the dragon seeds its own random (Entity.random), not from the level one.
test_spawn_egg_fluid_parity gains the dragon egg used on a block.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Horizon agentandClaude Opus 5.5 committed 2026-11-05 15:34:34 +01:00
1 parent aa19826c39
commit d7e7b92735
2 files changed
+20 -11

No files matched your search

+14 -7
View File
@@ -4177,18 +4177,25 @@ impl Game<'_> {
true
}
/// Create an Ender Dragon for a scripted spawn such as a spawn egg or a
/// dispenser. Unlike the End fight's dragon, this one is not assigned to
/// the fight UUID and can therefore be summoned in any dimension.
/// `EntityType.create` for an Ender Dragon from a spawn egg, by hand or from a dispenser
/// (`SpawnEggItem.spawnMob`, `SpawnEggItemBehavior`). Unlike the End fight's dragon, this
/// one is not the fight's (`EnderDragon.aiStep` adopts it only when its UUID is the fight's),
/// so it can be spawned in any dimension. Horizon keeps one dragon per level, as `/summon`
/// and loading do: with one already there, nothing spawns and the egg stays.
pub(super) fn spawn_dragon_spawned(&mut self, at: [f64; 3]) -> Option<u32> {
// Horizon currently keeps one simulated dragon per level, matching
// the existing `/summon`/save representation.
if self.dragon.is_some() {
return None;
}
let position = Vec3::new(at[0], at[1], at[2]);
let yaw = self.world.random.next_float() * 360.0;
let seed = self.world.random.next_long();
// EntityType.create: `snapTo(.., Mth.wrapDegrees(level.getRandom().nextFloat() * 360))`.
let yaw = horizon_client::camera::wrap_degrees(self.world.random.next_float() * 360.0);
// Mob.finalizeSpawn (EnderDragon keeps it): the follow range's
// `random.triangle(0, 0.11485)`, then `setLeftHanded(nextFloat() < 0.05)`, from the
// level's random. Neither changes how the dragon flies.
let _ = self.world.random.triangle(0.0, 0.114_850_000_000_000_01);
let _ = self.world.random.next_float();
// The dragon's own random (`Entity.random`), not the level's.
let seed = super::dispenser::entity_random().next_long();
let mut dragon = Dragon::new(position, yaw, seed);
dragon.id = self.next_entity + 1;
self.next_entity += 9;
+6 -4
View File
@@ -4,7 +4,8 @@
Fluid cases send UseItem into a deep pool; a UseItemOn control has a mob
under the last look direction. Compare spawn, egg count and used stat. Covers
lava/water sources, flowing lava, a solid obstruction, waterlogged blocks,
creative, adventure (with/without can_place_on) and peaceful monster eggs.
creative, adventure (with/without can_place_on) and peaceful monster eggs, and the
Ender Dragon's egg on a block (Horizon simulates the dragon apart from other mobs).
"""
import json
try:
@@ -24,6 +25,7 @@ CASES = [
('adventure-allowed', 'lava', 'strider', 'adventure',
'[minecraft:can_place_on={blocks:"minecraft:lava"}]', True),
('peaceful', 'lava', 'zombie', 'survival', '', False),
('block-use-dragon', 'stone', 'ender_dragon', 'survival', '', True),
]
SCRIPT = [("setup", ["chat on", "cmd Alice gamerule spawn_mobs false"])]
previous_mode = "creative"
@@ -37,15 +39,15 @@ for name, block, mob, mode, component, spawn in CASES:
'cmd Alice fill -2 74 -2 6 80 6 minecraft:stone',
'cmd Alice fill -2 81 -2 6 85 6 minecraft:air',
f'cmd Alice fill 0 75 0 4 80 4 minecraft:{block}',
f'cmd Alice tp @s 2.5 81 -0.5 0 {0 if name == "block-use" else 50}',
*(['cmd Alice summon minecraft:strider 2.5 81 1.5 {NoAI:1b}'] if name == 'block-use' else []),
f'cmd Alice tp @s 2.5 81 -0.5 0 {0 if name.startswith("block-use") else 50}',
*(['cmd Alice summon minecraft:strider 2.5 81 1.5 {NoAI:1b}'] if name.startswith('block-use') else []),
f'cmd Alice give @s {egg}{component} 8',
'cmd Alice scoreboard objectives remove eggs',
f'cmd Alice scoreboard objectives add eggs minecraft.used:minecraft.{mob}_spawn_egg',
*([f'cmd Alice gamemode {mode}'] if mode != 'creative' else []),
]
previous_mode = mode
SCRIPT += [(name + '-setup', setup), (name + '-use', ['use-on Alice 4 80 -1' if name == 'block-use' else 'use Alice 50']),
SCRIPT += [(name + '-setup', setup), (name + '-use', ['use-on Alice 4 80 -1' if name.startswith('block-use') else 'use Alice 50']),
(name + '-check', [
f'cmd Alice execute {"if" if spawn else "unless"} entity @e[type=minecraft:{mob}]',
f'cmd Alice clear @s {egg} 0',