Skip to main content Skip to table of contents
Repair Villager

The Repair Villager #

Updated 2023-02-19

Yes, I’m old enough to remember when mending didn’t exist, but you could keep repairing your favorite pickaxe on an anvil forever without having to worry about the prior work penalty. This data pack doesn’t restore the old behavior, but it does allow you to summon a custom villager who can repair your items for a fixed fee. It’s a data pack, not a mod, so you can include it in any world.

Download #

If you want to use the data pack in your own world, you can find specifics on how to install and use it along with downloads on the github page. Remember that data packs are only compatible with the Java Edition of Minecraft.

Behind the Scenes #

I am not going to go line-by-line into how the data pack functions work, but I want to highlight a few challenges that guided the design process as well as some common challenges when trying to do complicated things with Minecraft commands. I’ll end with some philosophy on why I built this as a data pack and not a mod.

The Problem #

The simpler solution would simply have been to remove the prior work penalty. That would restore the repair mechanic to how it was pre-1.8 (mostly). If the player drops an item on the ground, I can easily modify its data and remove the prior work, but the data for items held by the player are stored inside the player’s data. That whole data structure in read-only to commands and there’s no way I know of to sudo my way past the error that gets returned if I try to modify anything the player is holding.

So I needed a natural way of getting the item out of the player’s inventory so I could modify it. The villager provides that. As long as I was giving an item to a villager, it seemed natural to skip the prior work and just repair the item for a fee. That also means I can’t use the mechanic to stack more enchantments than would normally be possible. Repairing the item using the villager leaves its prior work intact, so if it has become too expensive to add additional enchantments to the item, the repair villager can’t help with that.

All I need is a quick data modify storage repairvillager:data repairItem.sell.tag.Damage set value 0 and I’m done. It’s helpful that Minecraft tracks damage—not health—for items. If I want to make an item be almost broken, I need to set the damage value to different values depending on the item type and material. But if I want the item to be fully repaired, just set the damage to 0.

Hold my Pickaxe #

I skipped at least a dozen steps there. How did this item’s data get into repairvillager:data and what does this have to do with villagers? I’m moving data around using the /data command, which can move and change data between three sources: entities, blocks (such as chests), and storage. Storage is a magical space for data pack creators where I can put whatever data I want and never have to worry about the game messing with it.

The basic process looks like this: Find items on the ground around the repair villager. For each item that can be repaired, copy its data to storage. Add price info depending on what type of item it is. kill the item on the ground so the player can’t pick it up. Copy the item’s data from storage to the villager by appending it to the villager’s list of trades.

The reason I use storage instead of copying directly to the villager is that once I use append, I lose track of it. I know it’s at the end of the array, but I can’t use Offers.Count - 1 because Minecraft commands don’t do that. The only way I can index into an array is using constant values. And -1 for the end of the array doesn’t work in Minecraft. I don’t know how many items this villager is already holding, so I can’t append the data until I’m done modifying it.

Speaking of setting the price based on the type of item, I need a switch statement for that.

  • Minecraft commands don’t do switch statements.

Alright, then I guess I’m doing serial else-ifs.

  • Minecraft commands also don’t do if statements.

So…

Conditional Execute #

My other favorite command is /execute. It can be prepended to any other command to change how that command runs. For example, it can change where in the world a command occurs, what entity is running the command, and similar. It can also skip running a command unless certain conditions are met, using execute if or execute unless. That sounds useful here, but it doesn’t quite work. There is an execute if data syntax, but it only checks if the data exists, not what its value is. I’ll need to find something different.

The execute as <selector> syntax allows me to pretend a different entity is executing the command. If multiple entities are selected, the command will run multiple times. If the selector doesn’t find any entities, the command won’t run at all.

