mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 11:06:11 -10:00
Merge pull request #9992 from UMCEKO/fix/tnt-owner-saved
Primed TNT saves and loads its owner
This commit is contained in:
5 files changed
+108
-45
No files matched your search
@@ -2260,7 +2260,9 @@ pub fn tnt_power(tag: &Tag) -> f32 {
|
||||
}
|
||||
|
||||
/// `PrimedTnt.addAdditionalSaveData`: its `fuse` and, when it is not the
|
||||
/// default, its `explosion_power`, and the encoded live block state.
|
||||
/// default, its `explosion_power`, the encoded live block state, and its
|
||||
/// `owner` (`EntityReference.store`). With no live owner, a loaded `owner`
|
||||
/// stays as the raw compound has it.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn tnt_to_nbt(
|
||||
position: Vec3,
|
||||
@@ -2271,6 +2273,7 @@ pub fn tnt_to_nbt(
|
||||
block_state: Tag,
|
||||
rotation: [f32; 2],
|
||||
uuid: [i32; 4],
|
||||
owner: Option<[i32; 4]>,
|
||||
raw: Option<&Tag>,
|
||||
) -> Tag {
|
||||
let mut tag = Base {
|
||||
@@ -2288,8 +2291,15 @@ pub fn tnt_to_nbt(
|
||||
put(&mut tag, "explosion_power", Tag::Float(power.to_bits()));
|
||||
}
|
||||
put(&mut tag, "block_state", block_state);
|
||||
if let Some(owner) = owner {
|
||||
put(&mut tag, "owner", Tag::IntArray(owner.to_vec()));
|
||||
}
|
||||
tag
|
||||
}
|
||||
/// `PrimedTnt.readAdditionalSaveData`: `EntityReference.read(input, "owner")`.
|
||||
pub fn tnt_owner(tag: &Tag) -> Option<[i32; 4]> {
|
||||
uuid_from_tag(tag.get("owner"))
|
||||
}
|
||||
/// `EvokerFangs.addAdditionalSaveData`: always save the live warmup counter.
|
||||
/// Known Owner UUIDs use `EntityReference.store` encoding; unresolved
|
||||
/// loaded Owner data remains in the raw compound.
|
||||
@@ -3666,6 +3676,7 @@ mod tnt_power_tests {
|
||||
[0.0, 0.0],
|
||||
[1, 2, 3, 4],
|
||||
None,
|
||||
None,
|
||||
);
|
||||
assert_eq!(tnt_power(&saved), 10.0);
|
||||
let plain = tnt_to_nbt(
|
||||
@@ -3678,9 +3689,38 @@ mod tnt_power_tests {
|
||||
[0.0, 0.0],
|
||||
[1, 2, 3, 4],
|
||||
None,
|
||||
None,
|
||||
);
|
||||
assert!(plain.get("explosion_power").is_none());
|
||||
}
|
||||
|
||||
/// `EntityReference.store(owner, output, "owner")`: the igniter's UUID as
|
||||
/// an int array, read back by `EntityReference.read`; an owner the save
|
||||
/// could not name keeps the loaded one from the raw compound.
|
||||
#[test]
|
||||
fn the_owner_round_trips_as_a_uuid() {
|
||||
let save = |owner, raw: Option<&Tag>| {
|
||||
let t = string("minecraft:tnt");
|
||||
tnt_to_nbt(
|
||||
Vec3::ZERO,
|
||||
Vec3::ZERO,
|
||||
false,
|
||||
80,
|
||||
4.0,
|
||||
t,
|
||||
[0.0; 2],
|
||||
[1; 4],
|
||||
owner,
|
||||
raw,
|
||||
)
|
||||
};
|
||||
let saved = save(Some([5, 6, 7, 8]), None);
|
||||
assert_eq!(saved.get("owner"), Some(&Tag::IntArray(vec![5, 6, 7, 8])));
|
||||
assert_eq!(tnt_owner(&saved), Some([5, 6, 7, 8]));
|
||||
assert_eq!(tnt_owner(&save(None, None)), None);
|
||||
assert_eq!(tnt_owner(&save(None, Some(&saved))), Some([5, 6, 7, 8]));
|
||||
assert_eq!(tnt_owner(&save(Some([9; 4]), Some(&saved))), Some([9; 4]));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -4504,6 +4544,7 @@ mod tests {
|
||||
[90.0, 10.0],
|
||||
[1; 4],
|
||||
None,
|
||||
None,
|
||||
);
|
||||
assert_eq!(rotation(&tnt), [90.0, 10.0]);
|
||||
let mut stand = ArmorStand::new(1, Vec3::ZERO, 0.0);
|
||||
|
||||
@@ -201,19 +201,8 @@ impl Game<'_> {
|
||||
crate::game::persist::with_raw(tag, self.persist.raw.get(&id))
|
||||
}
|
||||
Target::Tnt(id) => {
|
||||
let uuid = self.uuid_for(id);
|
||||
let t = self.tnts.iter().find(|t| t.id == id)?;
|
||||
entity_nbt::tnt_to_nbt(
|
||||
t.position,
|
||||
t.velocity,
|
||||
t.on_ground,
|
||||
t.fuse,
|
||||
t.power,
|
||||
t.block_state_nbt(self.pack.assets.registry),
|
||||
t.rotation,
|
||||
uuid,
|
||||
self.persist.raw.get(&id),
|
||||
)
|
||||
let i = self.tnts.iter().position(|t| t.id == id)?;
|
||||
self.tnt_nbt(i)
|
||||
}
|
||||
Target::Falling(id) => {
|
||||
let f = self.falling.iter().find(|f| f.id == id)?.clone();
|
||||
|
||||
@@ -967,19 +967,7 @@ impl Game<'_> {
|
||||
if !self.may_enter(plan.dim) {
|
||||
return;
|
||||
}
|
||||
let uuid = self.uuid_for(id);
|
||||
let t = &self.tnts[i];
|
||||
let tag = horizon_client::entity_nbt::tnt_to_nbt(
|
||||
t.position,
|
||||
t.velocity,
|
||||
t.on_ground,
|
||||
t.fuse,
|
||||
t.power,
|
||||
t.block_state_nbt(self.pack.assets.registry),
|
||||
t.rotation,
|
||||
uuid,
|
||||
self.persist_raw(id),
|
||||
);
|
||||
let tag = self.tnt_nbt(i);
|
||||
self.tnts.remove(i);
|
||||
self.forget_entity(id);
|
||||
self.portals.arrivals.push(Arrival { plan, tag });
|
||||
|
||||
@@ -1799,23 +1799,9 @@ impl Game<'_> {
|
||||
i: usize,
|
||||
keys: &HashSet<ChunkKey>,
|
||||
) -> Option<(ChunkKey, Tag)> {
|
||||
let (id, at) = (self.tnts[i].id, self.tnts[i].position);
|
||||
keys.contains(&chunk_of(at)).then(|| {
|
||||
let uuid = self.uuid_for(id);
|
||||
let t = &self.tnts[i];
|
||||
let tag = entity_nbt::tnt_to_nbt(
|
||||
t.position,
|
||||
t.velocity,
|
||||
t.on_ground,
|
||||
t.fuse,
|
||||
t.power,
|
||||
t.block_state_nbt(self.pack.assets.registry),
|
||||
t.rotation,
|
||||
uuid,
|
||||
self.persist.raw.get(&id),
|
||||
);
|
||||
(chunk_of(at), tag)
|
||||
})
|
||||
let at = self.tnts[i].position;
|
||||
keys.contains(&chunk_of(at))
|
||||
.then(|| (chunk_of(at), self.tnt_nbt(i)))
|
||||
}
|
||||
pub(super) fn save_boat(
|
||||
&mut self,
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
//! (flint and steel / fire charge).
|
||||
use super::{Game, entity::Entity};
|
||||
use horizon_client::entity_rules::EntityRules;
|
||||
use horizon_client::lan::playerdata::uuid_ints;
|
||||
use horizon_client::{
|
||||
Result, collisions::SceneCollisions, items::ItemStack, mesher::Vertex, mobs::Who, text::Text,
|
||||
};
|
||||
@@ -282,7 +283,13 @@ impl Game<'_> {
|
||||
t.position.z,
|
||||
];
|
||||
let bounds = t.bounds();
|
||||
let (owner, guest, power) = (t.owner, t.guest, t.power);
|
||||
let (id, owner, guest, power) = (t.id, t.owner, t.guest, t.power);
|
||||
let (owner, guest) = match owner {
|
||||
Some(_) => (owner, guest),
|
||||
None => self
|
||||
.loaded_tnt_owner(id)
|
||||
.map_or((None, None), |(w, g)| (Some(w), g)),
|
||||
};
|
||||
let in_water = self.bounds_in_water(bounds);
|
||||
self.tnts.remove(i);
|
||||
// PrimedTnt.tick: with `tnt_explodes` off it goes away without exploding.
|
||||
@@ -313,6 +320,57 @@ impl Game<'_> {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
/// The saved compound of primed TNT `i`: chunk saves, `/data get`, portals.
|
||||
pub(super) fn tnt_nbt(&mut self, i: usize) -> horizon_world::nbt::Tag {
|
||||
let id = self.tnts[i].id;
|
||||
let uuid = self.uuid_for(id);
|
||||
let owner = self.tnt_owner_uuid(self.tnts[i].owner, self.tnts[i].guest);
|
||||
let t = &self.tnts[i];
|
||||
horizon_client::entity_nbt::tnt_to_nbt(
|
||||
t.position,
|
||||
t.velocity,
|
||||
t.on_ground,
|
||||
t.fuse,
|
||||
t.power,
|
||||
t.block_state_nbt(self.pack.assets.registry),
|
||||
t.rotation,
|
||||
uuid,
|
||||
owner,
|
||||
self.persist.raw.get(&id),
|
||||
)
|
||||
}
|
||||
/// `EntityReference.of(owner)`: the UUID of the player or mob that lit it.
|
||||
fn tnt_owner_uuid(&mut self, owner: Option<Who>, guest: Option<u32>) -> Option<[i32; 4]> {
|
||||
match owner? {
|
||||
Who::Player => match guest {
|
||||
Some(conn) => self
|
||||
.lan
|
||||
.as_ref()?
|
||||
.hub
|
||||
.guest(conn)
|
||||
.map(|g| uuid_ints(g.uuid)),
|
||||
None => self.level.player_uuid,
|
||||
},
|
||||
Who::Mob(id) if self.mobs.iter().any(|m| m.id == id) => Some(self.uuid_for(id)),
|
||||
Who::Mob(id) => self.persist.uuids.get(&id).copied(),
|
||||
}
|
||||
}
|
||||
/// `PrimedTnt.getOwner` for TNT loaded with an `owner`: looked up when
|
||||
/// asked, so a player who joined or a mob loaded since is found.
|
||||
fn loaded_tnt_owner(&self, id: u32) -> Option<(Who, Option<u32>)> {
|
||||
let uuid = horizon_client::entity_nbt::tnt_owner(self.persist.raw.get(&id)?)?;
|
||||
let guest = self.lan.as_ref().and_then(|host| {
|
||||
let mut guests = host.hub.guests.iter();
|
||||
guests.find(|g| uuid_ints(g.uuid) == uuid).map(|g| g.conn)
|
||||
});
|
||||
if guest.is_some() {
|
||||
return Some((Who::Player, guest));
|
||||
}
|
||||
if self.level.player_uuid == Some(uuid) {
|
||||
return Some((Who::Player, None));
|
||||
}
|
||||
self.mob_by_uuid(uuid).map(|m| (Who::Mob(m), None))
|
||||
}
|
||||
/// `TntBlock.useItemOn`: flint and steel or a fire charge primes TNT.
|
||||
pub(super) fn ignite_tnt(&mut self, stack: &ItemStack) -> Result<bool> {
|
||||
let item = &*stack.item;
|
||||
@@ -594,6 +652,7 @@ mod tests {
|
||||
tnt.block_state_nbt(®istry),
|
||||
[0.0, 0.0],
|
||||
[221, 0, 0, 1],
|
||||
None,
|
||||
Some(&raw),
|
||||
);
|
||||
assert_eq!(saved.get("block_state"), Some(&expected));
|
||||
|
||||
Reference in new issue
Block a user