Skip to content

2. Introduction to Spells & Spell Trees

CAS_ual_TY edited this page Oct 7, 2024 · 22 revisions

Introduction to Spells & Spell Trees

This page will go into detail on how data driven spells and spell trees work. A good way to understand all of this is to read the introduction followed by the examples. There is also a small "Getting Started" guide at the bottom.


Introduction to Spell Trees

Spell trees are what the name suggests. They are made of nodes that represent entries of spells in the tree. Every spell tree starts with a root node and nodes may have any amount of children nodes attached to them. Once you unlock a node you get access to this node's child nodes and may unlock them as well. It is important to understand that spells can be in multiple spell trees and also multiple times in the same spell tree with different configurations.

Every node in a spell tree has requirements to be able to unlocked together with the cost in xp levels and you having to have the parent unlocked already, of course. These requirements allow you to restrict the unlocking further so the player has to achieve more than just grinding xp. There are two different kinds of requirements, however:

  • Hidden requirements must be fulfilled in order to be able to see the spell node in the spell tree at all. If the hidden requirements of the root node are not met then the entire spell tree will not show up for the player.
  • Learn requirements must be fulfilled in order to be able to learn the spell once you already see it.

The basic spell trees of this mod barely use hidden requirements as transparency and ease of use was a goal. But this should not stop anyone from finding nice uses for this feature.

Another important aspect is synchronization. Clients do not need to have the same spell tree file contents as the server counter part. Instead, they get synchronized on demand (when the client opens the spell progression menu in the enchantment table). This not only allows servers to change spell trees on demand without clients needing any adjust ments, but also allows servers to hide features of spell trees until they are discovered by the player (eg. by using hidden requirements). So you can give the clients empty spell tree files and then only fill the contents of these files on the server to hide the spell progression to players up to the point they discovered it themselves.

Finally, please understand that you can modify spell trees (which is very easy to do) without having to enter the world of data generated spells which is far more advanced and complex. You can combine any collection of spells and then make your own spell trees out of them without ever directly touching the files of these spells.


Node IDs

The node ID is used to save the progression of a player and also their equipped spells. It uniquely identifies a node inside a spell tree. This ID must be unique within the spell tree and should never change. By eg. swapping the node ID of two different nodes A and B you basically swap said aspects for players: Every player who had learned node A now has learned node B and vice versa (this is not a problem if the player had learned both already, of course) and if they have node A equipped in a certain spell slots they now have node B equipped in it instead and vice versa.

It is recommended to not leave out this element (it is optional in JSON) unless you know for certain that you are not going to edit this spell tree ever again. In this case the mod auto assigns them. A recommended scheme for choosing these IDs is by simply starting at 1 and counting up. You can always add a new spell node anywhere in the tree without any problems that way.

To summarize:

  • Within a spell tree, every spell node ID must be unique.
  • Start with ID 1 and simply count up.
  • If you remove a spell from a spell tree, do not re-use the node ID again.

Introduction to Spells

This section is about spells. Again, you do not need to work through this if you only intend on editing spell trees.

Important node already: You have the power to make spells however you like. But this also means that you have the power to lag out the server, destroy the world or even in very rare circumstances crash the game (everything is coded in a way that should always avoid crashes but there may have been slight oversights that can result in a crash in very specific circumstances; please report an issue in case this happens).


Spell Actions & Activations

First we talk about the when and the what (eg. when a spell does something and what it does). Every spell is made up of spell actions that are sequentially executed. These can be anything, eg. the first action picks the entity the spell owner is currently looking at and the second action applies damage to it. Every spell action has an activation defined. Basically, depending on which activation is active spell actions are either executed or ignored. The initial activation that starts the whole execution is also called an event. Events can be eg. active when a spell is activated by its owner, on_equip when a spell is equipped into a spell slot, or on_unequip when a spell is unequipped, respectively, to name a few. Every spell has a list of events it hooks into and events that are not listed in there are ignored. A lot of actions allow you to interact with your own activations like when certain conditions are met or when a timer has run out activation x is activated.

If certain requirements of a spell action are not met then the spell action simply does nothing. Examples are requiring only exactly 1 target inside a target group, requiring specific elements to be set (in JSON) and so on. This is mentioned in the documentation for each spell action type.


Target Groups & Targets

Next up we talk about the who and how. Spell actions typically interact with target groups either by basically finding/defining them or by doing something with/to them. These target groups contain targets as the name suggests. There are different kind of target types that are explained below and certain actions find/define/require certain target types, eg. the damage action requires targets of type living_entity (eg. players, cows, zombies, the dragon, but not paintings, armor stands, or projectiles). Some spell actions also require single targets which simply means that there may only be a single target within the target group.


Target Types

These are all the existing target types:

  • position: Anything in a world that has a position, eg. block positions or entities.
  • entity: Entities, eg. Armor Stands, Arrow Projectiles, Thrown Potions, Chickens, Spiders.
  • living_entity: Entities with health, eg. Players, Spiders, the Dragon, Sheep.
  • player: Players.
  • static: Anything in a world that has a static position, eg. block positions.
  • item: Items that are usually part of some inventory.

The inheritance chain is as follows (see the first point for explanation):

  • static > position (every target of type static is also automatically of type position but not the other way around).
  • player > living_entity > entity > position.

Value Types List

Spell actions require you to supply values to configure them. These value types have names that are listed here:

  • int: integers, eg. 0, 7, -87.
  • double: floating point numbers, eg. 0.0, 1.7, -43.467, 2.
  • vec3: 3-dimensional vector made of 3 double elements representing any position or motion in a world.
  • boolean: Logical values, eg. either true or false.
  • tag: NBT compound tags (see Minecraft wiki).
  • string: A sequence of characters, eg. 'hello', 'ABCdef123_', 'Let us also do some Spaces!!'.

