Kalandralang User Manual

Getting Started

These instructions assume you are using Linux. If you want to use Kalandralang on Windows, you can have a look at https://fdopen.github.io/opam-repository-mingw/ but you're on your own.

Installation

Install opam. Create an Opam switch if you didn't already, with:

opam switch create 4.13.1

Clone Kalandralang:

git clone https://github.com/doomeer/kalandralang

Ask Opam to build and install Kalandralang:

opam install kalandralang

This puts the executable in ~/.opam/4.13.1/bin/kalandralang, so if you have ~/.opam/4.13.1/bin in your PATH (which is the case if you told the opam installer to update your config files and if you restarted your terminal), you can run Kalandralang with:

kalandralang

If this doesn't work, try running this instead:

~/.opam/4.13.1/bin/kalandralang

If this works but kalandralang doesn't, it means that you need to fix your PATH.

Once Kalandralang is installed, you probably want to:

Usage

Command-Line Help

Run Kalandralang with --help for documentation of command-line parameters:

kalandralang --help

You can request help for a given subcommand, such as run or find:

kalandralang run --help
kalandralang find --help

Update Game Data

Kalandralang requires data files from the RePoE project to run. Those data files contain the list of base items, modifiers etc. Kalandralang looks for those files:

Kalandralang can download those files for you; just run:

kalandralang update-data

You probably also want to Update Cost Data.

Update Cost Data

Kalandralang reads the cost of currencies, harvest crafts etc. from file data/costs.json. It is a good idea to regularly update this file. See Costs for more information.

From poe.ninja and TFT

The following command uses poe.ninja's API and reads from The Forbidden Trove's repository to generate file data/costs.json :

kalandralang update-costs

If you are from the future, you may need to specify the league name:

kalandralang update-costs --ninja-league Sentinel

You can also specify the folder to read from TFT's repository:

kalandralang update-costs --tft-league lsc
Use Default Values

The following command generates file data/costs.json with default values:

kalandralang write-default-costs

If for some reason update-costs does not work for you, you can use write-default-costs to start with a base file that you can then edit manually (see Costs).

Running a Recipe

Write a recipe in a file, such as busted-vaal-regalia.kld (start from an Example Recipe), and ask kalandralang to run it:

kalandralang run busted-vaal-regalia.kld

Finding Identifiers

Recipes contain identifiers for base object types and modifiers. There are multiple ways to find those identifiers.

Using the Find Command

You can use the find command to find them.

For instance, to find the identifier for the Agate Amulet base type:

kalandralang find agate

This tells you that the identifier is "Metadata/Items/Amulets/Amulet9". This is the internal name in Path of Exile data files.

To find the identifier for the +1 to Level of all Chaos Skill Gems:

kalandralang find 'level*chaos skill'

This finds all base types and modifiers that match pattern level*chaos skill, where * can be anything. This gives two results:

Using the Show Mod Pool Instruction

To find the identifier for a modifier, you can also use the show_mod_pool instruction (see Show Mod Pool). For instance, here is a recipe that shows all modifiers that can spawn on a non-influenced Agate Amulet:

buy "Metadata/Items/Amulets/Amulet9"

# We want the item to have no modifier so that we see all modifiers that can spawn.
# Otherwise, we would not see modifiers that are blocked by existing modifiers.
# But we cannot just scour because no modifier can spawn on normal items.
# So we use annul until there is no modifier.
# Orbs of Annulments keep the item rare.
until no_prefix && no_suffix do annul
show_mod_pool

Example Recipe

Code

Here is an example recipe to make a fire amulet. It uses the old Harvest augments though, it would no longer work after Kalandra league. You can also find the code in examples/fire-amulet.kld.

# This recipe produces Citrine Amulets with:
# - +1 to Level of All Fire Skill Gems
# - Life
# - Increased Fire Damage
# - Increased Cast Speed
# - Increased Spell Damage

# First, we buy a base.
#
# Citrine Amulet's internal name is "Metadata/Items/Amulets/Amulet10".
# To find this identifier, run: kalandralang find citrine
#
# We buy one with item level 84 for 1 chaos.
# Kalandralang will choose a random rare amulet for us.
buy "Metadata/Items/Amulets/Amulet10" ilvl 84 for 1 chaos

# Let's display the current item.
echo "Item we bought:"
show

# Then, we start working on the suffixes.
# We spam Deafening Essence of Zeal until the amulet has both the essence mod
# (i.e. Increased Cast Speed) and Tier 1 Increased Fire Damage.
#
# Increased Fire Damage's internal name is "FireDamagePercent5".
# To find this identifier, run: kalandralang find 'increased fire damage'
# This gives a lot of results; an alternative is to go to
# https://poedb.tw/us/Amulets#ModifiersCalc, click on the mod you are interested in,
# click on the "i" icon next to the tier you want, and copy-paste the Mod Id.
#
# "repeat" causes the instruction (here "essence_of_zeal") to always be executed
# at least once. This ensures that we do have the essence mod even if the amulet
# already had "FireDamagePercent5" when we bought it.
repeat essence_of_zeal until has "FireDamagePercent5"

# Let's display the current item.
echo "Item after essence spam:"
show

# Now that we are happy with our suffixes, we start working on our prefixes.
#
# We will use Harvest to augment fire.
# But first we need to make sure the amulet has two open prefixes:
# - one for the augment itself;
# - and one to craft Adds # to # Fire Damage to Attacks
#   to ensure the augment gives us +1 to Level of All Fire Skill Gems.
#
# Additionally, we take the opportunity to ensure that we have Increased Spell Damage
# on the amulet.
#
# To do that, we use Harvest to reforge keeping suffixes until there are
# two open prefixes (i.e. exactly 1 prefix), and Increased Spell Damage.
# The internal name for this modifier is:
# - "SpellDamage5" for Tier 1;
# - "SpellDamage4" for Tier 2.
# We are happy with Tier 2, but it doesn't mean we don't want Tier 1,
# so our stopping condition checks for both mods.
#
# "until" does not execute the instruction (here "harvest_reforge_keep_suffixes")
# if the condition already holds.
until prefix_count 1 and (has "SpellDamage5" or has "SpellDamage4") do {
  harvest_reforge_keep_suffixes
}

