Lantern

Lantern is a data pack library that provides forceloaded chunks and slightly adjusts the data pack loading process. Lantern provides a very small API--the bare minimum required to use the forceloaded chunks effectively.

Lantern offers the following features:

  • Load hooks that allow your data pack to verify the version of Lantern or other dependencies.
  • Various scoreboard objectives used to hold global variables and data pack metadata.
  • A forceloaded chunk for each dimension, each containing several useful utility blocks.
  • A permanent armor stand entity useful for computational loot tables and other calculations.
  • Per-dimension tick hooks required for certain timing-sensitive operations.

This documentation serves as both a guide on the correct usage of Lantern and a reference of its various features. However, it is not a guide to 1.14 commands or the internal workings of data packs; the reader is expected to possess at least an intermediate understanding of these concepts.

Getting Started

To start working with Lantern, download the latest unbundled release and place it in your world's datapacks folder. This method is perfect for learning how Lantern works, but is not suitable for distributing Lantern to end users.

When you depend on Lantern, rather than using the #minecraft:load tag to initialize your data packs, you use #lantern.1:resolve. The function called by this tag is responsible for checking Lantern's major version (as multiple versions of Lantern may attempt to load at once).

Here is an example of a data pack that uses Lantern's tag:

// #lantern.1:resolve
{
    "values": [
        "#example:resolve"
    ]
}
# example:resolve
execute if score lantern lantern.versions matches 1 run function example:initialize
# example:initialize
scoreboard players set example lantern.versions 1
say example data pack initialized!

The point of this process is to allow data packs to validate that the Lantern features they were built to expect are indeed supported. As an example, example:initialize can safely assume that the forceloaded chunks exist and have blocks in the locations they should be.

In example:initialize, you may note that an example version was set on the scoreboard. This is good practice even if no other data packs depend on yours, as it allows functions called from #minecraft:tick or advancement triggers to confirm your data pack's successful initialization.

The version resolution process will be revisted on a later page.

Scoreboard Objectives

Lantern adds four scoreboard objectives, shown in the following table. The first two objectives mainly aid version resolution, while the other two are mainly for runtime data storage and calculations.

Objective NamePurposePersistent
lantern.versionsTrack versions of loaded data packsNo
lantern.flagsStore basic (boolean) configuration flagsNo
lantern.constDefine constants used in calculationsNo
lantern.globalStore arbitrary data that survives reloadsYes

lantern.versions and lantern.flags are guaranteed to be available during version resolution, regardless of whether the expected Lantern version loaded successfully. However, lantern.global and lantern.const must not be relied on without confirming that lantern lantern.versions is 1.

lantern.const is not strictly for constant values; its precise purpose is to store values that are finalized during or soon after version resolution and do not require information from previous reloads. This means that it can also be used for dynamic settings, not just hardcoded values.

Temporary Globals

Fake players prefixed with # on the lantern.global objective are considered temporary, and data packs may reset or overwrite these values without restriction. This provides an easy way to perform calculations without needing to fully namespace every involved variable.

execute store result score #result lantern.global run ...

Numeric Constants

Creating a fully namespaced fake player for every numeric constant, no matter how simple, would quickly prove tedious. Instead, simply name the constant by its value with a $ prefix, like this:

scoreboard players set $10000 lantern.const 10000

Forceloaded Chunks

For each of the three dimensions in Minecraft, Lantern forceloads the chunk at -30000000 0 8880. The following table gives a brief overview of the blocks in this chunk:

PositionBlockPurpose
-30000000 0 8880LecternArbitrary data storage
-30000000 0 8881SignText component resolution
-30000000 0 8882Shulker BoxGeneral item manipulation
-30000000 0 8883Shulker BoxSingle item manipulation
-30000000 0 8884DropperAdvanced loot table usage

Additionally, an armor stand with a UUID of cb-0-0-0-1 is placed in the Overworld's chunk.

All positions in these chunks with a Y of 0 or 1 are reserved by Lantern for future use. Other data packs may freely use positions with greater Y values, but there is no guarantee that blocks placed in those positions will persist for any period.

