mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 11:06:11 -10:00
Merge pull request #11393 from ilguerro/fix/10548-villager-memories
Save villager brain memories like vanilla
This commit is contained in:
2 files changed
+228
-20
No files matched your search
@@ -290,6 +290,8 @@ pub struct Ext {
|
||||
pub cure: Option<super::zombie_villager::Cure>,
|
||||
/// The villager's brain, built at its first tick.
|
||||
pub brain: Option<Box<super::villager_brain::Brain>>,
|
||||
/// Saved memories awaiting the first brain initialization.
|
||||
pub saved_memories: Option<Box<super::villager_brain::Memories>>,
|
||||
}
|
||||
pub fn ext(m: &Mob) -> Option<&Ext> {
|
||||
m.extra.villager.as_deref()
|
||||
@@ -838,29 +840,133 @@ pub fn write_nbt(m: &Mob, map: &mut horizon_world::nbt::Compound) {
|
||||
}
|
||||
/// The memories of `Brain.memories` the villager keeps besides positions.
|
||||
pub fn write_memories(m: &Mob, memories: &mut Vec<(&'static str, horizon_world::nbt::Tag)>) {
|
||||
use super::villager_brain::{Memories, NEVER, Slot};
|
||||
use horizon_world::nbt::Tag;
|
||||
fn push<T>(
|
||||
out: &mut Vec<(&'static str, Tag)>,
|
||||
key: &'static str,
|
||||
slot: &Slot<T>,
|
||||
encode: impl FnOnce(&T) -> Tag,
|
||||
) {
|
||||
if let Some(value) = slot.get() {
|
||||
let mut fields = vec![("value", encode(value))];
|
||||
if slot.ttl() != NEVER {
|
||||
fields.push(("ttl", Tag::Long(slot.ttl())));
|
||||
}
|
||||
out.push((key, Tag::compound(fields)));
|
||||
}
|
||||
}
|
||||
let Some(e) = ext(m) else { return };
|
||||
if let Some(t) = e.last_slept {
|
||||
let fallback = Memories::default();
|
||||
let mm = e
|
||||
.brain
|
||||
.as_ref()
|
||||
.map(|b| &b.mem)
|
||||
.or(e.saved_memories.as_deref())
|
||||
.unwrap_or(&fallback);
|
||||
// The three established position fields are written by entity_nbt; replace
|
||||
// them only when the live brain has a value (and therefore a possible TTL).
|
||||
for (key, slot) in [
|
||||
("minecraft:home", &mm.home),
|
||||
("minecraft:job_site", &mm.job_site),
|
||||
("minecraft:meeting_point", &mm.meeting_point),
|
||||
("minecraft:potential_job_site", &mm.potential_job_site),
|
||||
] {
|
||||
if slot.present() {
|
||||
memories.retain(|(k, _)| *k != key);
|
||||
push(memories, key, slot, |p| {
|
||||
Tag::compound([
|
||||
("dimension", Tag::String("minecraft:overworld".into())),
|
||||
("pos", Tag::IntArray(p.to_vec())),
|
||||
])
|
||||
});
|
||||
}
|
||||
}
|
||||
push(
|
||||
memories,
|
||||
"minecraft:golem_detected_recently",
|
||||
&mm.golem_detected_recently,
|
||||
|v| Tag::Byte(i8::from(*v)),
|
||||
);
|
||||
if mm.last_slept.present() {
|
||||
push(memories, "minecraft:last_slept", &mm.last_slept, |v| {
|
||||
Tag::Long(*v)
|
||||
});
|
||||
} else if e.brain.is_none()
|
||||
&& e.saved_memories.is_none()
|
||||
&& let Some(t) = e.last_slept
|
||||
{
|
||||
memories.push((
|
||||
"minecraft:last_slept",
|
||||
Tag::compound([("value", Tag::Long(t))]),
|
||||
));
|
||||
}
|
||||
push(memories, "minecraft:last_woken", &mm.last_woken, |v| {
|
||||
Tag::Long(*v)
|
||||
});
|
||||
}
|
||||
|
||||
pub fn read_nbt(m: &mut Mob, tag: &horizon_world::nbt::Tag) {
|
||||
use horizon_world::nbt::Tag;
|
||||
let int = |t: Option<&Tag>| match t {
|
||||
Some(Tag::Int(v)) => Some(i64::from(*v)),
|
||||
Some(Tag::Byte(v)) => Some(i64::from(*v)),
|
||||
Some(Tag::Short(v)) => Some(i64::from(*v)),
|
||||
Some(Tag::Long(v)) => Some(*v),
|
||||
_ => None,
|
||||
};
|
||||
let last_slept = int(tag
|
||||
.get("Brain")
|
||||
.and_then(|b| b.get("memories"))
|
||||
.and_then(|b| b.get("minecraft:last_slept"))
|
||||
.and_then(|j| j.get("value")));
|
||||
fn int(t: Option<&Tag>) -> Option<i64> {
|
||||
match t {
|
||||
Some(Tag::Int(v)) => Some(i64::from(*v)),
|
||||
Some(Tag::Byte(v)) => Some(i64::from(*v)),
|
||||
Some(Tag::Short(v)) => Some(i64::from(*v)),
|
||||
Some(Tag::Long(v)) => Some(*v),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
let memories = tag.get("Brain").and_then(|b| b.get("memories"));
|
||||
let mut saved = super::villager_brain::Memories::default();
|
||||
fn read<T>(
|
||||
memories: Option<&Tag>,
|
||||
key: &str,
|
||||
slot: &mut super::villager_brain::Slot<T>,
|
||||
decode: impl FnOnce(&Tag) -> Option<T>,
|
||||
) {
|
||||
if let Some(entry) = memories.and_then(|m| m.get(key))
|
||||
&& let Some(value) = entry.get("value").and_then(decode)
|
||||
{
|
||||
// `ExpirableValue.codec`: a lenient optional `Codec.LONG`.
|
||||
match int(entry.get("ttl")) {
|
||||
Some(ttl) => slot.set_ttl(value, ttl),
|
||||
None => slot.set(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
for (key, slot) in [
|
||||
("minecraft:home", &mut saved.home),
|
||||
("minecraft:job_site", &mut saved.job_site),
|
||||
("minecraft:meeting_point", &mut saved.meeting_point),
|
||||
(
|
||||
"minecraft:potential_job_site",
|
||||
&mut saved.potential_job_site,
|
||||
),
|
||||
] {
|
||||
read(memories, key, slot, |v| match v.get("pos") {
|
||||
Some(Tag::IntArray(p)) if p.len() == 3 => Some([p[0], p[1], p[2]]),
|
||||
_ => None,
|
||||
});
|
||||
}
|
||||
read(
|
||||
memories,
|
||||
"minecraft:golem_detected_recently",
|
||||
&mut saved.golem_detected_recently,
|
||||
|v| int(Some(v)).map(|v| v != 0),
|
||||
);
|
||||
read(
|
||||
memories,
|
||||
"minecraft:last_slept",
|
||||
&mut saved.last_slept,
|
||||
|v| int(Some(v)),
|
||||
);
|
||||
read(
|
||||
memories,
|
||||
"minecraft:last_woken",
|
||||
&mut saved.last_woken,
|
||||
|v| int(Some(v)),
|
||||
);
|
||||
let e = ext_mut(m);
|
||||
e.food_level = int(tag.get("FoodLevel")).unwrap_or(0) as i32;
|
||||
e.gossips = tag
|
||||
@@ -868,7 +974,8 @@ pub fn read_nbt(m: &mut Mob, tag: &horizon_world::nbt::Tag) {
|
||||
.map(crate::gossip::Gossips::from_nbt)
|
||||
.unwrap_or_default();
|
||||
e.last_gossip_decay = int(tag.get("LastGossipDecay")).unwrap_or(0);
|
||||
e.last_slept = last_slept;
|
||||
e.last_slept = saved.last_slept.value();
|
||||
e.saved_memories = Some(Box::new(saved));
|
||||
// LivingEntity.readAdditionalSaveData: asleep in the bed again (the
|
||||
// pose; `ai_step` turns it the way the bed faces, or wakes it when the
|
||||
// bed is gone, as `baseTick`'s first tick does).
|
||||
@@ -893,6 +1000,104 @@ pub fn read_nbt(m: &mut Mob, tag: &horizon_world::nbt::Tag) {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn memory_fixture() -> Mob {
|
||||
let mut m = villager();
|
||||
super::super::villager_brain::init(&mut m, 0, 0, 1);
|
||||
let mm = &mut ext_mut(&mut m).brain.as_mut().unwrap().mem;
|
||||
mm.golem_detected_recently.set_ttl(true, 42);
|
||||
mm.last_slept.set_ttl(101, 17);
|
||||
mm.last_woken.set(202);
|
||||
mm.last_worked_at_poi.set_ttl(303, 7);
|
||||
mm.potential_job_site.set_ttl([4, 5, 6], 9);
|
||||
m
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn villager_memory_nbt_shape() {
|
||||
use horizon_world::nbt::Tag;
|
||||
let m = memory_fixture();
|
||||
let mut entries = Vec::new();
|
||||
write_memories(&m, &mut entries);
|
||||
let actual = Tag::compound(entries);
|
||||
let expected = Tag::compound([
|
||||
(
|
||||
"minecraft:golem_detected_recently",
|
||||
Tag::compound([("value", Tag::Byte(1)), ("ttl", Tag::Long(42))]),
|
||||
),
|
||||
(
|
||||
"minecraft:last_slept",
|
||||
Tag::compound([("value", Tag::Long(101)), ("ttl", Tag::Long(17))]),
|
||||
),
|
||||
(
|
||||
"minecraft:last_woken",
|
||||
Tag::compound([("value", Tag::Long(202))]),
|
||||
),
|
||||
(
|
||||
"minecraft:potential_job_site",
|
||||
Tag::compound([
|
||||
(
|
||||
"value",
|
||||
Tag::compound([
|
||||
("dimension", Tag::String("minecraft:overworld".into())),
|
||||
("pos", Tag::IntArray(vec![4, 5, 6])),
|
||||
]),
|
||||
),
|
||||
("ttl", Tag::Long(9)),
|
||||
]),
|
||||
),
|
||||
]);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn villager_memory_round_trip() {
|
||||
use horizon_world::nbt::Tag;
|
||||
let m = memory_fixture();
|
||||
let mut entries = Vec::new();
|
||||
write_memories(&m, &mut entries);
|
||||
entries.push((
|
||||
"minecraft:last_worked_at_poi",
|
||||
Tag::compound([("value", Tag::Long(303)), ("ttl", Tag::Long(7))]),
|
||||
));
|
||||
let tag = Tag::compound([(
|
||||
"Brain",
|
||||
Tag::compound([("memories", Tag::compound(entries))]),
|
||||
)]);
|
||||
let mut loaded = villager();
|
||||
read_nbt(&mut loaded, &tag);
|
||||
super::super::villager_brain::init(&mut loaded, 500, 0, 1);
|
||||
let mm = &mut ext_mut(&mut loaded).brain.as_mut().unwrap().mem;
|
||||
assert_eq!(
|
||||
(
|
||||
mm.golem_detected_recently.value(),
|
||||
mm.golem_detected_recently.ttl()
|
||||
),
|
||||
(Some(true), 42)
|
||||
);
|
||||
assert_eq!(
|
||||
(mm.last_slept.value(), mm.last_slept.ttl()),
|
||||
(Some(101), 17)
|
||||
);
|
||||
assert_eq!(
|
||||
(mm.last_woken.value(), mm.last_woken.ttl()),
|
||||
(Some(202), i64::MAX)
|
||||
);
|
||||
assert_eq!(
|
||||
(mm.last_worked_at_poi.value(), mm.last_worked_at_poi.ttl()),
|
||||
(None, i64::MAX)
|
||||
);
|
||||
assert_eq!(
|
||||
(mm.potential_job_site.value(), mm.potential_job_site.ttl()),
|
||||
(Some([4, 5, 6]), 9)
|
||||
);
|
||||
for _ in 0..42 {
|
||||
mm.golem_detected_recently.tick();
|
||||
}
|
||||
assert!(mm.golem_detected_recently.present());
|
||||
mm.golem_detected_recently.tick();
|
||||
assert!(!mm.golem_detected_recently.present());
|
||||
}
|
||||
|
||||
fn villager() -> Mob {
|
||||
use crate::mobs::{Anim, Brain, Flags, MobType, Part, Species};
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -435,12 +435,15 @@ fn make_brain(m: &mut Mob, packed: Memories, shuffle_seed: i64, w_times: (i64, i
|
||||
/// Builds the villager's brain as its constructor does, at this game time:
|
||||
/// the memories it remembers (job site, bed, bell) come from the mob.
|
||||
pub fn init(m: &mut Mob, game_time: i64, day_time: i64, shuffle_seed: i64) {
|
||||
let mut packed = Memories::default();
|
||||
packed.job_site.set_opt(m.extra.home);
|
||||
packed.home.set_opt(m.extra.bed);
|
||||
packed.meeting_point.set_opt(m.extra.meeting);
|
||||
if let Some(t) = super::villager::ext(m).and_then(|e| e.last_slept) {
|
||||
packed.last_slept.set(t);
|
||||
let saved = super::villager::ext_mut(m).saved_memories.take();
|
||||
let mut packed = saved.as_deref().cloned().unwrap_or_default();
|
||||
if saved.is_none() {
|
||||
packed.job_site.set_opt(m.extra.home);
|
||||
packed.home.set_opt(m.extra.bed);
|
||||
packed.meeting_point.set_opt(m.extra.meeting);
|
||||
if let Some(t) = super::villager::ext(m).and_then(|e| e.last_slept) {
|
||||
packed.last_slept.set(t);
|
||||
}
|
||||
}
|
||||
let b = make_brain(m, packed, shuffle_seed, (game_time, day_time));
|
||||
super::villager::ext_mut(m).brain = Some(Box::new(b));
|
||||
|
||||
Reference in new issue
Block a user