The @s selector returns only the entity that is running the execute command. That’s not useful on its own, but any selector can use filters to select sub-sets. That effectively creates AND logic. I can use @s[nbt={Item:{id:"minecraft:diamond_pickaxe"}}] to select only entities that are the one executing this command AND are diamond pickaxes. If this item is not a diamond pickaxe, then the selector will return an empty set, and the rest of the execute command won’t run.

The finished command uses the conditional logic to set the price for repairing a diamond pickaxe only if the item is actually diamond pickaxe. Then I just have to copy+paste this for every other item I want the villager to be able to repair. Tedious, but doable.

Cleaning Up #

Eventually, the player will walk away after buying back their repaired items. Now I have a villager with some useless trades that need to be removed. But do you remember how I can’t use variables to index into arrays? That’s still a problem. I also don’t have any kind of loop command.

It turns out, if you have recursion, you have loops. And Minecraft function files can call themselves. We’ll be opening a lot of stack frames here, but I trust Minecraft can handle it.

So, copy all the trade data from the villager to a temporary place in storage. That’s one data command, easy. Delete all the villager’s trades. That’s another data command, easy.

Now we need a new function that copies a single item back into the villager’s inventory ONLY if it hasn’t been bought and deletes it from storage in either case. The function ends by calling itself with another conditional execute command that runs only if there is still data in storage. Call the new function once and let the rest happen on its own. Congratulations, that’s a recursive foreach loop.

Redundant Data Desyncs #

What if the cleanup happens while the player still has the trade screen open?

Everything I am doing here is back-end data manipulation. I don’t have direct control over what menus appear on screen and I have to deal with the fact that Minecraft doesn’t expect data to change unless Minecraft itself is doing it. If I add a trade while the menu is open, it won’t appear for the player until they close and reopen the menu. Annoying, but it fails safe.

But if I remove an item in the middle of the array and then the player buys an item from near the end of the array, then Minecraft is going to modify the remaining quantity data for the wrong item.

I need to be able to forcefully close the menu whenever I clean the trades. There is no official way to do this.

Alright, I’ll teleport the villager very far away for one tick and then teleport them back. Crude, but it should work. There must be a limit to how far away the villager can be while keeping the trade window open.

  • Nope, there’s no distance limit.

Alright, I’ll teleport the villager to the nether and back again. Surely if the villager isn’t even in the same dimension, Minecraft will take the hint.

  • Nope, Minecraft does not care about trans-dimensional trading.

Desperate times. I’ll teleport the villager into an unloaded chunk. I can reload it with /forceload on the next tick and bring it back.

  • Nope, it turns out the player can trade with unloaded villagers. It doesn’t even desync the data. Minecraft is very proud of itself for being able to pull that off.

Damage the player? Nope. Damage the villager? Nope. Overwrite all the trade data with an empty set for one frame? I didn’t expect that one to work, but I was getting desperate.

Please, Minecraft, don’t make me kill and respawn this villager.

There is a solution. Villagers can naturally lose their profession (resetting all their trades) if their job block is broken. It’s possible a player will have the menu open when that happens. The devs at Mojang knew this, so the game does track the villager’s profession and closes the menu if it changes. I can change the profession with data, so I can use that to force the menu to close.

I can clear the villager’s profession for one tick and then restore it. A player who is looking in the right direction at the right time might notice the villager’s clothes flicker, but I can live with that.

Almost there, but there’s still a problem.

Edge Cases and Data Validation #

If there’s a valid job block nearby, Minecraft sometimes decides to assign a new profession to the villager before I can restore it. That doesn’t stop me from closing the menu, but it does overwrite all the trade data for the villager. No worries, I can copy the data to somewhere safe, like storage.

Be careful of edge cases. There might be multiple repair villagers in the same world and my code might try to close two menus at the same time. It was safe to use static locations in storage before because every function that used storage freed the storage before ending, so it was easy to guarantee that two functions couldn’t try to use the same storage space at the same time. Now I need to transfer data across game ticks, so it won’t be as easy. I could create an array in storage and append the data I need into it, but then I have to keep track of which data should go back to which villager. I can immediately think of two complicated ways to solve this, but there’s a simpler solution.