The next five pages will expand upon the roles of each of the forceloaded utilities listed above.

Lectern

-30000000 0 8880

Items in Minecraft have a special NBT compound called tag. This NBT tag can contain any other NBT tags, even values that Minecraft would never itself read. Therefore, item NBT is a useful tool for storing long-term data and applying transformations on NBT meant for another location.

This is why Lantern provides a forceloaded lectern--lecterns do not tick, have very low serialization overhead, and can store exactly one item. Therefore, the lectern's Book.tag is the best option for your arbitrary NBT storage needs.

All data packs that use Lantern are free to modify the Book.tag of the lectern at -30000000 0 8880, so long as they do not delete or overwrite Book.tag completely. It is, however, highly recommended that data packs add their namespace to the NBT path they choose to use. An example data pack would modify Book.tag.example to avoid conflicting with other data packs.

The lectern provided by Lantern cannot resolve text components. Use the sign instead.

Provided Tags

Lantern currently only provides one tag of its own within the lectern.

Book.tag.lantern.Dimension contains an int corresponding to the dimension the lectern is in. This allows data packs to confirm their current dimension without using an entity's Dimension tag. The value of this tag is 0 for the Overworld, -1 for The Nether, and 1 for The End.

Sign

-30000000 0 8881

Text components are a must-have for any data pack that wishes to display styled or translated text to the end user. Additionally, they are the only way to display dynamic values such as score, selector, and nbt. However, these more advanced text components do not work in every case--notably, they fail with item names, entity names, and container names.

In the few contexts where advanced text components are allowed, they are immediately translated into simple components. If a data pack places an advanced text component into one of these contexts, it could then copy the simplified text component to any other location (so long as it can be reached with data modify).

Lantern provides a sign at -30000000 0 8881 so that data packs can use the Text1 - Text4 tags for advanced text component resolution. Note that the execution context is completely ignored by signs, so @s will not work.

Shulker Boxes

-30000000 0 8882
-30000000 0 8883

Shulker boxes have a very special property, making them stand out even from other containers--the way a shulker box's contents are dropped is controlled by the loot table, rather than the game's code. With the correct loot table, a shulker box's contents can be funneled anywhere with the right loot command, without interfering with how shulker boxes drop in survival.

Being able to funnel a shulker box's contents is useful primarily because it is the only reliable way to dynamically modify the player's inventory, ender chest, and equipped armor. Additionally, it is a great way to drop large quantities of natural-looking items on the ground at once (with loot spawn).

Lantern replaces the minecraft:yellow_shulker_box's loot table, and provides two different yellow shulker boxes. The shulker box at -30000000 0 8882 is free for any use, while the shulker box at -3000000000 0 8883 must be used only for its first slot (Slot:0b / container.0).

To funnel the contents of one of the shulker boxes, you must use loot's mine subcommand with a provided tool of minecraft:air{drop_contents:1b}. For example, to give yourself the contents of the -30000000 0 8882 shulker box, use the following command:

loot give @s mine -30000000 0 8882 minecraft:air{drop_contents:1b}

Bow Dropper

-30000000 0 8884

Sometimes, one may wish to evaluate a loot table to obtain a numeric result (for example, a value based on the current biome, which is otherwise opaque to commands). The easiest way to do this is setting an NBT tag containing the desired result on the generated item. However, to avoid having to check NBT, one can instead generate multiple unstackable items and use execute store to count how many were created.

The issue with the unstackable item approach is that one is required to use loot spawn, as the result of loot replace or loot insert is limited by the number of slots available. This results in unnecessary item entities being spawned, which makes the approach rather difficult to justify.

There is, however, a strange workaround that allows loot insert to function just as well as loot spawn. The way it works is an implementation detail--just know that if you make a loot table that drops bows and loot insert into the block at -30000000 0 8884, the result you get will be the number of bows dropped, even though the dropper has no room for them.

Overall, this bow dropper fills a small niche, but including it in Lantern does not introduce extra overhead, and it is a useful tool for projects that use computational loot tables.

