mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 11:06:11 -10:00
Merge pull request #3064 from iLostName/trial-spawner-spawn-egg
🤖 Agent: read the diff at 557a82738: a spawn egg on a trial spawner follows `TrialSpawner.overrideEntityToSpawn` -> `FullConfig.overrideEntity` in the 26.3 source: data reset, normal and ominous configs replaced by `Holder.direct(withSpawning(type))` (saved inline through the new `TrialConfig::encode`, `optionalFieldOf` defaults left out), then `setState(INACTIVE)`; plain spawners unchanged. Round-trip test included. verify and parity green on this head. Queued for the merger.
This commit is contained in:
3 files changed
+130
-16
No files matched your search
@@ -10,6 +10,7 @@ use horizon_client::{
|
||||
Mob, Who,
|
||||
spawner::{Spawner, SpawnerAdded, SpawnerEvent, SpawnerSpawn},
|
||||
spawning,
|
||||
trial_spawner::TrialState,
|
||||
},
|
||||
particles::{ParticleOptions, ParticleType},
|
||||
};
|
||||
@@ -373,12 +374,11 @@ impl Game<'_> {
|
||||
/// clicked block has no spawner.
|
||||
pub(super) fn use_spawn_egg_on_spawner(&mut self, pos: [i32; 3], kind: &str) -> Option<bool> {
|
||||
let key = (pos[0], pos[1], pos[2]);
|
||||
if !matches!(
|
||||
self.world.block_entities.get(&key),
|
||||
Some(BlockEntity::Spawner(_))
|
||||
) {
|
||||
return None;
|
||||
}
|
||||
let trial = match self.world.block_entities.get(&key) {
|
||||
Some(BlockEntity::Spawner(_)) => false,
|
||||
Some(BlockEntity::TrialSpawner(_)) => true,
|
||||
_ => return None,
|
||||
};
|
||||
if !self.ui.rules.bool("spawner_blocks_work") {
|
||||
self.system_message(horizon_client::text::Text::translate(
|
||||
"advMode.notEnabled.spawner",
|
||||
@@ -386,16 +386,26 @@ impl Game<'_> {
|
||||
));
|
||||
return Some(false);
|
||||
}
|
||||
let mut events = Vec::new();
|
||||
let mut random = std::mem::replace(
|
||||
&mut self.world.random,
|
||||
horizon_core::random::LegacyRandom::new(0),
|
||||
);
|
||||
if let Some(BlockEntity::Spawner(s)) = self.world.block_entities.get_mut(&key) {
|
||||
s.set_entity_id(kind, &mut random, &mut events);
|
||||
if trial {
|
||||
// TrialSpawnerBlockEntity.setEntityId: overrideEntityToSpawn, which
|
||||
// ends in setState(INACTIVE).
|
||||
if let Some(BlockEntity::TrialSpawner(t)) = self.world.block_entities.get_mut(&key) {
|
||||
t.override_entity(kind);
|
||||
}
|
||||
let ominous = self.trial_spawner_block(key).is_some_and(|(_, o)| o);
|
||||
let _ = self.set_trial_spawner_state(key, TrialState::Inactive, ominous);
|
||||
} else {
|
||||
let mut events = Vec::new();
|
||||
let mut random = std::mem::replace(
|
||||
&mut self.world.random,
|
||||
horizon_core::random::LegacyRandom::new(0),
|
||||
);
|
||||
if let Some(BlockEntity::Spawner(s)) = self.world.block_entities.get_mut(&key) {
|
||||
s.set_entity_id(kind, &mut random, &mut events);
|
||||
}
|
||||
self.world.random = random;
|
||||
let _ = self.apply_spawner_events(key, events, &HashMap::new());
|
||||
}
|
||||
self.world.random = random;
|
||||
let _ = self.apply_spawner_events(key, events, &HashMap::new());
|
||||
// sendBlockUpdated(3) and the player's block_change game event.
|
||||
self.spawner_block_updated(key);
|
||||
self.post_block_game_event("minecraft:block_change", pos, Some(Who::Player), None);
|
||||
|
||||
@@ -272,7 +272,7 @@ impl Game<'_> {
|
||||
Ok(())
|
||||
}
|
||||
/// `setState` (and `removeOminous`): the block's state and ominous flag.
|
||||
fn set_trial_spawner_state(
|
||||
pub(super) fn set_trial_spawner_state(
|
||||
&mut self,
|
||||
key: Pos,
|
||||
state: TrialState,
|
||||
|
||||
@@ -106,6 +106,67 @@ impl TrialConfig {
|
||||
.unwrap_or(d.items_to_drop_when_ominous),
|
||||
})
|
||||
}
|
||||
/// `TrialSpawnerConfig.DIRECT_CODEC`'s encoding: the fields that differ
|
||||
/// from their defaults (`optionalFieldOf` with a default).
|
||||
pub fn encode(&self) -> Tag {
|
||||
let d = Self::default();
|
||||
let mut out = Vec::new();
|
||||
if self.spawn_range != d.spawn_range {
|
||||
out.push(("spawn_range", Tag::Int(self.spawn_range)));
|
||||
}
|
||||
for (key, value, default) in [
|
||||
("total_mobs", self.total_mobs, d.total_mobs),
|
||||
(
|
||||
"simultaneous_mobs",
|
||||
self.simultaneous_mobs,
|
||||
d.simultaneous_mobs,
|
||||
),
|
||||
(
|
||||
"total_mobs_added_per_player",
|
||||
self.total_mobs_added_per_player,
|
||||
d.total_mobs_added_per_player,
|
||||
),
|
||||
(
|
||||
"simultaneous_mobs_added_per_player",
|
||||
self.simultaneous_mobs_added_per_player,
|
||||
d.simultaneous_mobs_added_per_player,
|
||||
),
|
||||
] {
|
||||
if value != default {
|
||||
out.push((key, Tag::Float(value.to_bits())));
|
||||
}
|
||||
}
|
||||
if self.ticks_between_spawn != d.ticks_between_spawn {
|
||||
out.push(("ticks_between_spawn", Tag::Int(self.ticks_between_spawn)));
|
||||
}
|
||||
let weighted = |data: Tag, weight: i32| {
|
||||
Tag::compound(vec![("data", data), ("weight", Tag::Int(weight))])
|
||||
};
|
||||
if !self.spawn_potentials.is_empty() {
|
||||
let list = self.spawn_potentials.iter();
|
||||
out.push((
|
||||
"spawn_potentials",
|
||||
Tag::List(list.map(|(data, w)| weighted(data.encode(), *w)).collect()),
|
||||
));
|
||||
}
|
||||
if self.loot_tables_to_eject != d.loot_tables_to_eject {
|
||||
let list = self.loot_tables_to_eject.iter();
|
||||
out.push((
|
||||
"loot_tables_to_eject",
|
||||
Tag::List(
|
||||
list.map(|(id, w)| weighted(Tag::String(id.as_str().into()), *w))
|
||||
.collect(),
|
||||
),
|
||||
));
|
||||
}
|
||||
if self.items_to_drop_when_ominous != d.items_to_drop_when_ominous {
|
||||
out.push((
|
||||
"items_to_drop_when_ominous",
|
||||
Tag::String(self.items_to_drop_when_ominous.as_str().into()),
|
||||
));
|
||||
}
|
||||
Tag::compound(out)
|
||||
}
|
||||
/// `calculateTargetTotalMobs`.
|
||||
pub fn target_total_mobs(&self, additional_players: i32) -> i32 {
|
||||
(self.total_mobs + self.total_mobs_added_per_player * additional_players as f32).floor()
|
||||
@@ -487,6 +548,18 @@ impl TrialSpawner {
|
||||
self.next_spawn_data = None;
|
||||
self.reset_statistics();
|
||||
}
|
||||
/// `overrideEntityToSpawn` (a spawn egg's `setEntityId`): the data resets
|
||||
/// and both configs spawn only `id` (`withSpawning`), saved inline as
|
||||
/// `Holder.direct`. The caller then sets the state to `inactive`.
|
||||
pub fn override_entity(&mut self, id: &str) {
|
||||
self.reset();
|
||||
for slot in [&mut self.normal, &mut self.ominous] {
|
||||
let mut entity = Compound::new();
|
||||
entity.insert("id".into(), Tag::String(id.into()));
|
||||
slot.config.spawn_potentials = vec![(SpawnData::new(entity, None, None), 1)];
|
||||
slot.saved = Some(slot.config.encode());
|
||||
}
|
||||
}
|
||||
/// `getOrCreateNextSpawnData`: a potential at random, else empty data.
|
||||
fn next_or_create(
|
||||
&mut self,
|
||||
@@ -1030,6 +1103,37 @@ mod tests {
|
||||
assert!(s.save().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_spawn_egg_overrides_both_configs_inline() {
|
||||
let breeze = TrialConfig {
|
||||
total_mobs: 2.0,
|
||||
..TrialConfig::default()
|
||||
};
|
||||
let registry = |_: &str| Some(breeze.clone());
|
||||
let mut s = TrialSpawner::load_with(
|
||||
&tag(
|
||||
r#"{normal_config:"minecraft:trial_chamber/breeze/normal",spawn_data:{entity:{}},total_mobs_spawned:3}"#,
|
||||
),
|
||||
®istry,
|
||||
);
|
||||
s.override_entity("minecraft:zombie");
|
||||
assert_eq!(s.next_spawn_data, None);
|
||||
assert_eq!(s.total_mobs_spawned, 0);
|
||||
for slot in [&s.normal, &s.ominous] {
|
||||
assert_eq!(slot.config.spawn_potentials.len(), 1);
|
||||
assert_eq!(
|
||||
slot.config.spawn_potentials[0].0.id().as_deref(),
|
||||
Some("minecraft:zombie")
|
||||
);
|
||||
let saved = slot.saved.as_ref().expect("saved inline");
|
||||
assert_eq!(TrialConfig::decode(saved).as_ref(), Some(&slot.config));
|
||||
}
|
||||
assert_eq!(
|
||||
s.normal.saved.as_ref().and_then(|t| t.get("total_mobs")),
|
||||
Some(&Tag::Float(2.0f32.to_bits()))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn registry_configs_resolve_and_are_saved_by_id() {
|
||||
let breeze = TrialConfig {
|
||||
|
||||
Reference in new issue
Block a user