mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 17:21:49 -10:00
Merge branch 'heads/pt3/2586' into union/pt3-2
This commit is contained in:
commit
285ecb6e89
15 files changed
+261
-87
No files matched your search
@@ -142,8 +142,8 @@ const GENES: [&str; 7] = [
|
||||
"aggressive",
|
||||
];
|
||||
const MEMORIES: [&str; 7] = [
|
||||
"long_jump_cooldown",
|
||||
"ram_cooldown",
|
||||
"long_jump_cooling_down",
|
||||
"ram_cooldown_ticks",
|
||||
"attack_target_cooldown",
|
||||
"hunted_recently",
|
||||
"dig_cooldown",
|
||||
|
||||
@@ -795,7 +795,7 @@ pub fn main() -> horizon_client::Result<()> {
|
||||
chunk,
|
||||
&mut gen_random,
|
||||
&mut rules,
|
||||
&mut spawn,
|
||||
&mut |l, r, _| spawn(l, r).is_some(),
|
||||
&mut |e| match e {
|
||||
GenEvent::Column(x, z) => log.push(format!("col {x} {z}")),
|
||||
GenEvent::Spawn(t, p, yaw) => spawns.push(format!(
|
||||
|
||||
@@ -744,22 +744,31 @@ pub fn mob_to_nbt(m: &Mob, uuid: [i32; 4], raw: Option<&Tag>) -> Tag {
|
||||
if let Some(a) = m.extra.attributes.as_deref() {
|
||||
put_attributes(map, m, a);
|
||||
}
|
||||
// Zombie and Piglin.setBaby, which their load runs even for an adult, make
|
||||
// the speed attribute that AttributeMap.pack then saves.
|
||||
if crate::mobs::doors::is_zombie(species) || species == crate::mobs::Species::Piglin {
|
||||
let speed = crate::mobs::finalize::Attr::MovementSpeed;
|
||||
let base = crate::mobs::finalize::base_value(m, speed);
|
||||
// The attribute their load makes, which AttributeMap.pack then saves:
|
||||
// Zombie and Piglin.setBaby (even for an adult) the speed, Rabbit.setVariant
|
||||
// the attack damage.
|
||||
use crate::mobs::finalize::Attr;
|
||||
let made = if crate::mobs::doors::is_zombie(species) || species == crate::mobs::Species::Piglin
|
||||
{
|
||||
Some(Attr::MovementSpeed)
|
||||
} else if species == crate::mobs::Species::Rabbit {
|
||||
Some(Attr::AttackDamage)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Some(attr) = made {
|
||||
let base = crate::mobs::finalize::base_value(m, attr);
|
||||
let mut list = match map.get(&"attributes".into()) {
|
||||
Some(Tag::List(l)) => l.clone(),
|
||||
_ => Vec::new(),
|
||||
};
|
||||
let speed_id = format!("minecraft:{}", speed.name());
|
||||
let attr_id = format!("minecraft:{}", attr.name());
|
||||
if !list
|
||||
.iter()
|
||||
.any(|t| text(t.get("id")).as_deref() == Some(speed_id.as_str()))
|
||||
.any(|t| text(t.get("id")).as_deref() == Some(attr_id.as_str()))
|
||||
{
|
||||
list.push(Tag::compound([
|
||||
("id", string(&speed_id)),
|
||||
("id", string(&attr_id)),
|
||||
("base", Tag::Double(base.to_bits())),
|
||||
]));
|
||||
map.insert("attributes".into(), Tag::List(list));
|
||||
|
||||
@@ -12715,6 +12715,7 @@ impl Game<'_> {
|
||||
let (uploads, unloaded) =
|
||||
horizon_client::spans::time("streaming", || self.streaming.update(&mut self.world));
|
||||
self.streaming.extra.truncate(areas);
|
||||
self.spawn_fresh_chunks();
|
||||
if let Some(lod) = &mut self.lod {
|
||||
self.lod_observe
|
||||
.extend(uploads.iter().map(|(key, _)| (key.0, key.2)));
|
||||
|
||||
@@ -514,9 +514,9 @@ impl Game<'_> {
|
||||
};
|
||||
spawning::LocalDifficulty::new(base, self.world.day_time, inhabited, moon)
|
||||
}
|
||||
/// Advance every mob one tick, then apply their events.
|
||||
pub(super) fn tick_creatures(&mut self) -> Result<()> {
|
||||
self.attack_ticks = self.attack_ticks.saturating_add(1);
|
||||
/// Generation's animals for the chunks generated since, once lit: chunk
|
||||
/// generation places them whether the level ticks or not.
|
||||
pub(super) fn spawn_fresh_chunks(&mut self) {
|
||||
let fresh: Vec<(i32, i32)> = self
|
||||
.world
|
||||
.chunks
|
||||
@@ -530,6 +530,10 @@ impl Game<'_> {
|
||||
}
|
||||
self.spawn_generation_creatures(key.0, key.1);
|
||||
}
|
||||
}
|
||||
/// Advance every mob one tick, then apply their events.
|
||||
pub(super) fn tick_creatures(&mut self) -> Result<()> {
|
||||
self.attack_ticks = self.attack_ticks.saturating_add(1);
|
||||
self.tick_leads();
|
||||
self.spawn_natural();
|
||||
// ServerChunkCache.tickChunks: the custom spawners tick only with spawn_mobs.
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
//! it reads, the spawning chunks and caps of `ServerChunkCache.tickChunks`,
|
||||
//! and the mobs it adds (finalized with the game's `finalizeSpawn`).
|
||||
use super::{Dim, Game};
|
||||
use horizon_client::vanillagen::VanillaTerrain;
|
||||
use horizon_client::{
|
||||
biome::{BiomeInfo, SpawnEntry},
|
||||
blocks::BlockFacts,
|
||||
@@ -19,9 +20,12 @@ use horizon_client::{
|
||||
};
|
||||
use horizon_core::{
|
||||
geometry::{Aabb, Vec3},
|
||||
random::{LegacyRandom, RandomSource, WorldgenRandom},
|
||||
random::{LegacyRandom, RandomSource, WorldgenRandom, XoroshiroRandom},
|
||||
};
|
||||
use horizon_world::{
|
||||
nbt::{Compound, Tag},
|
||||
registry::BlockStateId,
|
||||
};
|
||||
use horizon_world::registry::BlockStateId;
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Reused buffers for one pass over the spawning chunks: the candidate chunks
|
||||
@@ -157,6 +161,16 @@ impl GameLevel<'_, '_> {
|
||||
let chunk = self.game.world.chunks.get(&(x >> 4, z >> 4))?;
|
||||
Some(self.game.res.biomes().get(chunk.biome(x, y, z)))
|
||||
}
|
||||
/// The block whose noise cell `BiomeManager.getBiome` reads.
|
||||
fn zoomed(&self, x: i32, y: i32, z: i32) -> [i32; 3] {
|
||||
let zoom = self.game.streaming.biome_zoom;
|
||||
let [qx, qy, qz] = zoom.quart_at_block(horizon_core::position::BlockPos::new(x, y, z));
|
||||
[qx << 2, qy << 2, qz << 2]
|
||||
}
|
||||
fn zoomed_biome(&self, x: i32, y: i32, z: i32) -> Option<&BiomeInfo> {
|
||||
let [x, y, z] = self.zoomed(x, y, z);
|
||||
self.biome(x, y, z)
|
||||
}
|
||||
}
|
||||
impl SpawnLevel for GameLevel<'_, '_> {
|
||||
fn state(&self, x: i32, y: i32, z: i32) -> BlockStateId {
|
||||
@@ -171,7 +185,9 @@ impl SpawnLevel for GameLevel<'_, '_> {
|
||||
fn block_tag(&self, tag: &str, block: &str) -> bool {
|
||||
self.w.block_tag(tag, block)
|
||||
}
|
||||
// Level.getBiome: the zoomed biome.
|
||||
fn biome_tag(&self, tag: &str, x: i32, y: i32, z: i32) -> bool {
|
||||
let [x, y, z] = self.zoomed(x, y, z);
|
||||
self.w.biome_tag(tag, x, y, z)
|
||||
}
|
||||
fn sky_light(&self, x: i32, y: i32, z: i32) -> i32 {
|
||||
@@ -237,11 +253,13 @@ impl NaturalLevel for GameLevel<'_, '_> {
|
||||
Category::from_name(&t.category),
|
||||
))
|
||||
}
|
||||
// The `natural_mob_spawns` attribute reads the zoomed biome
|
||||
// (fullResolutionBiomes), the spawn probability the noise cell.
|
||||
fn biome_spawns(&self, x: i32, y: i32, z: i32) -> Option<&HashMap<String, Vec<SpawnEntry>>> {
|
||||
self.biome(x, y, z).map(|b| &b.spawns)
|
||||
self.zoomed_biome(x, y, z).map(|b| &b.spawns)
|
||||
}
|
||||
fn spawn_cost(&self, entity: &str, x: i32, y: i32, z: i32) -> Option<SpawnCost> {
|
||||
self.biome(x, y, z)?.spawn_costs.get(entity).copied()
|
||||
self.zoomed_biome(x, y, z)?.spawn_costs.get(entity).copied()
|
||||
}
|
||||
fn creature_probability(&self, x: i32, y: i32, z: i32) -> f32 {
|
||||
self.biome(x, y, z).map_or(0.1, |b| b.creature_probability)
|
||||
@@ -573,37 +591,68 @@ impl<'a> Game<'a> {
|
||||
}
|
||||
/// `NoiseBasedChunkGenerator.spawnOriginalMobs`: creature packs for a
|
||||
/// freshly generated chunk, from the biome at its corner column's top,
|
||||
/// with the chunk's decoration-seeded random (mobs are not generated in
|
||||
/// with the chunk's decoration-seeded random, finalized with the
|
||||
/// region's random as generation adds them (mobs are not generated in
|
||||
/// the End).
|
||||
pub(super) fn spawn_generation_creatures(&mut self, cx: i32, cz: i32) {
|
||||
if !self.world.chunks.contains_key(&(cx, cz)) || self.dim == Dim::End {
|
||||
return;
|
||||
}
|
||||
let Some(terrain) = self.end.locator() else {
|
||||
return;
|
||||
};
|
||||
let mut region = terrain.region_random(cx, cz);
|
||||
let any = region.as_any();
|
||||
let added = if let Some(r) = any.downcast_mut::<LegacyRandom>() {
|
||||
self.generation_creatures(cx, cz, &terrain, r)
|
||||
} else if let Some(r) = any.downcast_mut::<XoroshiroRandom>() {
|
||||
self.generation_creatures(cx, cz, &terrain, r)
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
for nbt in added {
|
||||
self.load_mob(Tag::Compound(nbt), None);
|
||||
}
|
||||
}
|
||||
fn generation_creatures<Q: RandomSource + 'static>(
|
||||
&self,
|
||||
cx: i32,
|
||||
cz: i32,
|
||||
terrain: &VanillaTerrain,
|
||||
region: &mut Q,
|
||||
) -> Vec<Compound> {
|
||||
let mut random = WorldgenRandom::new(LegacyRandom::new(0));
|
||||
random.set_decoration_seed(self.level.seed, cx * 16, cz * 16);
|
||||
// The region's own random: no creature's spawn predicate draws from it.
|
||||
let mut rules = LegacyRandom::new(self.level.seed ^ (i64::from(cx) << 32 | i64::from(cz)));
|
||||
let mut pending = Vec::new();
|
||||
{
|
||||
let level = self.natural_level();
|
||||
let source = [cx * 16, level.max_y(), cz * 16];
|
||||
let mut spawn = |l: &GameLevel, r: &SpawnRequest| {
|
||||
let spawned = l.game.finalize_spawned(r, &mut pending);
|
||||
if let Some(s) = &spawned {
|
||||
l.pending.borrow_mut().push(s.bounds);
|
||||
let mut added = Vec::new();
|
||||
let mut group = None;
|
||||
let level = self.natural_level();
|
||||
let source = [cx * 16, level.max_y(), cz * 16];
|
||||
let mut spawn = |l: &GameLevel, r: &SpawnRequest, region: &mut Q| {
|
||||
// Each pack starts without group data.
|
||||
if r.group_index == 0 {
|
||||
group = None;
|
||||
}
|
||||
let [x, y, z] = r.position;
|
||||
let (bx, by, bz) = (x.floor() as i32, y.floor() as i32, z.floor() as i32);
|
||||
let biome = l.zoomed_biome(bx, by, bz).map_or("", |b| b.name.as_str());
|
||||
let at = Vec3::new(x, y, z);
|
||||
match terrain.spawn_generated(r.entity, at, r.yaw, biome, region, &mut group) {
|
||||
Some(nbt) => {
|
||||
added.push(nbt);
|
||||
true
|
||||
}
|
||||
spawned
|
||||
};
|
||||
spawn_selection::spawn_mobs_for_chunk_generation(
|
||||
&level,
|
||||
source,
|
||||
(cx, cz),
|
||||
&mut random,
|
||||
&mut rules,
|
||||
&mut spawn,
|
||||
&mut |_| {},
|
||||
);
|
||||
}
|
||||
self.add_spawned(pending, true);
|
||||
None => false,
|
||||
}
|
||||
};
|
||||
spawn_selection::spawn_mobs_for_chunk_generation(
|
||||
&level,
|
||||
source,
|
||||
(cx, cz),
|
||||
&mut random,
|
||||
region,
|
||||
&mut spawn,
|
||||
&mut |_| {},
|
||||
);
|
||||
added
|
||||
}
|
||||
}
|
||||
@@ -1619,7 +1619,7 @@ impl<L: FinalizeLevel + ?Sized, R: RandomSource> Ctx<'_, L, R> {
|
||||
S::Frog => {
|
||||
self.variant(m, |r| &r.frog);
|
||||
let jump = self.uniform(100, 140);
|
||||
set_memory(m, "long_jump_cooldown", i64::from(jump), i64::MAX);
|
||||
set_memory(m, "long_jump_cooling_down", i64::from(jump), i64::MAX);
|
||||
self.ageable(m, g)
|
||||
}
|
||||
S::ZombieNautilus => {
|
||||
@@ -1734,9 +1734,9 @@ impl<L: FinalizeLevel + ?Sized, R: RandomSource> Ctx<'_, L, R> {
|
||||
}
|
||||
S::Goat => {
|
||||
let jump = self.uniform(600, 1200);
|
||||
set_memory(m, "long_jump_cooldown", i64::from(jump), i64::MAX);
|
||||
set_memory(m, "long_jump_cooling_down", i64::from(jump), i64::MAX);
|
||||
let ram = self.uniform(600, 6000);
|
||||
set_memory(m, "ram_cooldown", i64::from(ram), i64::MAX);
|
||||
set_memory(m, "ram_cooldown_ticks", i64::from(ram), i64::MAX);
|
||||
m.extra.screaming = self.random.next_double() < 0.02;
|
||||
age_boundary_reached(m);
|
||||
if !m.is_baby() && self.float() < 0.1 {
|
||||
|
||||
@@ -539,18 +539,21 @@ pub fn spawn_aabb(s: Species, x: f64, y: f64, z: f64, width: f32, height: f32) -
|
||||
Vec3::new(x + half, y + f64::from(scale * height), z + half),
|
||||
)
|
||||
}
|
||||
/// `checkSpawnObstruction` of a freshly created mob with the given box.
|
||||
/// `checkSpawnObstruction` of a freshly created mob with the given box, with
|
||||
/// the mobs already `spawned` in the way; None in a generation region, which
|
||||
/// has no entities (`WorldGenRegion.getEntities`).
|
||||
pub(crate) fn spawn_obstruction<L: NaturalLevel>(
|
||||
s: Species,
|
||||
l: &L,
|
||||
bounds: Aabb,
|
||||
spawned: &[Aabb],
|
||||
spawned: Option<&[Aabb]>,
|
||||
x: i32,
|
||||
y: i32,
|
||||
z: i32,
|
||||
) -> bool {
|
||||
let unobstructed =
|
||||
|l: &L| l.unobstructed(bounds) && !spawned.iter().any(|b| b.intersects(bounds));
|
||||
let unobstructed = |l: &L| {
|
||||
spawned.is_none_or(|s| l.unobstructed(bounds) && !s.iter().any(|b| b.intersects(bounds)))
|
||||
};
|
||||
match obstruction(s) {
|
||||
Obstruction::NoLiquidUnobstructed => !contains_any_liquid(l, bounds) && unobstructed(l),
|
||||
Obstruction::Unobstructed => unobstructed(l),
|
||||
@@ -1084,7 +1087,7 @@ pub fn spawn_category_for_position<L: NaturalLevel, R: RandomSource>(
|
||||
let far = distance > despawn && removed_when_far(species);
|
||||
if far
|
||||
|| !mob_spawn_rules(species, l, x, y, z)
|
||||
|| !spawn_obstruction(species, l, bounds, &spawned, x, y, z)
|
||||
|| !spawn_obstruction(species, l, bounds, Some(&spawned), x, y, z)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -1237,8 +1240,8 @@ pub enum GenEvent {
|
||||
Spawn(String, [f64; 3], f32),
|
||||
}
|
||||
/// `NaturalSpawner.spawnMobsForChunkGeneration`: creature packs placed on
|
||||
/// the chunk's surface. `rules` is the level's own random, which the spawn
|
||||
/// predicates draw from.
|
||||
/// the chunk's surface. `rules` is the region's random, which the spawn
|
||||
/// predicates and `spawn` (finalizeSpawn) draw from.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn spawn_mobs_for_chunk_generation<L: NaturalLevel, R: RandomSource, Q: RandomSource>(
|
||||
l: &L,
|
||||
@@ -1246,7 +1249,7 @@ pub fn spawn_mobs_for_chunk_generation<L: NaturalLevel, R: RandomSource, Q: Rand
|
||||
chunk: (i32, i32),
|
||||
random: &mut R,
|
||||
rules: &mut Q,
|
||||
spawn: &mut dyn FnMut(&L, &SpawnRequest) -> Option<Spawned>,
|
||||
spawn: &mut dyn FnMut(&L, &SpawnRequest, &mut Q) -> bool,
|
||||
trace: &mut dyn FnMut(GenEvent),
|
||||
) {
|
||||
if !l.spawn_mobs() {
|
||||
@@ -1269,7 +1272,6 @@ pub fn spawn_mobs_for_chunk_generation<L: NaturalLevel, R: RandomSource, Q: Rand
|
||||
};
|
||||
let count = sample_count(&entry, random);
|
||||
let mut group_index = 0u32;
|
||||
let mut spawned: Vec<Aabb> = Vec::new();
|
||||
let mut x = xo + random.next_int_bound(16).unwrap_or(0);
|
||||
let mut z = zo + random.next_int_bound(16).unwrap_or(0);
|
||||
let (start_x, start_z) = (x, z);
|
||||
@@ -1311,7 +1313,7 @@ pub fn spawn_mobs_for_chunk_generation<L: NaturalLevel, R: RandomSource, Q: Rand
|
||||
let yaw = random.next_float() * 360.0;
|
||||
let bounds = spawn_box(fx, fy, fz, width, height);
|
||||
if mob_spawn_rules(species, l, bx, pos[1], bz)
|
||||
&& spawn_obstruction(species, l, bounds, &spawned, bx, pos[1], bz)
|
||||
&& spawn_obstruction(species, l, bounds, None, bx, pos[1], bz)
|
||||
{
|
||||
let request = SpawnRequest {
|
||||
entity: &entry.entity,
|
||||
@@ -1321,9 +1323,8 @@ pub fn spawn_mobs_for_chunk_generation<L: NaturalLevel, R: RandomSource, Q: Rand
|
||||
group_index,
|
||||
reason: Reason::ChunkGeneration,
|
||||
};
|
||||
if let Some(result) = spawn(l, &request) {
|
||||
if spawn(l, &request, rules) {
|
||||
group_index += 1;
|
||||
spawned.push(result.bounds);
|
||||
trace(GenEvent::Spawn(entry.entity.clone(), [fx, fy, fz], yaw));
|
||||
success = true;
|
||||
}
|
||||
|
||||
@@ -563,7 +563,15 @@ impl SpawnerExt for Spawner {
|
||||
let rules_ok = data.custom_spawn_rules.is_some()
|
||||
|| spawn_selection::mob_spawn_rules(s, l, own[0], own[1], own[2]);
|
||||
if !rules_ok
|
||||
|| !spawn_selection::spawn_obstruction(s, l, bounds, &added, own[0], own[1], own[2])
|
||||
|| !spawn_selection::spawn_obstruction(
|
||||
s,
|
||||
l,
|
||||
bounds,
|
||||
Some(&added),
|
||||
own[0],
|
||||
own[1],
|
||||
own[2],
|
||||
)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1081,7 +1081,15 @@ impl TrialSpawnerExt for TrialSpawner {
|
||||
width * scale,
|
||||
height * scale,
|
||||
);
|
||||
if !spawn_selection::spawn_obstruction(species, l, bounds, &[], own[0], own[1], own[2]) {
|
||||
if !spawn_selection::spawn_obstruction(
|
||||
species,
|
||||
l,
|
||||
bounds,
|
||||
Some(&[]),
|
||||
own[0],
|
||||
own[1],
|
||||
own[2],
|
||||
) {
|
||||
return None;
|
||||
}
|
||||
let request = SpawnerSpawn {
|
||||
|
||||
@@ -19,6 +19,7 @@ use horizon_world::{
|
||||
generator::{
|
||||
GeneratorData, GeneratorWorker, OverworldGenerator, ProtoChunk, SurfaceSample,
|
||||
},
|
||||
structure::legacy::PieceRandom,
|
||||
terrain::PreLightChunkStage,
|
||||
},
|
||||
};
|
||||
@@ -221,6 +222,24 @@ impl VanillaTerrain {
|
||||
pub fn set_spawn_conditions(&self, difficulty: u8, day_time: i64) {
|
||||
self.structure_mobs.set_conditions(difficulty, day_time);
|
||||
}
|
||||
/// The random a chunk's generation steps draw from (`WorldGenRegion`).
|
||||
pub fn region_random(&self, cx: i32, cz: i32) -> Box<dyn PieceRandom> {
|
||||
self.generator.region_random(ChunkPos::new(cx, cz))
|
||||
}
|
||||
/// A mob chunk generation spawns, as the chunk saves it
|
||||
/// (`StructureMobFinalizer::spawn_generated`).
|
||||
pub fn spawn_generated(
|
||||
&self,
|
||||
kind: &str,
|
||||
position: horizon_core::geometry::Vec3,
|
||||
yaw: f32,
|
||||
biome: &str,
|
||||
random: &mut dyn PieceRandom,
|
||||
group: &mut Option<crate::mobs::finalize::Group>,
|
||||
) -> Option<horizon_world::nbt::Compound> {
|
||||
self.structure_mobs
|
||||
.spawn_generated(kind, position, yaw, biome, random, group)
|
||||
}
|
||||
/// A proto chunk's blocks, biomes, scheduled ticks and post-processing
|
||||
/// marks as a column.
|
||||
fn column(&self, chunk: &ProtoChunk) -> Chunk {
|
||||
|
||||
@@ -8,7 +8,7 @@ use crate::{
|
||||
items::Items,
|
||||
mobs::{
|
||||
Mob, MobType, MobTypes,
|
||||
finalize::{self, Ctx, FinalizeLevel, Reason, SpawnData, SpawnRegistries},
|
||||
finalize::{self, Ctx, FinalizeLevel, Group, Reason, SpawnData, SpawnRegistries},
|
||||
spawning::{LocalDifficulty, MOON_BRIGHTNESS},
|
||||
},
|
||||
};
|
||||
@@ -135,6 +135,8 @@ fn run<R: RandomSource>(
|
||||
difficulty: LocalDifficulty,
|
||||
data: &SpawnData<'_>,
|
||||
m: &mut Mob,
|
||||
reason: Reason,
|
||||
group: &mut Option<Group>,
|
||||
) -> Vec<Mob> {
|
||||
let mut cx = Ctx {
|
||||
level,
|
||||
@@ -144,7 +146,7 @@ fn run<R: RandomSource>(
|
||||
created: Vec::new(),
|
||||
jockey_rider: None,
|
||||
};
|
||||
cx.finalize(m, Reason::Structure, None);
|
||||
*group = cx.finalize(m, reason, group.take());
|
||||
if let Some(rider) = cx.jockey_rider {
|
||||
debug_assert_eq!(rider.id, m.id);
|
||||
*m = rider;
|
||||
@@ -190,23 +192,78 @@ impl StructureMobs for StructureMobFinalizer {
|
||||
structures: &[String],
|
||||
random: &mut dyn PieceRandom,
|
||||
) -> bool {
|
||||
let Some(data) = self.data.get_or_init(|| {
|
||||
let data = load(&self.root);
|
||||
if data.is_none() {
|
||||
log::error!("Horizon: no mob data, structure mobs are finalized when loaded");
|
||||
}
|
||||
data
|
||||
}) else {
|
||||
let Some(data) = self.data() else {
|
||||
return false;
|
||||
};
|
||||
let mut raw = Tag::Compound(nbt.clone());
|
||||
if let Tag::Compound(c) = &mut raw {
|
||||
c.remove(&"horizon:finalize".into());
|
||||
}
|
||||
let Some(mut m) = entity_nbt::mob_from_nbt(&raw, &data.types, 1) else {
|
||||
let Some(m) = entity_nbt::mob_from_nbt(&raw, &data.types, 1) else {
|
||||
// Not a mob: vanilla finalizes only Mob instances.
|
||||
return true;
|
||||
};
|
||||
let reason = Reason::Structure;
|
||||
match self.finish(
|
||||
data,
|
||||
m,
|
||||
Some(raw),
|
||||
biome,
|
||||
structures,
|
||||
random,
|
||||
reason,
|
||||
&mut None,
|
||||
) {
|
||||
Some(saved) => {
|
||||
*nbt = saved;
|
||||
true
|
||||
}
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StructureMobFinalizer {
|
||||
fn data(&self) -> Option<&Data> {
|
||||
self.data
|
||||
.get_or_init(|| {
|
||||
let data = load(&self.root);
|
||||
if data.is_none() {
|
||||
log::error!("Horizon: no mob data, structure mobs are finalized when loaded");
|
||||
}
|
||||
data
|
||||
})
|
||||
.as_ref()
|
||||
}
|
||||
/// A mob chunk generation spawns (`spawnOriginalMobs`): a new one of
|
||||
/// `kind` finalized as a structure mob is, as the chunk saves it; `group`
|
||||
/// carries a pack's group data from one member to the next.
|
||||
pub fn spawn_generated(
|
||||
&self,
|
||||
kind: &str,
|
||||
position: Vec3,
|
||||
yaw: f32,
|
||||
biome: &str,
|
||||
random: &mut dyn PieceRandom,
|
||||
group: &mut Option<Group>,
|
||||
) -> Option<Compound> {
|
||||
let data = self.data()?;
|
||||
let m = Mob::new(1, data.types.get(kind)?.clone(), position, yaw);
|
||||
let reason = Reason::ChunkGeneration;
|
||||
self.finish(data, m, None, biome, &[], random, reason, group)
|
||||
}
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn finish(
|
||||
&self,
|
||||
data: &Data,
|
||||
mut m: Mob,
|
||||
raw: Option<Tag>,
|
||||
biome: &str,
|
||||
structures: &[String],
|
||||
random: &mut dyn PieceRandom,
|
||||
reason: Reason,
|
||||
group: &mut Option<Group>,
|
||||
) -> Option<Compound> {
|
||||
let base = self.difficulty.load(Ordering::Relaxed);
|
||||
let day_time = self.day_time.load(Ordering::Relaxed);
|
||||
let mut level = Level {
|
||||
@@ -228,21 +285,20 @@ impl StructureMobs for StructureMobFinalizer {
|
||||
};
|
||||
let any = random.as_any();
|
||||
let created = if let Some(r) = any.downcast_mut::<LegacyRandom>() {
|
||||
run(&mut level, r, difficulty, &spawn, &mut m)
|
||||
run(&mut level, r, difficulty, &spawn, &mut m, reason, group)
|
||||
} else if let Some(r) = any.downcast_mut::<XoroshiroRandom>() {
|
||||
run(&mut level, r, difficulty, &spawn, &mut m)
|
||||
run(&mut level, r, difficulty, &spawn, &mut m, reason, group)
|
||||
} else {
|
||||
log::error!("Horizon: unknown region random, structure mobs are finalized when loaded");
|
||||
return false;
|
||||
return None;
|
||||
};
|
||||
let mut mobs = vec![(m, Some(raw))];
|
||||
let mut mobs = vec![(m, raw)];
|
||||
mobs.extend(created.into_iter().map(|c| (c, None)));
|
||||
let root = mobs
|
||||
.iter()
|
||||
.position(|(m, _)| m.extra.mount.is_none())
|
||||
.unwrap_or(0);
|
||||
*nbt = tree(&mobs, root, &self.dimension);
|
||||
true
|
||||
Some(tree(&mobs, root, &self.dimension))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1334,6 +1334,17 @@ impl<R: RandomSource> Pipeline<R>
|
||||
where
|
||||
R::Positional: Clone + Send + Sync,
|
||||
{
|
||||
/// WorldGenRegion's level random, positioned at the center chunk.
|
||||
fn region_random_at(&self, center: ChunkPos) -> R {
|
||||
self.root
|
||||
.from_hash_of("minecraft:worldgen_region_random")
|
||||
.fork_positional()
|
||||
.at(BlockPos::new(
|
||||
center.x.wrapping_shl(4),
|
||||
0,
|
||||
center.z.wrapping_shl(4),
|
||||
))
|
||||
}
|
||||
/// ChunkGenerator.getBaseHeight: the first free Y above the noise column
|
||||
/// (with aquifers, without terrain adaptation) for a heightmap type.
|
||||
fn base_height(&self, x: i32, z: i32, kind: HeightmapType) -> i32 {
|
||||
@@ -1778,6 +1789,7 @@ trait SeededPipeline: Send + Sync {
|
||||
fn forget_structure_starts(&self);
|
||||
fn ring_positions(&self, worker: &mut WorkerState, set: &str) -> Option<Vec<ChunkPos>>;
|
||||
fn spawn_position(&self) -> Result<[i32; 2], GeneratorError>;
|
||||
fn region_random(&self, center: ChunkPos) -> Box<dyn super::structure::legacy::PieceRandom>;
|
||||
fn locate(
|
||||
&self,
|
||||
worker: &mut WorkerState,
|
||||
@@ -1816,17 +1828,7 @@ where
|
||||
) -> Result<DecorateStats, GeneratorError> {
|
||||
match &self.decoration {
|
||||
Some(decoration) => {
|
||||
let center = region[4].position();
|
||||
// WorldGenRegion's level random, positioned at the center chunk.
|
||||
let mut random = self
|
||||
.root
|
||||
.from_hash_of("minecraft:worldgen_region_random")
|
||||
.fork_positional()
|
||||
.at(BlockPos::new(
|
||||
center.x.wrapping_shl(4),
|
||||
0,
|
||||
center.z.wrapping_shl(4),
|
||||
));
|
||||
let mut random = self.region_random_at(region[4].position());
|
||||
decoration.decorate(
|
||||
&self.data,
|
||||
self.seed,
|
||||
@@ -1915,6 +1917,9 @@ where
|
||||
set,
|
||||
)
|
||||
}
|
||||
fn region_random(&self, center: ChunkPos) -> Box<dyn super::structure::legacy::PieceRandom> {
|
||||
Box::new(self.region_random_at(center))
|
||||
}
|
||||
fn spawn_position(&self) -> Result<[i32; 2], GeneratorError> {
|
||||
if let Some(position) = self.spawn_position.get() {
|
||||
return Ok(*position);
|
||||
@@ -2551,6 +2556,14 @@ impl OverworldGenerator {
|
||||
pub fn spawn_position(&self) -> Result<[i32; 2], GeneratorError> {
|
||||
self.pipeline.spawn_position()
|
||||
}
|
||||
/// A new `WorldGenRegion` random centred on the chunk: what its generation
|
||||
/// steps draw from (`level.getRandom()`).
|
||||
pub fn region_random(
|
||||
&self,
|
||||
center: ChunkPos,
|
||||
) -> Box<dyn super::structure::legacy::PieceRandom> {
|
||||
self.pipeline.region_random(center)
|
||||
}
|
||||
/// Create per-thread state. Workers are cheap; keep one per thread.
|
||||
pub fn worker(&self) -> GeneratorWorker {
|
||||
let data = self.pipeline.data();
|
||||
|
||||
@@ -246,7 +246,7 @@ public final class VanillaFinalizeSpawnOracle {
|
||||
Map.entry("jump_strength", Attributes.JUMP_STRENGTH));
|
||||
|
||||
static final List<Map.Entry<String, MemoryModuleType<?>>> MEMORIES = List.of(
|
||||
Map.entry("long_jump_cooldown", MemoryModuleType.LONG_JUMP_COOLDOWN_TICKS), Map.entry("ram_cooldown", MemoryModuleType.RAM_COOLDOWN_TICKS),
|
||||
Map.entry("long_jump_cooling_down", MemoryModuleType.LONG_JUMP_COOLDOWN_TICKS), Map.entry("ram_cooldown_ticks", MemoryModuleType.RAM_COOLDOWN_TICKS),
|
||||
Map.entry("attack_target_cooldown", MemoryModuleType.ATTACK_TARGET_COOLDOWN), Map.entry("hunted_recently", MemoryModuleType.HUNTED_RECENTLY),
|
||||
Map.entry("dig_cooldown", MemoryModuleType.DIG_COOLDOWN), Map.entry("is_emerging", MemoryModuleType.IS_EMERGING),
|
||||
Map.entry("home", MemoryModuleType.HOME));
|
||||
|
||||
@@ -171,6 +171,8 @@ public final class VanillaNaturalSpawnOracle {
|
||||
int tick = -1;
|
||||
List<String> log;
|
||||
boolean logColumns;
|
||||
// A WorldGenRegion sees no entities (getEntities is empty).
|
||||
boolean generationRegion;
|
||||
int next = 1;
|
||||
@Override public RegistryAccess registryAccess() { return registries; }
|
||||
@Override public Difficulty getDifficulty() { return difficulty; }
|
||||
@@ -281,11 +283,13 @@ public final class VanillaNaturalSpawnOracle {
|
||||
@Override public Iterable<Entity> getAllEntities() { return entities; }
|
||||
@Override public List<Entity> getEntities(Entity except, AABB bb, Predicate<? super Entity> selector) {
|
||||
List<Entity> out = new ArrayList<>();
|
||||
if (generationRegion) return out;
|
||||
for (Entity e : entities) if (e != except && e.getBoundingBox().intersects(bb) && selector.test(e)) out.add(e);
|
||||
return out;
|
||||
}
|
||||
@Override public <T extends Entity> List<T> getEntities(EntityTypeTest<Entity, T> type, AABB bb, Predicate<? super T> selector) {
|
||||
List<T> out = new ArrayList<>();
|
||||
if (generationRegion) return out;
|
||||
for (Entity e : entities) {
|
||||
T t = type.tryCast(e);
|
||||
if (t != null && e.getBoundingBox().intersects(bb) && selector.test(t)) out.add(t);
|
||||
@@ -811,11 +815,13 @@ public final class VanillaNaturalSpawnOracle {
|
||||
vaultCase(level, c, access, blockLookup, mainRandom, log);
|
||||
} else if (mode.equals("gen")) {
|
||||
level.logColumns = true;
|
||||
level.generationRegion = true;
|
||||
int n = level.entities.size();
|
||||
RandomSource random = RandomSource.create(Long.parseLong(c.getOrDefault("gseed", "0")));
|
||||
BlockPos sourcePos = new BlockPos(chunk.at.getMinBlockX(), level.getMaxY(), chunk.at.getMinBlockZ());
|
||||
NaturalSpawner.spawnMobsForChunkGeneration(level, sourcePos, chunk.at, random);
|
||||
level.logColumns = false;
|
||||
level.generationRegion = false;
|
||||
for (Entity e : level.entities.subList(n, level.entities.size()))
|
||||
if (e.getVehicle() == null)
|
||||
log.add("spawn " + BuiltInRegistries.ENTITY_TYPE.getKey(e.getType()) + " " + bits(e.getX()) + " " + bits(e.getY()) + " " + bits(e.getZ()) + " " + yaw(e));
|
||||
|
||||
Reference in new issue
Block a user