As long as I’m moving data around, why not just move it somewhere the game isn’t using inside the villager’s data? The list of trades the villager offers is stored in a tag called “offers” in the villager’s data. Why don’t I just copy it to a new tag called “offersbackup” so I can copy it back in case the original gets overwritten.

Unfortunately, the game knows what tags are supposed to be present for each entity type. Any command that tries to write tags that aren’t in the spec will fail silently. This can’t happen in storage, but the moment I try to manipulate data attached to entities, I have to follow the expected format.

Villager Armor and the Tag Tag #

There’s an exception. The item data structure contains the tag tag. This tag can contain several different arbitrary types of data and Minecraft doesn’t validate anything being written there.

That sounds promising, but items getting overwritten was already a problem. Where am I going to find an item on this villager that I can dump arbitrary data into safely?

All mobs (including villagers) have armor slots, even if they don’t use them. It’s just a thing in the mob class everything inherits from. Minecraft doesn’t stop me from putting things there even if the mob receiving the armor can’t normally wear armor. Minecraft also doesn’t check that the items I’m putting there are actually armor.

So,

data modify entity @s ArmorItems[0] set value {id:"minecraft:emerald", Count:1b, tag:{}}

This villager is now wearing an emerald as a helmet. The fact that it’s an emerald doesn’t mean anything. It could have been anything. I just thought an emerald was poetic. And there at the end is the all-important tag tag. So we continue.

data modify entity @s ArmorItems[0].tag.Offers set from entity @s Offers

The emerald helmet’s tag tag now contains a copy of all the villager’s trades. Even if Minecraft overwrites that data during the next tick, I can restore it from the emerald.

And it works! GG. It took a lot of steps, but I now have a villager who can pick up items, repair them, assign unique prices to them, sell them back to the player, and tidy up the menu afterwards. No mods needed, just a folder full of command functions.

Why a Data Pack? #

I could have coded all of this in Java and released it as a mod. That probably would have been easier, at least for someone who knows Java. Given all the years I’ve spent playing modded Minecraft and even configuring my own mod packs, I’m honestly surprised I never learned Java to start making my own mods.

The boring answer is that I like making things that work in vanilla Minecraft. It means that I can update my game without having to wait for modding frameworks to update. I can even play development snapshots if I want and this data pack will work until Mojang changes one of the commands I’m using. There are exceptions, but Mojang usually adds new features to commands without breaking old uses. So data packs can be longer-lived and have fewer dependencies.

There’s another reason I enjoy it. It reminds me of what I liked about learning to build with redstone in survival. There are mods that add the redstone equivalent of integrated circuits. There are single components that are xor gates, nor latches, monostables, clocks, and more. These are more efficient, but I enjoy building things out of simple redstone components. Trying to make complicated circuits efficiently often requires doing things with simple components that they weren’t meant to do. Understanding what each component is meant to do is simple, but each component’s behavior becomes very complicated when you consider how it interacts with every other possible component. Tutorials often answer the question, what is it for? but I enjoyed learning to ask the question, what can it do?

I never followed a tutorial to learn how to do recursive for-loops. No one ever told me how to store arbitrary data in a villager with the tag tag. The documentation for the execute command includes a lot of info on possible uses, including if-then logic, but it doesn’t spell out how to use @s with filters to ask questions about the entity performing the command. I also did some ray tracing nonsense using a recursive execute that I decided not to discuss here; that’s definitely not in the documentation.

I figured things out by asking what each command can do. I could have given up when I realized there wasn’t a command called closeMenu, but I learned to ask the question, what other tools do I have that can do this? and I enjoy realizing there’s an answer. There’s always some of this type of thinking in coding (at least for me), but the simplicity and limitations of Minecraft commands force that kind of creativity a lot. Doing things with data packs that were never meant to work is its own reward to me.