# Let's display the current item.
echo "Item after harvest reforges:"
show

# Let's craft Adds # to # Fire Damage to Attacks and augment fire to get
# our +1 to Level of All Fire Skill Gems.
craft "EinharMasterAddedFireDamage1"
harvest_augment_fire

# Finally, let's craft life and call it a day!
remove_crafted_mods
craft "EinharMasterIncreasedLife3"

# Let's say that we can sell this for 10 Exalted Orbs.
gain 10 exalt

Run Once

Run this recipe with:

kalandralang run examples/fire-amulet.kld

Here is an example run:

Item we bought:
--------
Citrine Amulet (Rare)
--------
(prefix) +29 to maximum Life
(prefix) 20% increased maximum Energy Shield
(suffix) +24% to Global Critical Strike Multiplier
(suffix) +17% to Cold Resistance
--------
Paid up to now: 0.01ex (1c)
Item after essence spam:
--------
Citrine Amulet (Rare)
--------
(prefix) 0.73% of Physical Attack Damage Leeched as Life
(suffix) 25% increased Fire Damage
(suffix) 18% increased Cast Speed
(suffix) +38% to Global Critical Strike Multiplier
--------
Paid up to now: 3.49ex (604c)
Item after harvest reforges:
--------
Citrine Amulet (Rare)
--------
(prefix) 18% increased Spell Damage
(suffix) 24% increased Fire Damage
(suffix) 18% increased Cast Speed
(suffix) +36% to Global Critical Strike Multiplier
--------
Paid up to now: 18.49ex (3198c)
--------
Citrine Amulet (Rare)
--------
(prefix) 18% increased Spell Damage
(prefix) +1 to Level of all Fire Skill Gems
(suffix) 23% increased Fire Damage
(suffix) 18% increased Cast Speed
(suffix) +38% to Global Critical Strike Multiplier
(prefix) {crafted} +45 to maximum Life
--------
Cost:
     1 × chaos
    10 × harvest_reforge_keep_suffixes
     1 × remove_crafted_mods
   201 × essence_of_zeal
     1 × harvest_augment_fire
     1 × craft "EinharMasterAddedFireDamage1"
     1 × craft "EinharMasterIncreasedLife3"
Total: 28.50ex (4928c) — Profit: -18.50ex (-3198c)

Run 100 Times

Run this recipe 100 times with:

kalandralang run examples/fire-amulet.kld -c 100

Kalandralang eventually tells us:

Average cost (out of 100):
     1.00 × chaos
    75.55 × harvest_reforge_keep_suffixes
     1.00 × remove_crafted_mods
   152.01 × essence_of_zeal
     1.00 × harvest_augment_fire
     1.00 × craft "EinharMasterAddedFireDamage1"
     1.00 × craft "EinharMasterIncreasedLife3"
Total: 125.97ex (21785c) — Profit: -115.97ex (-20055c)

     █                                                                          
   ▆ █                                                                          
   █ █ ▅   ▅                                                                    
 ▃ █ █ █  ▃█                                                                    
 █ █ █ █ ▂██                                                                    
 █ █ █ █ ███                                                                    
 █ █████ ███                                                                    
 █▆█████▆███                                                                    
 ███████████▅ ▅      ▅                                                          
 ████████████ █▃▃▃  ▃█▃ ▃                                                       
▂████████████ ████▂ ███ █▂▂    ▂                ▂                               
█████████████ █████ ███ ███    █                █                               
‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
0ex                                                                       1000ex

It looks like this recipe is really not worth it!

It looks like hoping to get Increased Spell Damage with Harvest reforge keeping suffixes is very costly. Using Harvest augment fire to get +1 to Level of All Fire Skill Gems is also not cheap, maybe we should have started with the prefixes by spamming Alteration Orbs. Can you improve the recipe to make it less ambitious but also less catastrophic?

Costs

To estimate the cost of each craft, Kalandralang needs to know the value of currencies, harvest crafts etc. Kalandralang has default values for each of those, but those values are unlikely to be very accurate as values change a lot over time. You can customize those values by creating a file named costs.json in the data directory that you set up in Setup a Working Directory.

To customize costs, it is recommended to generate data/costs.json using write-default-costs or update-costs (see Update Cost Data). You can then edit the values in this file manually.

This file is a JSON file composed of one JSON object. The name of each field is usually a keyword corresponding to an instruction. For instance, field "exalt" specifies the value of Exalted Orbs, field "harvest_reforge_life" specifies the cost of the harvest_reforge_life instruction, and field "pristine" specifies the value of Pristine Fossils. For resonators, the fields are named "primitive_resonator", "potent_resonator", "powerful_resonator" and "prime_resonator". The value of each field is the cost in chaos.

For instance, the following costs.json file:

{
  "exalt": 150,
  "hunter_exalt": 225
}

specifies that the value of Exalted Orbs is 150 Chaos Orbs, and that the value of Hunter Exalted Orbs is 225 Chaos Orbs.

If the value of a given instruction is not specified (i.e. does not appear in the file or is defined as null), its default value is used.

Language Reference

General Syntax Rules

Comments

Comments start with character # and end at the end of lines. For instance:

# This line is a comment
exalt # This is also a comment, but "exalt" isn't

