Posted April 5, 201312 yr Minecraft NPC Villages Explained (Or, Why Are My Villagers Clumping Together?) Disclaimers: This explanation is current as of April 4, 2013, and pertains to vanilla Minecraft(no mods affecting the files mentioned within.) The names of all class files mentioned are current MCP mappings, and all code will be pseudo. This part only covers village expansion, more can be explained if people ask. Introduction So, one day I decided that I wanted to build a massive city, and populate it with NPC villagers. But with my villagers sticking together in one building like rare-earth magnets, and ignoring the gorgeous mansions that were waiting across the street, I knew that something was wrong. I began perusing forum posts for answers. Most of the results I found mentioned burying villagers alive, locking them up, or using various things to attract them. (Suggesting that they may like to gather around pumpkins, animals, prisoners, etc.) Others who understood more of how the invisible radius of a village works had better ideas, but there was a lot of micro-managing that did not account for some of the ways the code actually works. So, I began reading the Java code for my answers. (Thank you, MCP) The Explanation Everything starts in VillageCollection.java This class keeps track of the existing villages, and the positions of each villager in a list named villagerPositionsList. This list is mostly useless. In the class's tick() function, it iterates through each village that exists, calling their respective tick() functions, which call their Villagers' updateAITick() functions. VillageCollection then calls its function dropOldestVillagerPosition(), which removes the villager position from the top of the list. This is probably the only important use of this list. The oldest villager position is passed to VillageCollection.addUnassignedWoodenDoorsAroundToNewDoorsList() I should mention that each Village object keeps track of the wooden doors that belong to it, a list of VillageDoorInfo objects named villageDoorInfoList. The function VillageCollection.addUnassignedWoodenDoorsAroundToNewDoorsList() checks a list named newDoors and adds any *new* wooden doors to it that are within 16 blocks horizontally and 4 blocks vertically of the oldest villager position. Doors that already exist in a Village's villageDoorInfoList have their internal timer reset. This will be explained later. But, doors are only added to newDoors if they are "valid." addUnassignedWoodenDoorsAroundToNewDoorsList() calls VillageCollection.addDoorToNewListIfAppropriate(), which looks at the 5 blocks on either side of the door's base. It counts the number of "outside" blocks, that being blocks which the sun can shine on during the day. A door is "valid" if the number of outside blocks on one side *does not equal* the number on the other side. Returning from VillageCollection.dropOldestVillagerPosition(), the next step in tick() is to call VillageCollection.addNewDoorsToVillageOrCreateVillage(). If any doors in newDoors are close enough to an existing Village(within the Village's radius plus 32, squared), then that Village gets the door. Otherwise, a new Village is created and that door is given to it to handle. That sums up most of what happens inside of VillageCollection.java //within VillageCollection function tick { foreach Village, call Village.tick() call dropOldestVillagerPosition() call addNewDoorsToVillageOrCreateVillage() } function dropOldestVillagerPosition { pos = villagerPositionList.pop() call addUnassignedWoodenDoorsAroundToNewDoorsList(pos) } function addUnassignedWoodenDoorsAroundToNewDoorsList(pos) { foreach door within 16 of pos.x and 16 of pos.z and 4 of pos.y { if door is in a village then door.timer = 0 else call addDoorToNewListIfAppropriate(door) } } function addDoorToNewListIfAppropriate(door) { put 5 "outside" blocks on one side of door into list1 put 5 "outside" blocks on other side of door into list2 if list1.length is not equal to list2.length call newDoors.add(door) } function addNewDoorsToVillageOrCreateVillage { foreach door in newDoors { find a village where (distance between village center and the door)^2 < (32 + village radius)^2 if village found call village.addDoorInfo(door) else create new village.addDoorInfo(door) } } Now I will explain the relevant code in EntityVillager.java Most of the Villager's actions spawn from the function EntityVillager.updateAITick() It updates its position to VillageCollection's villagerPositionList, and sets its home to the nearest village. If no village is found, the Villager will wander until it finds one. The Villager's wander area is centered on the Village's center, with a radius that is 60% of the Village's radius. //within EntityVillager constructor EntityVillager { set wander area to home village with radius of village.radius * 0.6 } function tick() { set home village to closest village do villager stuff } And then onto Village.java Every time a Village's tick() function is called, it takes care of all sorts of code pertaining to population count and iron golems. It also calls Village.removeDeadAndOutOfRangeDoors(), which removes door info from villageDoorInfoList for any door that does not exist, or any door whose internal timer has passed 1200 ticks. The function then calls Village.updateVillageRadiusAndCenter(), which is also called when a new VillageDoorInfo is added to villageDoorInfoList. This method recalculates the Village's center by getting the mean of all the doors' positions. Then, it sets the Village radius to the distance between the center and the most distant door(unless it is within 32 blocks, 32 is the minimum radius). //within Village.java function tick { call removeDeadAndOutOfRangeDoors() } function removeDeadAndOutOfRangeDoors { foreach door in villageDoorInfoList if block at door is not a door OR if door.timer > 12000 then remove door } That is how the code works. Why I Call This Code "Broken" The code I have examined is meant to let the radius of a Village grow organically, as explained above. But, as I mentioned in the Introduction, this doesn't work. Village radius is based on the location of the furthest door in the Village, and doors are added by chance when a Villager is near them. But because the doors' internal timers expire before being noticed by passing Villagers, the radius shrinks. And Villagers prefer to stay within 60% of the Village's radius to its center, so they rarely see distant doors. I've been lucky enough to have a village of radius 35 (minimum is 32), even with lots of micro-managing. Hope I helped someone, please tell me how I can improve this post! Yes, my ideas can be naive. But I'm hoping that speaking my mind will inspire those who know what they are doing!
April 5, 201312 yr Author I was hoping that this thread would garner more attention seeing as how long it took me to put together, is there a better place to post/pin this? Yes, my ideas can be naive. But I'm hoping that speaking my mind will inspire those who know what they are doing!
April 6, 201312 yr I was wondering about creating a wiki page for it on the wiki, seeing as it's a good reference post I really liked it at least, thanks for creating it! <3 If you guys dont get it.. then well ya.. try harder...
April 6, 201312 yr Author Awh thanks. Which wiki? I think that it could use some refining/feedback before I wiki it Yes, my ideas can be naive. But I'm hoping that speaking my mind will inspire those who know what they are doing!
April 6, 201312 yr This post is very helpful, it will be good to keep in mind, especially the door timer part... I may have to adjust some things to deal with that. Thanks!
April 6, 201312 yr Author for sure, all feedback appreciated! Yes, my ideas can be naive. But I'm hoping that speaking my mind will inspire those who know what they are doing!
April 15, 201312 yr Author Any ideas on where I should save this? Yes, my ideas can be naive. But I'm hoping that speaking my mind will inspire those who know what they are doing!
April 15, 201312 yr I'd still say the forge wiki would be a good place If you guys dont get it.. then well ya.. try harder...
April 22, 201312 yr Author I haven't used a wiki in a while so any help would be appreciated. Yes, my ideas can be naive. But I'm hoping that speaking my mind will inspire those who know what they are doing!
April 22, 201312 yr Author I haven't used a wiki in a while so any help would be appreciated. Yes, my ideas can be naive. But I'm hoping that speaking my mind will inspire those who know what they are doing!
April 3, 201411 yr I've started playing Minecraft recently and I run into this issue. The first google search sent me straight here but I see that this topic is a year old so I was wondering if someone found a workaround for this. I found the issue in the issue tracker: https://bugs.mojang.com/browse/MC-78 Will post a link back to here from there. This is a very good analisys of the problem. Thanks for the explanation.
April 3, 201411 yr I've started playing Minecraft recently and I run into this issue. The first google search sent me straight here but I see that this topic is a year old so I was wondering if someone found a workaround for this. I found the issue in the issue tracker: https://bugs.mojang.com/browse/MC-78 Will post a link back to here from there. This is a very good analisys of the problem. Thanks for the explanation. I'm not sure about the specific fix to the clumping problem, but I think it may be possible to override the vanilla villager AI with your own. Every EntityLiving has a public list of AI tasks called "tasks". I haven't tried it, but I suspect you could clear the vanilla EntityVillager AI tasks with a new EntityAITasks (or maybe just clear the taskEntries list within the tasks) and then build up a list of custom AI using the addTask method. Check out my tutorials here: http://jabelarminecraft.blogspot.com/
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.