Variables

The last important concept to talk about are variables. These allow you numerous different things that include defining a variable and its value (of course), using them for spell actions instead of writing direct values, or getting certain values from targets (eg. player health, item count, entity uuid) that are deposited in variables. Almost every value of a spell action allows you to substitute it with a variable. You can also use them to make tiered spells - by defining variables for the entire spell you can set them dependent on their position in a spell tree.

If a spell action has an element that has the prefix d/ you can always subtitute it with a variable. You do that by supplying a string instead of format <<variable_name>>. This prefix also allows you to do in-line math expressions (eg. << a + b >>) which is explained on another page in this wiki.


Element Keys (Names)

JSON keys can look overwhelming but they are not hard to grasp and are instead meant to help you without having to constantly look at documentation. Prefixes are delimited by / and the name of the key is always behind the last one.


Spells & Spell Nodes & Spell Trees

The prefixes (eg. s1, s2, s3, n1, n2, t1, t2, etc.) are primarily there to order the elements and to help with keeping a proper oversight. They have no meaning except that s is used for Spells, n is used for Spell Nodes, and t is used for Spell Trees.


Spell Actions

  • a/: The activation to interact with (eg. enable or disable).
    • The activation that a spell ations requires to be fired will always just be named activation
  • ad/: The delayed activation to interact with.
    • These types of activations happen after a certain amount of time and restart the entire list of actions from the top (eg. targets and variables are reset)
  • t/: A target group containing a single target to interact with.
  • ts/: A target group containing any amount of targets to interact with.
  • td/: A target group to add a new target to. You may leave this empty to not add it to any target group.
  • d/: Allows you to substitute a value with a variable (see the section about variables) or with an in-line math expression.
  • [variable-type]/: Tells you what type is expected.
  • var/: The variable to deposit a value in. You may leave this empty to not assign this value to any variable.

Keys Prefixes Example

This action replenishes mana for all living entities in a given target group by a set amount.

{
  "type": "spells_and_shields:replenish_mana",
  "activation": String,
  "d/double/mana_amount": Double,
  "ts/targets": String
},

Explanation:

  • d/double/mana_amount: The amount of mana replenished.
    • d/: You may substitute this value with a variable.
    • double/: A double value is expected.
    • mana_amount The name of the key (i.e. something that describes it further).
  • ts/targets: The target group to replenish mana for.
    • ts/: The target group may be of any size.
    • targets: The name of the key (i.e. something that describes it further).

Built-in Values

Next, built-in values are explained. These are defined for certain events and circumstances but you can always modify or interact with them however you like (if you edit eg. a variable that came built-in the change will only apply for this context so the next time the spell is fired the variable comes with it's proper value again). Not all of them are listed here as some are only interacting with certain spell actions.

Here are some examples of events (built-in activations):

  • active activation: When the spell in a spell slot of a player is fired by pressing the key bind.
  • on_equip activation: When a spell is equipped into a spell slot of a player (eg. when equipping a spell in the spell progression menu).
  • on_unequip activation: When a spell in a spell slot of a player is unequipped or overridden by another spell.

These always come with the following target groups and variables (other events might not come with these):

  • owner target group (player type): The player that has/had this spell equipped.
  • spell_slot variable (int type): The integer representing the slot the spell was equipped in starting at 0.

The following variable always exists no matter the activation:

  • mana_cost variable (double type): Contains the set mana cost of a spell.

Example

Note: The mod comes with a bunch of spells that can also serve as examples. You can view them in this repository by going back to the "code" tab and then into: /src/main/resources/data/spells_and_shields/spells_and_shields/spells.

Let us make a spell that damages the target we are looking at. We are omitting everything except for the spell actions and we also do not include any spell action parameters that are not important for this example right now (this means that this example is not usable just like that and requires minor additions to work like the proper key prefixes):

...
{
  "type": "spells_and_shields:look_at_target",
  "activation": "active",
  "miss_activation": "",
  "block_hit_activation": "",
  "entity_hit_activation": "looked_at_entity",
  "range": 50.0,
  "source": "owner"
},
{
  "type": "spells_and_shields:simple_mana_check",
  "activation": "looked_at_entity",
  "target": "owner"
},
{
  "type": "spells_and_shields:sourced_damage",
  "activation": "looked_at_entity",
  "damage": 2.0,
  "source": "owner",
  "targets": "entity_hit"
},
...

These spell actions do the following:

  • The first action is activated by active (firing the spell using a key bind). We try to find an entity the source (the owner of the spell) is looking at. If such an entity exists we enable the looked_at_entity activation with the entity automatically being put into the entity_hit target group. If no such entity was found (the owner does not look at an entity but rather at a block or air) then this is not enabled.
  • Next, if we found such an entity we perform a simple mana check. If the target (owner) has enough mana then this mana is burned otherwise the looked_at_entity activation is automatically disabled.
  • Finally, if the looked_at_entity activation is still active (there was enough mana) we damage the targets (we damage entity_hit which is the entity the owner looked at) with owner being the source of the damage.

To summarize:

  • We look for the entity the owner is looking at
  • If this entity exists we perform the mana check
  • If the mana check succeeds we damage the entity

Notes:

  • The amount of mana checked and burned is set in the base of the spell and not as part of the action. There are other actions to do this with a custom amount.
  • If the entity is not of type living_entity then this damage action would simply do nothing.
  • If we were to do the mana check first then we might burn the mana but then not find an entity to perform the damage on