Comments are ignored by the interpreter. Use them to make your code more readable.

Keywords

Keywords are composed of lowercase letters (a to z) and underscores _. They are predefined and correspond to language instructions. Here are some examples:

Numbers

Numbers are sequences of digits (0 to 9), optionally prefixed by a minus character - for negative numbers. Examples:

Identifiers

Identifiers start and end with double quotes ". For instance:

if has "GlobalSkillGemLevel1" then gain 15 exalt

In this example, "GlobalSkillGemLevel1" is the identifier for the +1 to Level of all Chaos Skill Gems modifier.

See also Finding Identifiers.

Labels

Labels start with a period . which is followed by lowercase or uppercase letters (a to z and A to Z), digits (0 to 9), and underscores _. They are used to refer to a given position in the code, to be able to jump to this position from other parts of the code. Here are some examples:

Well-chosen label names help to make the code more readable.

Symbols

The language uses the following symbol characters:

Whitespace

In general, whitespace (blanks, tabs and newlines) is ignored. Although you do have to separate keywords and numbers with whitespace (for instance abc is one keyword, but a bc is two), and newlines do matter to end comments.

Computational Model

Program Point

Recipes are sequences of instructions. For instance:

transmute
regal
exalt

is a recipe composed of three instructions. The first one is to apply an Orb of Transmutation, the second one is to apply a Regal Orb, and the third one is to apply an Exalted Orb.

When executing a recipe, Kalandralang keeps track of the program point, which is the next instruction to perform. At the very start, this is the first instruction of the program. After each instruction, the program point automatically moves on to the next instruction in the sequence, except if the instruction which was just executed is a Control-Flow Instruction, in which case it may move somewhere else.

Current Item

When executing a recipe, Kalandralang stores a current item in memory. At the very start, there is no current item, so usually the first instruction is to buy a base (see Buying a Base). After that, each instruction can modify the current item or replace it completely. For instance, transmute is an instruction that applies an Orb of Transmutation on the current item.

Some instructions contain conditions. Those conditions are evaluated on the current item. For instance, no_suffix is a condition that evaluates to true if the current item has no suffix.

Set-Aside Item Stack

When executing a recipe, Kalandralang stores a stack of set-aside items in memory. At the very start, there is no set-aside item. Use the set_aside instruction to set the current item aside (see Set Aside). This pushes the current item on top of the stack. It can then be used as input of an Awakener's Orb or a Recombinator in particular. Note that Splitting an item also pushes one of the two resulting items aside.

Since set-aside items form a stack, you can set-aside multiple items. For instance, if you push one item A, then set-aside another item B, item B will be on top of the stack, followed by item A. Using a recombinator for instance will thus take item B and remove it from the stack. Item A is then on top of the stack and can be used as input of another recombinator.

Current Imprint

When executing a recipe, Kalandralang stores a current imprint in memory. At the very start, there is no current imprint. Use the beastcraft_imprint instruction to make an imprint of the current item (see Imprint). Use the use_imprint instruction to restore the current item to the current imprint (see Use Imprint).

Spendings and Gains

When executing a recipe, Kalandralang counts how much you spend. Each instruction can add to this number. For instance, exalt adds one Exalted Orb to your spendings. Kalandralang also counts how much you gain (see Selling). At the end, Kalandralang can show total spendings and earnings.

Errors

It may be possible for instructions to fail. For instance, transmute fails if the current item is not of normal rarity. Failures cause the recipe to stop immediately with an error message. If this happens, you should modify your recipe to ensure that it does not try to perform invalid instructions. For instance, you could add an if instruction before an augment to only perform the augment if the item has an open prefix or an open suffix:

if open_prefix or open_suffix then augment

Buying a Base

buy [exact] [<rarity>] [<influence>] [<influence>] <identifier> [ilvl <number>] [with [fractured] <identifier>]* [for <amount>] is an instruction that sets or replaces the current item to a given base.

For instance, here is how to start from a random rare Agate Amulet of item level 100:

buy "Metadata/Items/Amulets/Amulet9"

Here is how to start from a normal rarity, hunter-influenced Agate Amulet of item level 86:

buy hunter "Metadata/Items/Amulets/Amulet9" ilvl 86
scour

Here is how to start from a random rare hunter and warlord-influenced Agate Amulet of item level 100:

buy hunter warlord "Metadata/Items/Amulets/Amulet9"

Here is how to start from a rare ilvl 86 Agate Amulet with fractured +1 to Level of all Chaos Skill Gems and Tier 1 Dexterity (and some other random modifiers), bought for 10 Chaos Orbs:

buy "Metadata/Items/Amulets/Amulet9"
  ilvl 86
  with fractured "GlobalChaosGemLevel1"
  with "Dexterity9"
  for 10 chaos

Note that "Dexterity9" is Tier 1, not Tier 9. This is how modifiers are named internally in the game.

Here is how to start from a magic ring with Tier 1 Maximum Life and nothing else:

buy exact magic "Metadata/Items/Rings/Ring2" with "IncreasedLife7"

Crafting Instructions

Currencies

The following keywords are instructions to apply currency to the current item.

