Skip to main content

Origins

Origins is an addon built on Apoli. Apoli provides the powers, and Origins lets players pick a bundle of them at spawn. That bundle is an origin: a name, an icon, some lore and a list of Apoli powers.

Everything on this page is specific to Origins. Without the Origins mod you don't need any of it, and you can grant Apoli powers by command or from your own addon instead.

An origin file

Origins live in data/<namespace>/origins/, and the file name is the origin's ID.

{
  "powers": [
    "origins:aqua_affinity",
    "origins:water_breathing",
    "origins:like_water"
  ],
  "icon": "minecraft:cod",
  "impact": 2,
  "name": "Merling",
  "description": "You breathe water, but air is another matter."
}

Fields

FieldTypeDefaultDescription
powersArray of power entries[]The Apoli powers this origin grants.
iconIconoptionalThe item or texture shown in the selection screen.
impactInteger from 0 to 3, or none, low, medium or high0The impact dots that show how strong the origin is. origins:impact reads it back.
nameText Componenta translation keyThe origin's display name. Without it, the translation key origin.<namespace>.<path>.name is used.
descriptionText Componenta translation keyThe lore shown in the selection screen. Without it, the translation key origin.<namespace>.<path>.description is used.
orderIntegerlastThe sort position in the screen. Origins are sorted by impact first, lowest first, then by order, and an origin without an order goes after those that have one.
loading_priorityInteger0When several packs define the same ID, the highest value wins.
unchoosableBooleanfalseThe origin exists but can't be picked, as with an admin origin.
name_scroll_speedFloat1.0How fast a long name scrolls across its box.
max_playersInteger-1How many players can hold this origin at once (see Capping an origin).
upgradesArray of upgrades[]Conditions that turn this origin into another one (see Upgrading into another origin).
tagsString or Array of strings[]Free-form labels on the origin. origins:origin_tag tests them, so a condition can name a group of origins instead of listing IDs.

Upgrading into another origin

With upgrades, an origin becomes another one when the player meets a condition. Each entry names an entity condition and the origin to change into. As soon as the condition holds, the player's origin on that layer is swapped for the new one and their powers are reconciled in place.

FieldTypeDefaultDescription
conditionEntity Condition TyperequiredTested against the holder. When it passes, the upgrade happens.
originIdentifierrequiredThe origin to change into.
announcementText ComponentoptionalA message sent to the player when the upgrade happens. Without it, nothing is said.
{
  "icon": { "item": "minecraft:phantom_membrane" },
  "impact": 2,
  "powers": ["example:gliding"],
  "upgrades": [
    {
      "condition": {
        "type": "apoli:advancement",
        "advancement": "minecraft:end/kill_dragon"
      },
      "origin": "origins:elytrian",
      "announcement": "You have slain the dragon. Your wings are whole."
    }
  ]
}

Entries are tested in order and the first one that passes wins, so put the most specific condition first. An entry that names the origin it is written in is ignored, and so is one that names an origin no pack defines.

Powers the two origins share are left alone, so a resource, cooldown or stored inventory that both grant survives the upgrade untouched. Only the powers that differ are revoked and granted.

An origin in a swap pool upgrades too. The old entry leaves the pool and the new one takes its place, without disturbing whatever is swapped in.

note

The check runs on the server, once a second per player, and only while at least one loaded origin declares upgrades, so a pack without any pays nothing. Expensive conditions are still evaluated at that rate, so prefer cheap ones (an advancement, a resource, a tag) over a raycast.

Capping an origin

max_players limits an origin to a number of players. Once that many hold it, nobody else can take it: it disappears from the choose screen, the random roll never lands on it, auto_choose and derived layers skip it, and a client that picks it anyway is refused with a message and shown the screen again.

ValueMeaning
-1 (default)Use the layer's max_players_per_origin.
0Unlimited, which takes the origin out of a capped layer's limit.
A number above 0At most that many players. 1 makes the origin exclusive to whoever takes it first.
{
  "icon": { "item": "minecraft:dragon_egg" },
  "impact": 3,
  "max_players": 1,
  "powers": ["example:dragon_flight"]
}

The cap counts the whole server, offline players included. The record lives in the world save, so a player who logs off keeps their slot. It is counted per layer and origin, so the same origin offered in two layers has a separate cap in each.

The current holder keeps the origin. A cap never revokes an origin someone already has, and being at the cap doesn't stop that holder from seeing it in the view screen. Swap pools count too: an origin granted into a swappable layer's pool takes a slot.

note

Explicit grants aren't capped. /origin set, origins:grant_origin, origins:transfer_origin and origins:copy_origin all go through regardless, so an operator or a data pack can always hand out an origin. Only selection by players is limited.

Layer-wide defaults go on the layer as max_players_per_origin, and /origin cap inspects and clears the record (see the command).

The icon field

icon accepts three shapes:

"icon": "minecraft:cod"
"icon":{
   "item":"minecraft:potion",
   "components":{
      "minecraft:potion_contents":{
         "potion":"minecraft:water_breathing"
      }
   }
}
"icon":{
   "texture":"example:textures/gui/origins/merling.png"
}

The first is an item ID and the second a full Item Stack. The third draws a texture instead, so you don't have to register an item just to have a custom icon. This is the shared Icon data type, and Apoli's skill trees take the same shapes.

