Skip to content

KubeJS Integration

Custom Action

First you need to add a custom action somewhere in your recipe:

Format

Name Description Type / Literal
type type "custom"
id id string
custom properties...

Example

{
    "type": "custom",
    "id": "example_log_action",
    "custom_property": "my_value"
}

Then define the behavior of your custom action in KubeJS:

// startup script. will be executed when recipe is loaded
LycheeEvents.customAction('example_log_action', event => {
    let msg = event.data.custom_property

    // use ProbeJS for more information about the parameters
    event.action.applyFunc = (recipe, ctx, times) => {
        console.log(msg)
    }
    // it is recommended to cancel the event to prevent the action from being modified by other codes
    event.cancel()
})

Custom Condition

First you need to add a custom condition somewhere in your recipe:

Format

Name Description Type / Literal
type type "custom"
id id string
custom properties...

Example

{
    "type": "custom",
    "id": "example_always_true_condition"
}

Then define the behavior of your custom condition in KubeJS:

// startup script. will be executed when recipe is loaded
LycheeEvents.customCondition('example_always_true_condition', event => {
    // use ProbeJS for more information about the parameters
    // here you need to return the repeat times that no greater than the given times, or 0 if the condition is not met
    // in this case, the condition is always met
    event.condition.testFunc = (recipe, ctx, times) => times

    // this function is optional
    // will be called when the condition is displayed in JEI/REI on the client side
    // InteractionResult.SUCCESS => checkmark
    // InteractionResult.FAIL    => cross
    // InteractionResult.PASS    => the default "-"
    event.condition.testInTooltipsFunc = () => InteractionResult.SUCCESS

    // it is recommended to cancel the event to prevent the action from being modified by other codes
    event.cancel()
})

Execute Code When Clicking the Info Badge

You can execute code when clicking the info badge in JEI/REI:

// client script
LycheeEvents.clickedInfoBadge('your:recipe_id', event => {
    console.log(event.recipe.id)
    console.log(event.button == 0) // 0 for left click, 1 for right click
})

Examples

Anvil Crafting Recipe to Repair Tools

{
    "type": "lychee:anvil_crafting",
    "item_in": [
        {
            "item": "diamond_sword",
            "lychee:tag": {
                "Damage": 1
            }
        },
        {
            "item": "dirt"
        }
    ],
    "item_out": {
        "item": "diamond_sword"
    },
    "assembling": [
        {
            "type": "nbt_patch",
            "op": "copy",
            "from": "/item_in/0/tag",
            "path": "/item_out/tag"
        },
        {
            "type": "custom",
            "id": "repair_item",
            "target": "/item_out",
            "durability": 1
        }
    ],
    "contextual": {
        "type": "custom",
        "id": "is_item_damaged",
        "target": "/item_in/0"
    }
}
LycheeEvents.customAction('repair_item', event => {
    let durability = event.data.durability
    event.action.applyFunc = (recipe, ctx, times) => {
        let material = ctx.getItem(1)
        let tool = ctx.getItem(2)
        let cost = 0
        for (; cost < material.count && tool.damaged; cost++) {
            tool.setDamageValue(tool.damageValue - durability)
        }
        ctx.materialCost = cost
    }
})

LycheeEvents.customCondition('is_item_damaged', event => {
    let target = LycheeReference.fromJson(event.data, 'target')
    event.condition.testFunc = (recipe, ctx, times) => {
        let indexes = recipe.getItemIndexes(target)
        return ctx.getItem(indexes.get(0)).damaged ? times : 0
    }
})

Anvil Crafting Recipe to Randomize the Trim on Armor

{
    "type": "lychee:anvil_crafting",
    "item_in": [
        {
            "item": "diamond_chestplate",
            "lychee:tag": {
                "Trim": {
                    "material": "minecraft:copper",
                    "pattern": "minecraft:eye"
                }
            }
        },
        {
            "item": "emerald"
        }
    ],
    "item_out": {
        "item": "diamond_chestplate",
        "lychee:tag": {
            "Trim": {
                "material": "minecraft:copper",
                "pattern": "minecraft:eye"
            }
        }
    },
    "assembling": [
        {
            "type": "custom",
            "id": "apply_random_trim"
        }
    ],
    "post": [
        {
            "type": "custom",
            "id": "update_enchantment_seed"
        }
    ],
    "contextual": {
        "type": "custom",
        "id": "is_item_trimmed",
        "target": "/item_in/0"
    }
}
let $RandomSource = Java.loadClass('net.minecraft.util.RandomSource')
let trimPool = ['coast', 'spire', 'rib', 'snout', 'dune']