Basic Currencies
Keyword Currency
transmute Orb of Transmutation
augment Orb of Augmentation
alt Orb of Alteration
regal Regal Orb
alch Orb of Alchemy
scour Orb of Scouring
bless Blessed Orb
chaos Chaos Orb
annul Orb of Annulment
exalt Exalted Orb
divine Divine Orb
orb_of_dominance Orb of Dominance
fracture Fracturing Orb
Conqueror Exalted Orbs
Keyword Currency
crusader_exalt Crusader Exalted Orb
hunter_exalt Hunter Exalted Orb
redeemer_exalt Redeemer Exalted Orb
warlord_exalt Warlord Exalted Orb
Eldritch Currencies
Keyword Currency
lesser_ember Lesser Eldritch Ember
greater_ember Greater Eldritch Ember
grand_ember Grand Eldritch Ember
exceptional_ember Exceptional Eldritch Ember
lesser_ichor Lesser Eldritch Ichor
greater_ichor Greater Eldritch Ichor
grand_ichor Grand Eldritch Ichor
exceptional_ichor Exceptional Eldritch Ichor
eldritch_annul Eldritch Orb of Annulment
eldritch_exalt Eldritch Exalted Orb
eldritch_chaos Eldritch Chaos Orb
Betrayal Currencies
Keyword Currency
veiled_chaos Veiled Chaos Orb (see also Unveiling)
Awakener's Orb

The awaken instruction applies an Awakener's Orb on the item which is on top of the Set-Aside Item Stack to destroy it and add its influence to the Current Item. For instance, here is how to destroy a hunter-influenced Agate Amulet to add hunter influence to a warlord Marble Amulet:

buy hunter "Metadata/Items/Amulets/Amulet9"
  with "AdditionalPierceInfluence1"
set_aside
buy warlord "Metadata/Items/Amulet/AmuletAtlas2"
  with "AreaOfEffectInfluence3"
awaken

This usually results in a Marble Amulet with the hunter modifier Projectiles Pierce an additional Target and the warlord modifier #% increased Area of Effect. But as both items could have more than one influenced modifier, this is not guaranteed.

Recombinators
Keyword Currency
armour_recombinator Armour Recombinator
weapon_recombinator Weapon Recombinator
jewellery_recombinator Jewellery Recombinator
recombine Armour Recombinator, Weapon Recombinator, or Jewellery Recombinator (chosen according to the base type of the current item)

All recombinator instructions recombine the Current Item and the item which is on top of the Set-Aside Item Stack together. Both item are deleted and the current item becomes the result of the recombination.

Essences

The following keywords are instructions to apply an essence to the current item.

Keyword Essence
essence_of_anger Deafening Essence of Anger
essence_of_anguish Deafening Essence of Anguish
essence_of_contempt Deafening Essence of Contempt
essence_of_doubt Deafening Essence of Doubt
essence_of_dread Deafening Essence of Dread
essence_of_envy Deafening Essence of Envy
essence_of_fear Deafening Essence of Fear
essence_of_greed Deafening Essence of Greed
essence_of_hatred Deafening Essence of Hatred
essence_of_loathing Deafening Essence of Loathing
essence_of_misery Deafening Essence of Misery
essence_of_rage Deafening Essence of Rage
essence_of_scorn Deafening Essence of Scorn
essence_of_sorrow Deafening Essence of Sorrow
essence_of_spite Deafening Essence of Spite
essence_of_suffering Deafening Essence of Suffering
essence_of_torment Deafening Essence of Torment
essence_of_woe Deafening Essence of Woe
essence_of_wrath Deafening Essence of Wrath
essence_of_zeal Deafening Essence of Zeal
essence_of_delirium Essence of Delirium
essence_of_horror Essence of Horror
essence_of_hysteria Essence of Hysteria
essence_of_insanity Essence of Insanity

Fossils

The following keywords are instructions to apply fossils to the current item. To apply multiple fossils, separate those keywords with +.

Keyword Fossil
aberrant Aberrant Fossil
aetheric Aetheric Fossil
bound Bound Fossil
corroded Corroded Fossil
dense Dense Fossil
faceted Faceted Fossil
frigid Frigid Fossil
jagged Jagged Fossil
lucent Lucent Fossil
metallic Metallic Fossil
prismatic Prismatic Fossil
pristine Pristine Fossil
scorched Scorched Fossil
serrated Serrated Fossil
shuddering Shuddering Fossil
fundamental Fundamental Fossil
deft Deft Fossil

For instance:

pristine

applies a Primitive Resonator socketed with a Pristine Fossil.

dense + fundamental

applies a Potent Resonator socketed with a Dense Fossil and a Fundamental Fossil.

faceted + aetheric + prismatic

applies a Powerful Resonator socketed with a Faceted Fossil, an Aetheric Fossil and a Prismatic Fossil.

metallic + lucent + jagged + deft

applies a Prime Resonator socketed with a Metallic Fossil, a Lucent Fossil, a Jagged Fossil and a Deft Fossil.

Crafting Bench

Built-Ins

The following keywords are instructions to apply a specify crafting bench operation on the current item using the crafting bench. All of them add a given modifier, except remove_crafted_mods which removes all crafted modifiers.

Keyword Modifier
multimod Can have up to 3 crafted modifiers
prefixes_cannot_be_changed Prefixes cannot be changed
suffixes_cannot_be_changed Suffixes cannot be changed
cannot_roll_attack_mods Cannot roll attack modifiers
cannot_roll_caster_mods Cannot roll caster modifiers
remove_crafted_mods Remove crafted mods
craft_any_prefix Add a cheap modifier chosen arbitrarily from the list of available prefixes
craft_any_suffix Add a cheap modifier chosen arbitrarily from the list of available suffixes

Use craft_any_prefix and craft_any_suffix when you want to craft a modifier without care for which one, for instance to add a third prefix or suffix to prevent an exalt or a reforge from adding a prefix or a suffix.

Other Modifiers

To add another mod using the crafting bench, use the craft <identifier> instruction. The identifier specifies which mod to add. See Finding Identifiers to find out which identifier to use. For instance, to add Adds # to # Cold Damage (lowest tier), use:

craft "DexMasterAddedColdDamageCrafted1"