The bow dropper must not be modified unless the modification is done with loot insert!

Armor Stand

cb-0-0-0-1

The NBT tags of entities can be useful for certain calculations, so Lantern provides an armor stand with a UUID of cb-0-0-0-1 for all data packs to use. Data packs may use cb-0-0-0-1 for whatever they want, so long as they return the entity to a forceloaded chunk once they are done, and refrain from killing it.

The HandItems may be useful for computational loot tables, as loot ... fish can take the mainhand or offhand of the current executor as arguments, and loot tables have been experimentally suggested as a viable alternative to command-based NBT checking.

Dimension Ticks

Minecraft assigns each dimension its own portion of every game tick. First the Overworld is processed, and then The End, and finally The Nether. In each of these dimension-specific ticks, various events happen that a data pack may wish to modify. However, data packs alone are limited to the very beginning of a game tick (#minecraft:tick or schedule), and therefore may not be able to perfectly detect events from a dimension tick before they influence the world.

Back when command blocks were used instead of data packs, this was hardly an issue to consider. Nearly all events happen in the Overworld, where the command blocks were, and command blocks actually run at the perfect time within the dimension-specific ticks. Unfortunately, it is impossible for data packs to replicate this once-ubiquitous timing without relying on a command block.

Therefore, Lantern includes a repeating command block in each forceloaded chunk, so that data packs may simply hook into the tag(s) for the dimensions in which they need to run functions in. The tags available are listed in this table:

TagDimension
#lantern.1:tick_overworldOverworld
#lantern.1:tick_the_netherThe Nether
#lantern.1:tick_the_endThe End

There is an additional tag--#lantern.1:tick_dimension. This tag runs each time one of the above tags runs, making it useful if your data pack has a single function that needs to run with the dimension-specific timing for every dimension.

Dimension ticks can be disabled, as they do cause a slight amount of runtime overhead, and serve a rather niche purpose. Read the Distributing Lantern page to learn how to disable this feature.

Version Resolution

For simple data packs, confirming the versions of any dependencies in #lantern.1:resolve is sufficient for the data pack to load. However, some data packs, such as Lantern itself, have more complex loading processes. For example, a data pack made to be bundled with other data packs should be aware that multiple versions of the same data pack may be loaded at once. In this case, only one version of the pack should be allowed to load.

A data pack aware of multiple versions would look like this:

// #lantern.1:resolve
{
    "values": [
        "#example:enumerate",
        "#example:resolve"
    ]
}
// #example:enumerate
{
    "values": [
        "example:v1/enumerate"
    ]
}
// #example:resolve
{
    "values": [
        "example:v1/resolve"
    ]
}
# example:v1/enumerate
execute if score lantern lantern.versions matches 1 unless score example lantern.versions matches 1.. run scoreboard players set example lantern.versions 1
# example:v1/resolve
execute if score example lantern.versions matches 1 run function example:v1/initialize
# example:v1/initialize
say example data pack initialized!

In simple cases, this example data pack functions identically to the example on the Getting Started page. However, the difference is that it will not initialize if a conflicting future version is present at the same time. Instead, the conflicting future version would be the one to initialize.

Resolution Tags

Lantern offers two tags to assist with version resolution. The first is #lantern.1:resolve, which suffices for nearly every use. Data packs can check for dependencies and then initialize themselves, or expose themselves as dependencies for other data packs to use. However, there are some features that may be augmented if processing is done after all dependent packs have been initialized.

It is for these features that Lantern offers the #lantern.1:post_resolve tag, which runs immediately after the #lantern.1:resolve tag. Post-resolution is a time for library data packs to use values given by their dependent packs to aid in an extended initialization process; for example, waiting until this point to compute some constants to store in the lantern.const objective.

Bundling

When a data pack has dependencies, it can quickly become a burden for the user to manage and install all of them. This is why it can be useful to merge the dependencies with the final data pack ZIP before you distribute your data pack to your users.

Not all data packs are made to support this, and most that claim to support bundling will break if another data pack bundles the dependency. The use of version resolution features as described on the previous page combined with including the version number in the actual folder names, however, can make bundling work gracefully, even when multiple data packs bundle different versions of the same dependency.

To bundle a data pack, simply merge its data folder with your data pack's data folder. There should not be any conflicts, unless your dependency makes use of the same function tags that you make use of. To resolve these conflicts, place the entries of your dependency above your own entries in each affected function tag.

Bundling can be tedious and error-prone when done manually. However, there is not currently a general-purpose tool to bundle compatible data packs. In the near future, Lantern will publish a standard for data packs that can be used to make the version resolution and bundling processes much simpler.

Ordering

Lantern's version resolution process gives the data pack developer full control over dependency management. However, Minecraft itself has no mechanism to ensure that a dependency will load and initialize before your own data pack. This has lead to packs exposing their own tags that run when they successfully load, which fragments the code of your own data pack, especially if you rely on multiple libraries that use this approach.

There is another way to ensure that dependencies load before dependent packs--placing the entries in #lantern.1:resolve for the dependency above the entries for the dependent pack. If this sounds familiar, it is because this is exactly what is recommended when bundling data packs together.

Guaranteeing load order is technically possible without bundling, however it requires you to provide your own, empty function tags for every dependency you have. Keep this method in mind, because although bundling is great for many practical use cases, it does not make sense for every data pack.

Note that the ordering only works in the way described above. You cannot reliably execute before or in the middle of a dependency's tags, however you can force your data pack's execution to occur afterwards!

Distributing Lantern

At this point, you should have a fairly good understanding of what Lantern offers, as well as an understanding of the version resolution process. Now you may be wondering how to distribute Lantern alongside your data pack.

For many data packs, bundling is a recommendation. It's good practice, and makes it easier for end users. However, for Lantern, bundling is a requirement of distribution. This is because of the version resolution process--if your data pack goes through version resolution, and then Lantern is unloaded, your data pack may very likely be in an invalid state. The scoreboards suggest that Lantern or any other dependencies were initialized, but this is only because they were not cleared on reload.

To package your data pack for release, take Lantern's data folder from the master branch of the GitHub repository, merge it with your own data folder, and handle any function tag conflicts as described on the bundling page.

Configuring Dimension Tags

The dimension tags offered by Lantern are an optional feature, although they are enabled by default. To opt out of dimension tags, delete the following file when you bundle Lantern:

data/lantern.1/functions/flags/enable_dimension_tags.mcfunction

Configuring Forceloading

Although Lantern's primary purpose is to provide forceloaded utility blocks, not every data pack requires access to these. They may, however, wish to use Lantern's version resolution tags. In this case, a data pack may opt out of forceloading by deleting the following file when bundling Lantern:

data/lantern.1/functions/flags/enable_forceloading.mcfunction

Compatibility

This page is a reference for what is required for a data pack to be considered compatible with Lantern. This is a useful reference for external data packs

Location

In each dimension, the cuboid from -30000000 0 8880 to -29999985 1 8895 is reserved for Lantern internal use. Modifying blocks in this area is strictly forbidden, except when specifically allowed in this documentation.

Additionally, data packs that depend on Lantern are encouraged to use the cuboid from -30000000 2 8880 to -29999985 255 8895 for any purpose, and should not impose additional restrictions on these positions.

Therefore, a library that wishes to provide its own forceloaded utilities should use its own chunk, rather than the chunk at -30000000 0 8880.

Entity + UUID

The UUID cb-0-0-0-1 is reserved for an armor stand provided by Lantern. Data packs must not kill the armor stand or not leave it outside of a Lantern forceloaded chunk. Additionally, summoning another entity with the same UUID is expressly forbidden.

Loot Table

The minecraft:yellow_shulker_box loot table is overridden by Lantern. For a data pack to be compatible with Lantern, it must not make its own changes to this loot table. However, it may include a verbatim copy of the loot table (for example, Minecraft Phi and AESTD do this).

A copy of Lantern's minecraft:yellow_shulker_box loot table is available here.