LycheeEvents.customAction('apply_random_trim', event => {
    event.action.applyFunc = (recipe, ctx, times) => {
        let input = ctx.getItem(0)
        let output = ctx.getItem(2)
        let player = ctx.getParam('this_entity')
        let random = $RandomSource.create()
        //make sure the crafting result consistent
        random.setSeed(player.enchantmentSeed)
        output.setNbt(
            input.nbt.merge({
                Trim: {
                    pattern: trimPool[random.nextInt(trimPool.length)]
                }
            })
        )
    }
})

LycheeEvents.customAction('update_enchantment_seed', event => {
    event.action.applyFunc = (recipe, ctx, times) => {
        let player = ctx.getParam('this_entity')
        player.onEnchantmentPerformed(null, 0) // update seed. null == ItemStack.EMPTY
    }
})

LycheeEvents.customCondition('is_item_trimmed', event => {
    let target = LycheeReference.fromJson(event.data, 'target')
    event.condition.testFunc = (recipe, ctx, times) => {
        let indexes = recipe.getItemIndexes(target)
        let stack = ctx.getItem(indexes.getInt(0))
        return stack?.nbt?.Trim ? times : 0
    }
})

Transforming Item on Depot

{
    "type": "lychee:block_interacting",
    "item_in": {
        "item": "create:wrench"
    },
    "block_in": "create:depot",
    "post": [
        {
            "type": "drop_item",
            "item": "minecraft:cobblestone"
        },
        {
            "type": "prevent_default"
        },
        {
            "type": "custom",
            "id": "consume_item_on_depot"
        }
    ],
    "contextual": {
        "type": "custom",
        "id": "has_item_on_depot",
        "ingredient": {
            "item": "minecraft:stone"
        }
    }
}
let $LevelPlatformHelper = Java.loadClass('dev.latvian.mods.kubejs.platform.LevelPlatformHelper')

LycheeEvents.customAction('consume_item_on_depot', event => {
    event.action.applyFunc = (recipe, ctx, times) => {
        let be = ctx.getParam('block_entity')
        let inv = $LevelPlatformHelper.get().getInventoryFromBlockEntity(be, 'up')
        inv.extractItem(0, 1, false)
    }
})

LycheeEvents.customCondition('has_item_on_depot', event => {
    let ingredient = Ingredient.of(event.data.ingredient)
    event.condition.testFunc = (recipe, ctx, times) => {
        let be = ctx.getParam('block_entity')
        let inv = $LevelPlatformHelper.get().getInventoryFromBlockEntity(be, 'up')
        return ingredient.test(inv.getStackInSlot(0)) ? times : 0
    }
})

Item Inside Recipe with Dynamic Time

{
    "type": "lychee:item_inside",
    "time": 5,
    "block_in": "*",
    "item_in": [
        {
            "item": "yellow_dye"
        }
    ],
    "post": [
        {
            "type": "drop_item",
            "item": "red_dye"
        }
    ],
    "contextual": [
        {
            "type": "custom",
            "id": "neighbor_block_boost",
            "booster_block": "minecraft:red_wool"
        }
    ]
}
let $DirectionPlane = Java.loadClass('net.minecraft.core.Direction$Plane')

LycheeEvents.customCondition('neighbor_block_boost', event => {
    let booster_block = event.data.booster_block

    event.condition.testFunc = (recipe, ctx, times) => {
        let item = ctx.getParam('this_entity')
        let count = item.lychee$getCount()
        if (count != 0) {
            return times
        }
        let pos = ctx.getParam('lychee:block_pos')
        for (const direction of $DirectionPlane.HORIZONTAL) {
            let neighbor = ctx.level.getBlock(pos.relative(direction))
            if (neighbor == booster_block) {
                count += 1
            }
        }
        item.lychee$setCount(count)
        console.info('Neighbor block boost: ' + count)
        return times
    }
})