Note that this operation has a very low estimated cost. It is intended to be used to block modifiers. You can use it to add meta-mods but the cost will not be estimated correctly. For meta-mods, use Built-In crafting bench operations instead.

Harvest Crafting

The following keywords are instructions to perform harvest crafts on the current item.

Keyword Craft
harvest_augment_attack Augment an item with a new Attack modifier, then remove a random modifier
harvest_augment_caster Augment an item with a new Caster modifier, then remove a random modifier
harvest_augment_chaos Augment an item with a new Chaos modifier, then remove a random modifier
harvest_augment_cold Augment an item with a new Cold modifier, then remove a random modifier
harvest_augment_critical Augment an item with a new Critical modifier, then remove a random modifier
harvest_augment_defences Augment an item with a new Defences modifier, then remove a random modifier
harvest_augment_fire Augment an item with a new Fire modifier, then remove a random modifier
harvest_augment_life Augment an item with a new Life modifier, then remove a random modifier
harvest_augment_lightning Augment an item with a new Lightning modifier, then remove a random modifier
harvest_augment_physical Augment an item with a new Physical modifier, then remove a random modifier
harvest_augment_speed Augment an item with a new Speed modifier, then remove a random modifier
harvest_non_attack_to_attack Remove a random non-Attack modifier from an item and add a new Attack modifier
harvest_non_caster_to_caster Remove a random non-Caster modifier from an item and add a new Attack modifier
harvest_non_chaos_to_chaos Remove a random non-Chaos modifier from an item and add a new Attack modifier
harvest_non_cold_to_cold Remove a random non-Cold modifier from an item and add a new Attack modifier
harvest_non_critical_to_critical Remove a random non-Critical modifier from an item and add a new Attack modifier
harvest_non_defences_to_defences Remove a random non-Defences modifier from an item and add a new Attack modifier
harvest_non_fire_to_fire Remove a random non-Fire modifier from an item and add a new Attack modifier
harvest_non_life_to_life Remove a random non-Life modifier from an item and add a new Attack modifier
harvest_non_lightning_to_lightning Remove a random non-Lightning modifier from an item and add a new Attack modifier
harvest_non_physical_to_physical Remove a random non-Physical modifier from an item and add a new Attack modifier
harvest_non_speed_to_speed Remove a random non-Speed modifier from an item and add a new Attack modifier
harvest_reforge_attack Reforge an item as a rare item with random modifiers, including an Attack modifier
harvest_reforge_caster Reforge an item as a rare item with random modifiers, including a Caster modifier
harvest_reforge_chaos Reforge an item as a rare item with random modifiers, including a Chaos modifier
harvest_reforge_cold Reforge an item as a rare item with random modifiers, including a Cold modifier
harvest_reforge_critical Reforge an item as a rare item with random modifiers, including a Critical modifier
harvest_reforge_defences Reforge an item as a rare item with random modifiers, including a Defences modifier
harvest_reforge_fire Reforge an item as a rare item with random modifiers, including a Fire modifier
harvest_reforge_life Reforge an item as a rare item with random modifiers, including a Life modifier
harvest_reforge_lightning Reforge an item as a rare item with random modifiers, including a Lightning modifier
harvest_reforge_physical Reforge an item as a rare item with random modifiers, including a Physical modifier
harvest_reforge_speed Reforge an item as a rare item with random modifiers, including a Speed modifier
harvest_reforge_attack_more_common Reforge an item as a rare item with random modifiers, including an Attack modifier. Attack modifiers are more common
harvest_reforge_caster_more_common Reforge an item as a rare item with random modifiers, including a Caster modifier. Caster modifiers are more common
harvest_reforge_chaos_more_common Reforge an item as a rare item with random modifiers, including a Chaos modifier. Chaos modifiers are more common
harvest_reforge_cold_more_common Reforge an item as a rare item with random modifiers, including a Cold modifier. Cold modifiers are more common
harvest_reforge_critical_more_common Reforge an item as a rare item with random modifiers, including a Critical modifier. Critical modifiers are more common
harvest_reforge_defences_more_common Reforge an item as a rare item with random modifiers, including a Defences modifier. Defences modifiers are more common
harvest_reforge_fire_more_common Reforge an item as a rare item with random modifiers, including a Fire modifier. Fire modifiers are more common
harvest_reforge_life_more_common Reforge an item as a rare item with random modifiers, including a Life modifier. Life modifiers are more common
harvest_reforge_lightning_more_common Reforge an item as a rare item with random modifiers, including a Lightning modifier. Lightning modifiers are more common
harvest_reforge_physical_more_common Reforge an item as a rare item with random modifiers, including a Physical modifier. Physical modifiers are more common
harvest_reforge_speed_more_common Reforge an item as a rare item with random modifiers, including a Speed modifier. Speed modifiers are more common
harvest_reforge_keep_prefixes Reforge a rare item, keeping all Prefixes
harvest_reforge_keep_suffixes Reforge a rare item, keeping all Suffixes
harvest_reforge_more_likely Reforge a rare item, being much more likely to receive the same modifier types
harvest_reforge_less_likely Reforge a rare item, being much less likely to receive the same modifier types

Beastcrafting

The following keywords are instructions to perform beastcrafts on the current item.

Keyword Craft
beastcraft_aspect_of_the_avian Add Aspect of the Avian
beastcraft_aspect_of_the_cat Add Aspect of the Cat
beastcraft_aspect_of_the_crab Add Aspect of the Crab
beastcraft_aspect_of_the_spider Add Aspect of the Spider
beastcraft_split See Split
beastcraft_imprint See Imprint
Split

beastcraft_split splits the current item into two items. One of them becomes the Current Item, the other is pushed on top of the Set-Aside Item Stack.

