Skip to main content Skip to table of contents
Wool Portals

Wool Linking Portals #

Written 2023-06-03

This isn’t a new idea. There are multiple similar tools that exist in the Minecraft modding scene and I’ve built mod-packs using at least one of them in the past. But I wanted to see if I could create it in vanilla using only data packs.

Linking portals don’t allow the player to move to places they don’t already have access to the way nether portals and end portals do. Instead, they allow the player to establish a quickly mode of travel between two places they already have access to. By using colored wool for the portals, sixteen different pairs of linking portals can be constructed in each world.

Download #

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

Behind the Scenes #

This is going to be about both the Wool Portals data pack and the Teleport Scoreboard data pack it depends on. I built the Teleport Scoreboard data pack as a separate data pack (basically, a library) so I’d have the option of using it with other projects in the future. It allows an entity to be teleported to any location by setting three scoreboard values to the destination coordinates (plus one extra to specify the destination dimension). That does nothing by itself because a typical player doesn’t have the ability to set the required scoreboard values or trigger the teleportation. The Wool Portals pack gives players a way to move around the world using that functionality.

But before I could start work on the wool portals, I needed to get the teleportation method working. That was the main challenge.

The Problem #

How do I teleport the player to an arbitrary location? There are two requirements that make this difficult. First, the destination might not be loaded. Second, I don’t know the location when coding the data pack.

If I want to teleport the player to a specific place, it is easy. The following command gets the job done and doesn’t need the destination to be loaded in advance.

teleport @p 42 64 100

This teleports the player to the provided coordinates. But that’s the problem. The coordinates must be provided when the command is written. In a typical programming language, I could do something like the following.

int x = 42
int y = 64
int z = 100
teleport @p x y z

Then I could do whatever math I want on x, y, and z before passing them to the teleport command.

This doesn’t work in Minecraft. Minecraft functions have no concept of variables. If a command needs a number as an input, that number must be know at design time. This works if I know I want to send a player to a specific location. But if I want the player to be able to place portals wherever they want, this will not work.

There’s another way to use the teleport command. Instead of sending the player to a specific set of coordinates, I can teleport the player to the location of a known entity. Assuming I’ve given the “portal” tag to that entity, I can use the following.

teleport @p @e[tag=portal, limit=1]

With this, I can place an invisible entity at the portal’s location when the portal is created. I give the marker the “portal” tag. Then I can teleport the player to that entity any time I want, and I don’t need to know in advance where the portal marker will be. It’s so close to being a solution, but there’s a problem.

Entities, including markers, unload with the world chunk when you get too far away from them. The target selector I used above can only find the portal marker if it is in a loaded chunk. That is a problem.

If I teleport the player to a set of coordinates, I need to know the coordinates in advance. If I teleport the player to an entity, that entity must be loaded. The first option is obviously impossible. The second option is problematic because I need a way to keep the portal marker loaded at all times.

The Old Solution #

forceload add -16 -16 16 16

Simple enough. The forceload command protects a specified chunk of the world so it always remains loaded, no matter how far away the player is. If I just forceload all the chunks where my portals are, it should work.

This has the same problem where I need to enter the coordinates to forceload in advance. This can be solved because I know the chunk will be naturally loaded at the moment when the portal is created (because the player has to be standing right there placing the blocks). So I can forceload the chunk at that moment using the portal marker. The code is a little messy, but it works.

execute at @e[tag=portal, limit=1] run forceload add ~0 ~0 ~0 ~0

My old friend execute returns to save the day. Since I have a loaded entity there, I can use execute at with a target selector to move the command’s execution point to the portal’s location. Then I can call forceload at that point using Minecraft’s tilde notation, which sets the location relative to the point of execution. After that is done, the chunk and the portal marker will always remain loaded. Afterwards, that entity will always be available when I need to teleport the player.

That seems like a perfectly usable solution. So what’s the problem?

First problem, this could result in a lot of extra chunks being loaded. With sixteen wool colors and each portal needing two ends, that’s thirty-two chunks that could be loaded at all times. Thirty-two is probably not terrible, but I don’t want to write code that requires an expensive computer to run. The player could also be using other data packs and some of them might be loading other chunks. eventually it will add up. I’d prefer not to use forceload for this reason.

Second problem, this could break compatibility with other data packs. What if the player is using other data packs that are also trying to use forceload? If the player decides to move a portal, then I need to release the old chunk from being loaded. The forceload command has a syntax to release chunks, but I have no way of knowing if another data pack also had a reason for wanting that chunk loaded.

The forceload command doesn’t track which data pack requested that a chunk be loaded. I can imagine redesigning the forceload command to allow labeling of force-loaded regions with syntax to release them using the labels. Any remaining labels affecting the same chunks would remain in effect. The chunk would only be released when all of the data packs agreed to release it. But that’s just a fantasy. The command that exists in the game at this time doesn’t allow that.

There are workarounds. I’ve used some in the past to allow compatibility between my own data packs that were using forceload. But that wouldn’t allow compatibility with anyone else’s data packs unless they were using an identical workaround.

