From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: MrHua269 Date: Thu, 9 Jul 2026 12:13:58 +0800 Subject: [PATCH] Purpur: Lobotomize stuck villagers Co-authored by: William Blake Galbreath As part of: Purpur (https://github.com/PurpurMC/Purpur/blob/09f547de09fc5d886f18f6d99ff389289766ec9d/purpur-server/minecraft-patches/features/0001-Ridables.patch) Licensed under: MIT (https://github.com/PurpurMC/Purpur/blob/09f547de09fc5d886f18f6d99ff389289766ec9d/LICENSE) diff --git a/net/minecraft/world/entity/npc/villager/Villager.java b/net/minecraft/world/entity/npc/villager/Villager.java index f649c46cc9a34e646f8da3244866c6e011d30aa3..184637ac35a92097391f13bc6355f88495deb483 100644 --- a/net/minecraft/world/entity/npc/villager/Villager.java +++ b/net/minecraft/world/entity/npc/villager/Villager.java @@ -190,6 +190,53 @@ public class Villager extends AbstractVillager implements VillagerDataHolder, Re this.setCanPickUpLoot(true); } + // Purpur start + private boolean isLobotomized = false; public boolean isLobotomized() { return this.isLobotomized; } // Purpur + private int notLobotomizedCount = 0; // Purpur + + private boolean checkLobotomized() { + int interval = io.nanachiyo0721.shiroha.config.modules.optimizations.LobotomizeVillageConfig.villagerLobotomizeCheckInterval; + boolean shouldCheckForTradeLocked = io.nanachiyo0721.shiroha.config.modules.optimizations.LobotomizeVillageConfig.villagerLobotomizeWaitUntilTradeLocked; + if (this.notLobotomizedCount > 3) { + // check half as often if not lobotomized for the last 3+ consecutive checks + interval *= 2; + } + if (this.level().getGameTime() % interval == 0) { + // offset Y for short blocks like dirt_path/farmland + this.isLobotomized = !(shouldCheckForTradeLocked && this.getVillagerXp() == 0) && !canTravelFrom(net.minecraft.core.BlockPos.containing(this.position().x, this.getBoundingBox().minY + 0.0625D, this.position().z)); + + if (this.isLobotomized) { + this.notLobotomizedCount = 0; + } else { + this.notLobotomizedCount++; + } + } + return this.isLobotomized; + } + // Purpur end + + private boolean canTravelFrom(net.minecraft.core.BlockPos pos) { + return canTravelTo(pos.east()) || canTravelTo(pos.west()) || canTravelTo(pos.north()) || canTravelTo(pos.south()); + } + + private boolean canTravelTo(net.minecraft.core.BlockPos pos) { + net.minecraft.world.level.block.state.BlockState state = this.level().getBlockStateIfLoaded(pos); + if (state == null) { + // chunk not loaded + return false; + } + net.minecraft.world.level.block.Block bottom = state.getBlock(); + if (bottom instanceof net.minecraft.world.level.block.FenceBlock || + bottom instanceof net.minecraft.world.level.block.FenceGateBlock || + bottom instanceof net.minecraft.world.level.block.WallBlock) { + // bottom block is too tall to get over + return false; + } + net.minecraft.world.level.block.Block top = level().getBlockState(pos.above()).getBlock(); + // only if both blocks have no collision + return !bottom.hasCollision && !top.hasCollision; + } + @Override public Brain getBrain() { return (Brain)super.getBrain(); @@ -259,11 +306,20 @@ public class Villager extends AbstractVillager implements VillagerDataHolder, Re // Paper start - EAR 2 this.customServerAiStep(level, false); } - protected void customServerAiStep(ServerLevel level, final boolean inactive) { + protected void customServerAiStep(ServerLevel level, boolean inactive) { // Purpur - not final // Paper end - EAR 2 ProfilerFiller profiler = Profiler.get(); profiler.push("villagerBrain"); + // Purpur start + if (io.nanachiyo0721.shiroha.config.modules.optimizations.LobotomizeVillageConfig.villagerLobotomizeEnabled) { + // treat as inactive if lobotomized + inactive = inactive || checkLobotomized(); + } else { + this.isLobotomized = false; + } + // Purpur end if (!inactive) this.getBrain().tick(level, this); // Paper - EAR 2 + else if (this.isLobotomized && shouldRestock(level)) restock(); // Purpur - Lobotomize stuck villagers profiler.pop(); if (this.assignProfessionWhenSpawned) { this.assignProfessionWhenSpawned = false;