You can Swap the current item and the set-aside item to access the second item created by a split. For instance, to split an item that has +1 to Level of all Chaos Skill Gems and set the current item to the item that kept this modifier:

beastcraft_split
if not has "GlobalChaosGemLevel1" then swap
Imprint

beastcraft_imprint sets the Current Imprint to an imprint of the current item.

The item can then be restored by Using the Imprint. For instance, to try and add a suffix to an item which only has one prefix which is +1 to Level of All Skill Gems:

.try_again:
  beastcraft_imprint
  regal
  if prefix_count 2 then {
    use_imprint
    goto .try_again
  }

Betrayal Crafting

The following keywords are instructions to perform crafting obtained through the Immortal Syndicate on the current item.

Keyword Craft
aisling Aisling T4: remove a random modifier and add a new veiled modifier
Unveiling

unveil <mods> [else <instruction>] is an instruction that unveils an item with a veiled modifier. <mods> is a list of modifier identifiers separated by or. The Show Unveil Mod Pool instruction can be used to easily get the identifiers for the modifiers you want to unveil. If the first modifier in <mods> has been unveiled, it is chosen. Else, if the second modifier in this list has been unveiled, it is chosen. And so on. The optional else branch is executed if none of the modifiers were unveiled and another modifier had to be chosen instead.

For instance, here is how to try to unveil life on an amulet:

buy "Metadata/Items/Amulets/Amulet9"
veiled_chaos
unveil "JunMasterVeiledBaseLifeAndManaRegen_"

Here is how to try to unveil life or area damage or projectile damage, in this order of priority:

buy "Metadata/Items/Amulets/Amulet9"
veiled_chaos
unveil
  "JunMasterVeiledBaseLifeAndManaRegen_" or
  "JunMasterVeiledAreaDamageAndAreaOfEffect" or
  "JunMasterVeiledProjectileDamageAndProjectileSpeed"

Finally, here is how to unveil life or try again:

  buy "Metadata/Items/Amulets/Amulet9"
.try_again:
  veiled_chaos
  unveil "JunMasterVeiledBaseLifeAndManaRegen_" else goto .try_again

This is equivalent to using an if as follows:

  buy "Metadata/Items/Amulets/Amulet9"
.try_again:
  veiled_chaos
  unveil "JunMasterVeiledBaseLifeAndManaRegen_"
  if not has "JunMasterVeiledBaseLifeAndManaRegen_" then goto .try_again

Set Aside

The set_aside instruction pushes the Current Item on top of the Set-Aside Item Stack, then sets the current item to no item. You usually want to buy another item after that to get a new current item. Then you usually want to use an Awakener's Orb or a Recombinator.

Swap

The swap instructions exchanges the Current Item and the item on top of the Set-Aside Item Stack. It is in particular useful when Splitting items.

Use Imprint

The use_imprint instruction sets the Current Item to the Current Imprint, and sets the current imprint to no imprint.

Control-Flow Instructions

Interesting crafts are not linear: what to do after a given step often depends on the state of the item. Kalandralang allows you to express Conditions and to jump to a different Program Point, expressed with the use of a Label, depending on whether the condition holds or not.

Blocks

Everywhere you can write an instruction, you can write a block of instructions instead. A block is a sequence of instructions inside braces { ... }. For instance:

{
  prefixes_cannot_be_changed
  scour
}

is equivalent to:

prefixes_cannot_be_changed
scour

Typically, blocks are used in conditionals and loops. For instance:

if open_suffix then {
  prefixes_cannot_be_changed
  scour
}

is not equivalent to:

if open_suffix then
  prefixes_cannot_be_changed
  scour

The latter recipe applies an Orb of Scouring whether there was an open suffix or not, which is probably not what is intended according to the indentation.

Blocks can have any number of instructions, including zero or one: {} is a valid block which does nothing, and { alt } is equivalent to alt.

Nesting Complex Instructions

Complex instructions are:

Complex instructions cannot be used directly inside other complex instructions. They need to be put in a Block. For instance:

if open_suffix then
  if open_prefix then
    multimod
  else
    scour

will fail to parse. You should write this instead:

if open_suffix then {
  if open_prefix then
    multimod
  else
    scour
}

Label Definitions

A Label followed by a colon : gives a name to a program point to be able to jump to it from anywhere else in the recipe. For instance:

.start:

defines a label named .start. You can put this label at the beginning of your recipe to be able to jump to it later if you brick your item.

Goto

The goto <label> instruction sets the Program Point to the instruction that follows the given label's definition. For instance:

.chaos-spam:
  chaos
  goto .chaos-spam

creates an infinite loop of chaos spamming. This will in fact never end and you will have to interrupt Kalandralang using Ctrl+C.

goto instructions can often be avoided using If Conditionals and Loops. In large programs, it is recommended to avoid them at all costs. But crafting recipes are often small, and gotos can sometimes actually make the recipe more readable.

Stop

The stop instruction sets the Program Point to just after the last instruction of the recipe. This effectively causes the run to stop immediately. This is equivalent to putting a Label at the end and using Goto to jump to it.

If Conditionals

if <condition> then <instruction> executes the given instruction, but only if the condition holds. For instance:

if no_prefix then augment

causes an Orb of Augmentation to be applied on the current item if it has no prefix.

Another form of if conditionals is if <condition> then <instruction> else <instruction>. It executes the then instruction if the condition holds, or the else instruction if it doesn't. For instance:

if open_suffix then exalt else annul

causes an Exalted Orb to be applied on the current item if it has an open suffix, and an Orb of Annulment it its suffixes are full.

Loops

While Loops

while <condition> do <instruction> executes the given instruction until the given condition no longer holds. If the condition already does not hold before the while loop, the instruction is not executed at all. For instance:

while not has "GlobalChaosGemLevel1" do alt