Over time, I’ve grown weary of using forceload. There’s a fundamental incompatibility between any two data packs that use it and there’s no elegant solution I can trust other data pack makers would be using. I just can’t trust that some other data pack won’t unload one of the chunks I need. I could just accept that my data packs won’t be compatible with other people’s, but I don’t want to do that and I can’t expect normal users to be able to evaluate whether two data packs will conflict. So I’ve searched for other solutions.

Puzzle Pieces #

Let’s think back to the original problem.

If I teleport the player to a set of coordinates, I need to know the coordinates in advance. If I teleport the player to an entity, that entity must be loaded.

I’m choosing to rule out keeping an entity loaded. So let’s reexamine the first part. Do I need to know the coordinates in advance? I had several parts of a possible solution in my mind for over a year, but never worked on them because it seemed too daunting and I had other priorities. Finally, I decided to work on it.

I can do a relative teleport using tilde notation like this: teleport @p ~0 ~0 ~0. That teleports the player to the coordinates the command was execute at. If the player executes the command, then that’s the player’s location. If the command is run by a data pack then it gets run from the world’s origin by default.

Again, execute might save the day. I can offset where a command is run from using the following.

execute positioned ~8 ~0 ~0 run teleport @p ~0 ~0 ~0

If the player runs that command, it will teleport them eight blocks along the positive x axis. The teleport command is just doing a relative teleport to its own position, but I’ve offset the command’s location using execute positioned. In this way, commands can be moved around. Importantly, commands can be run from unloaded positions and they’ll still work. If you try to change blocks in unloaded chunks, the command will fail, but it does still run.

Offsetting commands is interesting, but it doesn’t help on its own. The offsets still have to be determined when the command is written.

But my friend execute has many ways of having a command run only in specific situations. Here’s the basic form I had in my head:

execute if something positioned ~0 ~0 ~0 run function some_function
execute unless something positioned ~1 ~0 ~0 run function some_function

The function some_function gets run exactly once, but it could get run in either of two locations, based on…something. What if I chain these together? I can create a chain of functions, each one conditionally calling the next in either of two locations. The first one conditionally offsets the second by 1 block. The second one offsets the third by 2 blocks. Then 4, then 8, then 16, etc. The final command in the chain finally performs the teleport.

For anyone unfamiliar with binary counting, the numbers above were not chosen randomly. This allows me to select any offset I want. Do you want a 13 block offset? Offset by 8, 4, and 1 while skipping the other offsets. Each command I add to the chain doubles the range that can be accessed.

Alright, that’s two pieces of the puzzle. I can offset commands and I can do it conditionally. But I still need a way for my data pack to figure out which offsets need to be skipped as its running.

I knew Minecraft could do math using the scoreboard for a long time, but never used it much. The scoreboard in Minecraft is best understood as a tool for mini-game makers to keep track of players’ scores. But it’s more powerful than that. Scores can be assigned to any entity or even to arbitrary names that don’t belong to any player or entity. With commands, it’s possible to set a player’s score, increase it by a specified amount, or do various kinds of math using another player’s score. I remembered enough of the documentation to know that it could do integer division and modulo.

I need to disclose at this point that I love math, but I’m aware that most people don’t. I’m going to skip the juicy details of the math because I assume most people either don’t want to read about math or already know why integer division and modulo are a powerful combination for these kinds of problems. Here’s the short version. With Fancy Math™, I can calculate whether a given number like 42 would need an offset of 1 (it doesn’t), 2 (it does), 4 (it doesn’t), and so on. For 42, the offsets it needs are 32, 8, and 2. All others can be skipped.

With those three puzzle pieces, the problem seems solvable. I can offset a command. I can chain together a series of conditional offsets. And it’s possible to calculate which offsets are needed.

Teleport Anywhere #

I ignored a lot of problems and complexity in the description above. When I started building the commands, there were a lot of details I had to deal with. It all starts with having the x, y, and z coordinates the player needs to teleport to stored in three scoreboard values.

The world is three-dimensional. To reach any position, I need to offset the command along three axes. If I used the tilde notation I mentioned above, then I would need three sets of functions, one for each axis. That would work, but I would rather avoid creating three almost-identical sets of code. Weirdly, Minecraft commands don’t just have a point of origin. They also have an orientation. I can almost think of it as an invisible entity running around making changes to the world very quickly. Maybe it’s Herobrine.

There’s another way to specify coordinates in commands called “caret notation”. teleport @s ~ ~ ~5 uses tilde notation to teleport the player five blocks south, regardless of what way the player is facing. teleport @s ^ ^ ^5 uses caret notation to teleport the player five blocks forward, which changes based on the direction the player is facing. There is, naturally, an execute rotated command that changes a command’s orientation.

In the completed data pack, there’s a single chain of offset functions that get called repeatedly inside a recursive for-loop. The loop starts by selecting the next axis, copying the scoreboard value for that axis somewhere I can do Fancy Math™ on it, and reorienting the command to face the correct direction. It also spins the command 180 degrees if the required offset is negative.