FieldTypeDefaultDescription
textureIdentifierrequired for the texture formThe path to the PNG, relative to assets/<namespace>/.
widthIntegerthe whole fileThe width in pixels of the region to draw, measured from the top-left corner of the file.
heightIntegerthe whole fileThe height in pixels of the region to draw, measured from the top-left corner of the file.

You don't usually need width and height. The file's real size is read from the PNG, and the whole image is scaled into the 16×16 icon slot, so a 16×16, 64×64 or 256×256 icon all work. Set them only to draw part of a larger file, such as the top-left 32×32 of a sprite sheet:

{
  "icon": {
    "texture": "example:textures/gui/origins/sheet.png",
    "width": 32,
    "height": 32
  }
}
note

A texture icon is a plain image, with no enchantment glint, item tooltip or durability bar. Use the item form when you want those.

Icons larger than the slot are scaled down with smoothing, so fine detail and thin lines survive. An icon made at exactly 16×16 is drawn pixel for pixel.

Powers come from Apoli

The powers list is plain Apoli power IDs. Nothing about them is specific to Origins, and they are the same powers you would write in any data pack. A good origin is usually one apoli:multiple that bundles the traits, plus a couple of standalone powers, which keeps the selection screen readable.

Hiding powers in the GUI

Each entry in powers is either a power ID or an object that groups several IDs behind an entity condition. The two forms can be mixed in one list.

FieldTypeDefaultDescription
conditionEntity Condition TypeoptionalWhile this fails, the group's powers are hidden from the origin's power list in the choose and view screens.
powersArray of Identifier[]The power IDs in this group.
{
  "name": "Aviator",
  "powers": [
    "example:flight",
    "example:slow_falling",
    {
      "condition": {
        "type": "apoli:advancement",
        "advancement": "example:mastery"
      },
      "powers": [
        "example:double_jump",
        "example:air_dash"
      ]
    }
  ]
}

Flight and slow falling always show. The double jump and air dash group stays hidden until the player earns example:mastery and then appears, so powers can be revealed as they unlock without changing the origin itself.

important

The condition only filters what is displayed. Every power in the list is granted as soon as the origin is chosen, whatever the condition says. To keep a power inactive until something is true, put a condition on the power's own JSON, which is how Apoli already gates a power at no runtime cost. Use both together for a power that is neither visible nor active yet.

note

The condition is evaluated on the client, so it can only read state the client knows about. A layer's conditioned origins have the same limitation.

Origins need a layer

An origin file on its own doesn't appear in game. It has to be placed in a layer, the slot the player chooses from. See Layers.

The origins: namespace

Apoli registers a namespace alias, so every type Apoli provides also answers to origins: as well as apoli:, with or without Origins installed. origins:health is apoli:health, with the same class, fields and behaviour. There is no separate Origins version of it.

{
  "type": "origins:health",
  "comparison": "<",
  "compare_to": 6
}
{
  "type": "apoli:health",
  "comparison": "<",
  "compare_to": 6
}

Those two are the same condition. The alias exists because years of packs were written for the original Origins mod, before powers were split out into Apoli, and it keeps that JSON working unchanged.

For reading these docs, that means:

  • Every power, action, condition, meta type and data type in the data pack reference can be written with either namespace, whether or not its page mentions it. Only the types listed below are Origins-only.
  • The reference pages print the apoli: spelling, because that is what the mod registers. Wherever you see apoli:x, origins:x works too.
  • Prefer apoli: in new packs, since that is the name the types are registered under.
note

The alias resolves on an Apoli server without Origins too, so a pack written with origins: type IDs keeps working there. This only applies to type IDs: power IDs like origins:phantomize are real Origins data pack content and have no apoli: equivalent.

The types Origins adds

As an Apoli addon, Origins also registers Apoli types of its own in the origins: namespace. They work in any power's JSON exactly like the built-in Apoli types, but a power that uses one fails to load without Origins installed. The alias only works in one direction, so these types have no apoli: spelling.

TypeKindWhat it does
origins:originEntity conditionChecks whether the player has a given origin.
origins:origin_tagEntity conditionChecks whether the player's origin carries a given tag.
origins:impactEntity conditionCompares the impact of the player's origins against a value.
origins:swappedEntity conditionChecks whether the player is wearing an origin from their swap pool.
origins:stored_originEntity conditionChecks whether an origin is remembered.
origins:stored_valueEntity conditionChecks whether a piece of text is remembered.
origins:impact (bi-entity)Bi-entity conditionCompares one player's impact against the other's.
origins:grant_originEntity actionGives the player a named origin, on a layer or into a swap pool.
origins:revoke_originEntity actionTakes an origin away from the player.
origins:force_swapEntity actionSwaps the player to another origin from their pool.
origins:open_swap_menuEntity actionOpens the swap picker.
origins:store_originEntity actionRemembers an origin under a named key.
origins:apply_stored_originEntity actionPuts a remembered origin back.
origins:store_valueEntity actionRemembers a piece of text.
origins:copy_originBi-entity actionCopies the target's origin onto the actor's copy layer.
origins:transfer_originBi-entity actionTakes, gives or copies a whole origin between two players.
origins:store_origin (bi-entity)Bi-entity actionRemembers the target's origin on the actor.
origins:action_on_swapPower typeRuns an entity action when the player swaps origins.
origins:chose_originAdvancement triggerFires when the player takes an origin.

The store and stored types make up the origin storage system. Origins also adds four badge types and two fields to apoli:action_on_callback. That power is core Apoli, but its entity_action_chosen only runs when Origins is there to report the choice.