is a recipe which spams Orbs of Alteration on the current item until the item has +1 to Level of all Chaos Skill Gems. If the item already has this modifier before the loop starts, no Orb of Alteration is used at all. It is equivalent to:

.loop:
  if has "GlobalChaosGemLevel1" then goto .stop
  alt
  goto .loop
.stop:
Until Loops

until <condition> do <instruction> executes the given instruction until the given condition holds. If the condition already holds before the while loop, the instruction is not executed at all. For instance:

until has "GlobalChaosGemLevel1" do alt

is equivalent to:

while not has "GlobalChaosGemLevel1" do alt
Repeat Loops

repeat <instruction> until <condition> executes the given instruction until the given condition holds. The instruction is always executed at least once. For instance:

repeat alt until has "GlobalChaosGemLevel1"

is equivalent to:

alt
until has "GlobalChaosGemLevel1" do alt

and to:

.loop:
  alt
  if not has "GlobalChaosGemLevel1" then goto .loop

Selling

gain <amount> causes the given Amount to be added to your earnings. It is typically used at the end of recipes to estimate profit margins. For instance, let's say that you want to estimate whether spamming Orbs of Alteration to get +1 to Level of all Chaos Skill Gems is more efficient than buying on the trade site, assuming you can sell the result for 20 chaos:

buy "Metadata/Items/Amulets/Amulet9"
until has "GlobalChaosGemLevel1" alt
gain 20 chaos

Kalandralang will execute the recipe and display your spendings, your earnings (here 20 Chaos Orbs), and the difference between the two.

This is not a very interesting example though because you can just add 20 chaos from the spendings in your head. However, when combined with If Conditionals, you can tell Kalandralang that the selling price depends on which mods the item has. For instance:

buy "Metadata/Items/Amulets/Amulet9"
until has "GlobalChaosGemLevel1" alt
gain 20 chaos
if has "Dexterity9" gain 30 chaos

The earnings of this recipe depend on whether the item ends up with Tier 1 Dexterity or not. If it does end up with Tier 1 Dexterity, the earnings are 50 chaos. Otherwise, they are 20 chaos. This is particularly useful for recipes that can fail but for which the resulting item is still sellable.

Other Instructions

Echo

echo <string> causes Kalandralang to output the given string. This is useful to know what is happening when debugging recipes, or simply to comment on what is going on. For instance:

echo "Will now try to obtain a veiled mod through Aisling."

Show

show causes Kalandralang to output the current item and the total amount we spent up to now. This is useful when debugging recipes, or simply to mark milestones in the recipe. It can typically be preceded by echo, for instance:

echo "Prefixes are done:"
show

Show Mod Pool

show_mod_pool causes Kalandralang to output the mods that could be added to the current item and the chance to add them when adding a single modifier (e.g. using an Exalted Orb). This also shows the Identifier of each mod.

This does not show mods that are blocked by other existing modifiers. If the suffixes are full, it will not show any suffix, and if the prefixes are full, it will not show any prefix. In particular, if the item rarity is normal, this will never show anything.

Example:

echo "Modifiers that can be added:"
show_mod_pool

Show Unveil Mod Pool

show_unveil_mod_pool causes Kalandralang to output the mods that could be unveiled on the current item. This also shows the Identifier of each mod.

This does not show mods that are blocked by other existing modifiers. It requires a veiled mod to be present and it only shows prefixes or suffixes, depending on whether the veiled mod is a prefix or suffix.

Example:

veiled_chaos
echo "Modifiers that can be unveiled:"
show_unveil_mod_pool

Amounts

Amounts are sequences of numbers followed by currencies. More precisely, amounts are of the form (<number> <currency>)*. For instance:

All Crafting Instructions can be used as currencies. This includes Currency instructions of course, but also Harvest Crafts, Beastcrafts, Betrayal Crafting, and all operations from the Crafting Bench. For instance, 1 harvest_augment_fire is a valid amount.

Conditions

Conditions are expressions that can be used in If Conditionals and Loops.

Constants

The following keywords are conditions expressions:

Keyword Meaning
true A condition which always holds
false A condition which never holds

Boolean Operators

The following operators can be used to combine conditions:

Keyword Usage Meaning
not not <condition> Negate a condition
and <condition> and <condition> Conjunction of two conditions: holds if both conditions hold
or <condition> or <condition> Disjunction of two conditions: holds if at least one of the two conditions hold

Parentheses in Conditions

not has higher precedence than and, which has higher precedence than or. What this means is that the following expression:

not open_suffix or no_prefix and full_prefixes

Is equivalent to:

((not open_suffix) or (no_prefix and full_prefixes))

Parentheses ( ... ) can be used around any condition. If operator precedence does not result in what you want, use parentheses. For instance:

not ((open_suffix or no_prefix) and full_prefixes)

gives a completely different meaning to the expression. If you don't understand precedence, just use parentheses everywhere!

Predicates on Current Item

The following expressions are conditions that hold depending on the current item.