I decided to switch from binary to octal for the offsets. This means that each offset function has eight different offsets it might apply to the next function in the chain. The advantage is that I can get all the way out to 30,000,000 blocks, the edge of a Minecraft world, with only 8 steps in the chain.

With those optimizations, the whole process required just eleven function files. That’s pretty good for how much work those functions need to do.

Where Are We Going? #

So that’s the backend finished. I can now teleport a player to any set of coordinates I want, even if the location is not loaded. I just need to get the coordinates on the scoreboard.

The location of each portal gets stored in data storage, which I’ve discussed before, so I won’t go into details about how it works again. But before I could deal with that, I needed to detect when and where the player was creating a portal.

I made the player activate each portal by using flint and steel to set it on fire. This is reminiscent of activating nether portals and it’s easy to detect when the player uses flint and steel. The one problem is that I don’t know where the portal is. I know it’s in front of the player, but how far in front?

It’s possible to find the portal using variations on a few of the techniques I described above. I know there will be a fire on top of the portal and I know the player is looking at the fire. I set up a loop that starts at the player’s position and orientation and searches for a fire block, moving the command forward by a small step each time using caret notation until it finds it.

The portal needs to have nine matching wool blocks in its base. There’s an easy way to check for any wool block, but it’s harder to ensure they’re all the same color. I could check the color of the first block and then branch out to sixteen almost-identical functions that search for the same color. That would be a mess.

It turns out there’s a simple way to check if two blocks are identical without knowing in advance what kind of block is being searched for. It isn’t pretty, but the following checks if two regions contain the same blocks.

execute if blocks ~ ~-2 ~ ~ ~-2 ~ ~-1 ~-2 ~-1 all run [stuff]

It’s long because it’s designed for checking large regions, but it can be used to check individual blocks. I still need sixteen different conditional checks to find out what color the first block is, but all the others can be compared to the first one without any further complexity.

I did take a shortcut when it comes to checking if the portal has been destroyed. If any of the wool blocks get broken, the portal should break. This check has to happen every game tick for every portal, so I wanted to optimize it as much as I reasonably could. To make it easier, I only check if the nine blocks under the portal are wool blocks. If you can manage to break and replace the block fast enough, you could change the color of the portal blocks without breaking the portal. The portal would continue acting as if the block were still the original color.

Breaking Bad Portals #

If you do break any of the wool blocks, the portal immediately gets disabled. But what if the player places another portal of the same color? There can only be two working portals of each color, so if the player created a third one, I need to disabled one of the other two. If the portal I want to destroy is loaded, that’s easy. If not, then I need another option.

I could forceload the chuck just to destroy the portal, but it would be a pity to go to all this effort to avoid using forceload and then end up using it anyway.

Another option is to create a special entity that waits for the portal to load and then destroys it. I’ll call this special entity the portal assassin. When I want to destroy a portal in an unloaded chunk, I can create a portal assassin and then send it to the location of the portal that should be destroyed. The portal assassin will unload immediately when it enters the unloaded chunk. The next time the player visits that area, both the portal and the portal assassin will load together. As soon as they both load, the portal assassin destroys the portal and then self-destructs. I can’t teleport the portal assassin directly to the portal’s marker entity since it wouldn’t be loaded. Luckily, I just made this code that teleports things to arbitrary places in unloaded chunks, so that’s very convenient.

Everything was going well until I started testing the code. If the portal that needed to be destroyed was loaded, it would be destroyed easily. Good. If the doomed portal was far away, it would also be destroyed, so that seemed good. But I’m nothing if not paranoid, so I did some digging and found out that the portal I was destroying was still loaded. In other words, I wasn’t testing the portal assassin because the portal wouldn’t unload, even though I had moved far away.

Spawn chunks.

Right.

For those who don’t know, there’s a small region around the player’s original spawn location that always remains loaded no matter how far away the player moves. My test area was in the spawn chunks, so nothing there could unload. No problem. I flipped the setup around so I remained near spawn and destroyed a portal that was far away.

The portal assassin still wasn’t activating. My code was detecting that the portal was still loaded and destroying it directly.

After all this effort to avoid using forceload, I couldn’t get the portal to unload. Oh the irony.

It took a while to figure out what was happening. I’m going to refer back to something I said earlier:

If any of the wool blocks get broken, the portal should break. This check has to happen every game tick for every portal—

It turns out that a chunk can’t unload during a tick when this checks happens there. A few grumpy minutes later, I had changed the code to only check for broken portals every other tick. That’s still fast enough that the player shouldn’t notice any delay. And if they do, then I’m going to say that’s their fault for being so observant.

I ran the test again and this time the portal unloaded as expected and was subsequently destroyed by my portal assassin. Fantastic!

Wrapping Up #

And that’s the amount of effort I was prepared to go to just to avoid using forceload. There are always more details I left out, but this has gone on long enough. In the end, I’m happy it all worked. I know I’ll get more use out of the Teleport Scoreboard library in future projects. I didn’t run into as many problems with this one as I did with the last big project, but I hope you enjoyed following along on my journey. Thanks for reading!