Question How do you set a villager's profession and job_block?

Suamere

New member
Mar 25, 2024
4
0
1
In a villager's NBT, they have Brain > memories > "minecraft:job_site"

They also have VillagerData > profession

I have attempted to call the Bukkit Villager setProfession(Profession.FARMER), as well as the NMS Villager to getVillagerData(), setProfession(VillagerProfession.FARMER), and then put it back with setVillagerData(villagerData).

Additionally, I also call brain.setMemory(JOB_SITE, BlockPos(...)) for the block position. Part of my code also has them walk to the block.

Java:
var nmsVillager = ((CraftVillager)bukkitVillager).getHandle();
nmsVillager.getNavigation().moveTo(block.getX(), block.getY(), block.getZ(), 1);

bukkitVillager.setProfession(Profession.FARMER);

Brain<Villager> villagerBrain = nmsVillager.getBrain();
VillagerData villagerData = nmsVillager.getVillagerData();
villagerData.setProfession(VillagerProfession.FARMER);
nmsVillager.setVillagerData(villagerData);

villagerBrain.setMemory(MemoryModuleType.JOB_SITE, Optional.of(GlobalPos.of(Level.OVERWORLD, new BlockPos(block.getX(), block.getY(), block.getZ()))));

The villager walks to the block, which tells me I have the right handle and my NMS is configured correctly.

But what happens is this: If the villager already has a profession, this code just strips them, making them have NONE as their profession. Of course they walk toward the block, but then they do nothing.

Best case scenario, they find whatever nearby random profession block and go take that random profession.

Their job_block NBT data seems to get updated (I forget what iteration I last confirmed that). But given that their profession is "NONE", they just look like a normal (not nitwit) villager, and their NBT shows "NONE" as their profession.

How do I set the villager's profession and specific job block?
 

Suamere

New member
Mar 25, 2024
4
0
1
Well, one "fix" is that I have added
Code:
nmsVillager.setVillagerXp(1);
. Turns out they WERE changing their profession, but for one cpu cycle (maybe tick?).

But as for setting the memory (job_site), I still have an issue.

Bukkit's Villager object has setMemory(MemoryKey.JOB_SITE, blockLocation). But the tooltip says Note that the value will not be persisted when the specific entity does not have that value by default.

And similarly, the NMS VillagerData has setMemory(MemoryModuleType.JOB_SITE, Optional of GlobalPos). And its first line of code is if (this.memories.containsKey(type)) {

This essentially means they can't set a memory that doesn't exist. And I don't see an addMemory ability. All of this is somewhat speculation, because Villagers frequently DO have a Brain > memories > job_site memory, though villagers with NONE as their job obviously do not.

Thoughts on how to set the job_site block for a villager?
 

Suamere

New member
Mar 25, 2024
4
0
1
After a couple days of trying, it seems that something internal is being cached. I either don't know how to fix it, or don't know how to trigger NMS to recognize a change.

In addition to my last two posts, I have also found that there is a MemoryKey called POTENTIAL_JOB_SITE. When you place a job block and see a villager suddenly start running toward it, that is the POI memory that is set, until they reach it and call dibs on it. Then it switches to JOB_SITE of course.

Within my server code, I can setMemory appropriately, and then getMemory and see that it exists. However, if I call the code again, and check getMemory for that POI, it is not there, so it is not persisting beyond the contextual instance I get ahold of during each event. Either that, or right after my event the AI instantly rejects or removes what I've set.

However, if I let a villager naturally pick up a profession from a block, say they become a FARMER from a COMPOSTER; Then if I run my code by pushing them toward another job, say toward a SMITHING_TABLE to become a TOOLSMITH... their profession changes, but if I place a Composter next to them, they walk to it and set it as their job_block, but remain a toolsmith. Lol.

If I DON'T place new composters around, then they become their Toolsmith and just never pick up any job block, though there are Composters and other job blocks around them. They just ignore them.

However, if I then set the same villager back to a FARMER by pointing at any composter, they absolutely find a nearby composter (not necessarily the one I try to assign to them) and they pick it up as their job_block, as if they've been yearning for it. Lol.

This tells me that something internal is being cached, packets not being sent, or something else weird is not happening whenever a villager's profession (or JOB_BLOCK memory) are set.

Still hoping for any help. Perhaps a pre-existing GOAL, or a way to write a goal... or a way to urge the AI to naturally take notice of a specified job block and use the natural AI to set it as a POTENTIAL. plzhlplol
 

Suamere

New member
Mar 25, 2024
4
0
1
Okay, here is what I have, cleaned up a bit. Essentially I just called the nms setProfession and setVillagerData. But then I issued a dispatchCommand to set the job_site, which I hear is a bad idea, but seems to be the only thing that works right now. I'm open to any ideas of cleanup or changes:

Java:
    public static void TrySetProfession(Villager villager, Block block) {
        setProfession(((CraftVillager)villager).getHandle(), block);
    }

    public  static void setProfession(net.minecraft.world.entity.npc.Villager nmsVillager, Block jobSite) {
        nmsVillager.setVillagerData(nmsVillager.getVillagerData().setProfession(ProfessionFromSite(jobSite)));
        setJobSite(nmsVillager.getUUID(), jobSite);
    }

    public  static void setJobSite(UUID uuid, Block jobSite) {
        String str = "data merge entity @e[nbt={UUID:" + uuidToNBT(uuid) + "},limit=1] {Brain:{memories:{\"minecraft:job_site\":{value:{pos:[I;" + jobSite.getX() + "," + jobSite.getY() + "," + jobSite.getZ() + "],dimension:\"minecraft:overworld\"}}}}}";
        Bukkit.dispatchCommand(Bukkit.getConsoleSender(), str);
    }

    public static String uuidToNBT(UUID uuid) {
        long mostSigBits = uuid.getMostSignificantBits();
        long leastSigBits = uuid.getLeastSignificantBits();
        return "[I;" + (int)(mostSigBits >> 32) + "," + (int)mostSigBits + "," + (int)(leastSigBits >> 32) + "," + (int)leastSigBits + "]";
    }

    public  static VillagerProfession ProfessionFromSite(Block jobSite) {
        return switch(jobSite.getType()) {
            case COMPOSTER -> VillagerProfession.FARMER;
            case SMITHING_TABLE -> VillagerProfession.TOOLSMITH;
            case BARREL -> VillagerProfession.FISHERMAN;
            default -> null;
        };
    }