Keyword Usage Meaning
has_mod has_mod <identifier> Holds if the item has the modifier denoted by the given identifier.
has_mod fractured has_mod fractured <identifier> Holds if the item has the modifier denoted by the given identifier and this modifier is fractured.
has_group has_group <identifier> Holds if the item has a modifier from the modifier group denoted by the given identifier.
has_group fractured has_group fractured <identifier> Holds if the item has a modifier from the modifier group denoted by the given identifier and this modifier is fractured.
has has <identifier> Equivalent to has_mod <identifier> or has_group <identifier>.
has fractured has fractured <identifier> Equivalent to has_mod fractured <identifier> or has_group fractured <identifier>.
is_base is_base <identifier> Holds if the base type of the item is <identifier>.
open_prefix open_prefix Holds if the item has at least one open prefix. This is not equivalent to prefix_count 0..2 as it depends on the item's rarity.
full_prefixes full_prefixes Holds if the item cannot have more prefixes. This is not equivalent to prefix_count 3 as it depends on the item's rarity.
open_suffix open_suffix Holds if the item has at least one open suffix. This is not equivalent to suffix_count 0..2 as it depends on the item's rarity.
full_suffixes full_suffixes Holds if the item cannot have more suffixes. This is not equivalent to suffix_count 3 as it depends on the item's rarity.
open_affix open_affix Holds if the item has at least one open prefix or suffix. This is not equivalent to affix_count 0..5 as it depends on the item's rarity.
full_affixes full_affixes Holds if the item can have neither more prefixes nor more suffixes. This is not equivalent to affix_count 6 as it depends on the item's rarity.
normal normal Holds if the item has normal rarity.
magic magic Holds if the item is magic.
rare rare Holds if the item is rare.

The following expressions are deprecated and may be removed in future versions, use Comparisons on Item Properties instead:

Keyword Usage Meaning
prefix_count prefix_count <number1>..<number2> Same as <number1> <= prefix_count <= <number2>.
prefix_count <number> Same as prefix_count = <number>.
no_prefix no_prefix Same as prefix_count = 0.
suffix_count suffix_count <number1>..<number2> Same as <number1> <= suffix_count <= <number2>.
suffix_count <number> Same as suffix_count = <number>.
no_suffix no_suffix Same as suffix_count = 0.
affix_count affix_count <number1>..<number2> Same as <number1> <= affix_count <= <number2>.
affix_count <number> Same as affix_count = <number>.
no_affix no_affix Same as affix_count = 0.

Comparisons

The following expressions allow to compare the result of Arithmetic Expressions together.

Operator Usage Meaning
= <expr1> = <expr2> <expr1> is equal to <expr2>
<> <expr1> <> <expr2> <expr1> is not equal to <expr2>
< <expr1> < <expr2> <expr1> is smaller than <expr2>
<= <expr1> <= <expr2> <expr1> is smaller than, or equal to <expr2>
> <expr1> > <expr2> <expr1> is greater than <expr2>
>= <expr1> >= <expr2> <expr1> is greater than, or equal to <expr2>

You can also write double comparisons <expr1> <operator1> <expr2> <operator2> <expr3>. Those are equivalent to <expr1> <operator1> <expr2> and <expr2> <operator2> <expr3>. For instance:

1 <= prefix_count <= 3

is a condition that holds if the current item has between 1 and 3 prefixes.

Arithmetic Expressions

Arithmetic expressions are expressions that evaluate into numbers. They can be used in Comparisons.

Constants

Integer constants are sequences of digits (0 to 9). Negative numbers are prefixed by a minus character (-). For instance, 1234, 01 and -32 are integer constants.

Operators

The following expressions allow to perform arithmetic operations on arithmetic expressions.

Operator Usage Meaning
- (unary) - <expr1> Negation of <expr1>
+ <expr1> + <expr2> Addition of <expr1> and <expr2>
- (binary) <expr1> - <expr2> Subtraction of <expr1> and <expr2>
* <expr1> * <expr2> Multiplication of <expr1> and <expr2>
/ <expr1> / <expr2> Division of <expr1> by <expr2>

Parentheses in Arithmetic Expressions

Operators follow the usual precedence rules: * and / have higher precedence than + and - (binary). - (unary) have the highest precedence. All operators are left-associative.

Parentheses ( ... ) can be used around any arithmetic expression.

See also Parentheses in Conditions.

Item Properties

The following expressions allow to get numeric properties from the current item.

Keyword Usage Meaning
prefix_count prefix_count Evaluates to the number of prefixes the current item has.
suffix_count suffix_count Evaluates to the number of suffixes the current item has.
affix_count affix_count Evaluates to the number of affixes (prefixes + suffixes) the current item has.
tier tier <mod_group> See Modifier Tiers.
Modifier Tiers

Mod groups are Identifiers that are shared by several modifiers. For instance, all modifiers that add Strength are part of mod group "Strength". The Find Command can tell you the mod group of modifiers.

If the current item has a modifier from group <mod_group>, expression tier <mod_group> evaluates to the tier that this modifier has in this group. If the current item has no modifier from this group, this expression evaluates to 999.

For instance, if the item has the best possible Strength modifier that can spawn on the current item, tier "Strength" evaluates to 1. If the item has the second best possible Strength modifier, this expression evaluates to 2, and so on.

Note that tiers depend on the item. For instance, the best +#-# to Maximum Life modifier is not the same modifier for Body Armours and for Gloves. Tier 1 Life on Gloves adds 80 to 89 Life, while this mod is only Tier 5 on Body Armours.

For instance, here is how to chaos spam an item until it has three attribute modifiers, either Tier 1 + Tier 1 + Tier 4 or better, or Tier 1 + Tier 2 + Tier 3 or better, or Tier 2 + Tier 2 + Tier 2 or better:

until
  tier "Dexterity" +
  tier "Intelligence" +
  tier "Strength" <= 6
do
  chaos

Converting Condition to Arithmetic Expressions

Brackets [ ... ] convert Conditions to arithmetic expressions. If the condition inside the brackets holds, the arithmetic expression evaluates to 1. Else, it evaluates to 0.

For instance:

buy "Metadata/Items/Amulets/Amulet9"
until
  [tier "Intelligence" <= 3] +
  [tier "Dexterity" <= 3] +
  [tier "Strength" <= 3]
  >= 2
do chaos

is a recipe that uses Chaos Orbs on an Agate Amulet until it has at least two attribute modifiers, both of them tier 3 or better.