KubeJS Examples
These scripts show a complete setup: a startup script registers the content items, and server scripts attach rules and react to events. See the API Reference for every method used here.
Registering Items and Recipes
StartupEvents.registry('item', event => {
event.create('spirit_manual').displayName('Nameless Cultivation Manual')
event.create('spirit_stone').displayName('Spirit Stone')
})
ServerEvents.recipes(event => {
event.shaped('example:spirit_manual', ['ABA', ' C ', 'ABA'], {
A: 'minecraft:paper',
B: 'minecraft:lapis_lazuli',
C: 'minecraft:book'
})
})The item is registered by KubeJS, so the rest of the mod sees it as a normal item and can bind rules to its real item ID.
Reacting to Events
// kubejs/server_scripts/mxt_events.js
MxtEvents.abilityUse(event => {
if (event.isPre() && event.getAbility() === 'example:forbidden') event.cancel()
})
MxtEvents.resourceConsume(event => {
if (event.isPre()) event.setAmount('example:spirit_power', event.getAmounts()['example:spirit_power'] || 0)
})
MxtEvents.cultivationBreak(event => {
if (event.getPhase() === 'Pre') {
// event.getEvent() is the native CultivationBreakEvent.Pre.
event.getEvent().setCost('example:spirit_power', 20)
}
})The matching data pack can bind example:spirit_manual to a technique and hook example:spirit_stone into item_aura or currency. That way the script only registers content, while the rules stay data-driven and are synchronised to every client.
Defining a Script Action
Register the callback under a namespaced ID in a server script:
MxtActions.entity('example:heal', (entity, params) => {
entity.heal(params.amount || 1)
})Any matching data pack field can then use it through the pre-registered mxt:js type:
{
"type": "mxt:js",
"id": "example:heal",
"params": { "amount": 4 }
}The callback may take a third argument: the formula context of the dispatch, which carries the values only the triggering event knows. This one scales its effect by the damage that caused it:
MxtActions.entity('example:knockback_on_hit', (entity, params, context) => {
const damage = context.explicit('damage')
if (Number.isNaN(damage)) return
entity.push(0, params.strength * damage, 0)
})Defining a Script Cost
A cost is checked and then paid, so it registers both halves at once. The id is what a data pack puts in a costs array:
MxtCosts.register('example:quest_token',
(player, params, context) => player.persistentData.getInt('tokens') >= (params.count || 1),
(player, params, context) => {
player.persistentData.putInt('tokens', player.persistentData.getInt('tokens') - (params.count || 1))
}
){"type": "mxt:js", "id": "example:quest_token", "params": {"count": 3}}A script cost needs a player, and its context is built from that player alone, so read player state (or the explicit values of that context) rather than an event payload.
Running a Built-in Action from a Script
MxtActions.executeEntity(player, {
type: 'mxt:heal',
amount: 4
})The definition object has the same shape as a single action inside a data pack, so any built-in action type works here.
Testing a Built-in Condition
const enoughQi = MxtConditions.testEntity(player, {
type: 'mxt:resource_compare',
resource: 'mxt:spirit_power',
min: 10
})Evaluating Number Providers
const levelScaled = MxtValues.evaluateNumber(player, {
type: 'mxt:expression',
expression: 'level * 2 + 1'
})
const actualAura = MxtValues.evaluateResource(player, 'mxt:spirit_power', {
type: 'mxt:actual_concentration'
})Paying Several Resources Atomically
const result = MxtResources.consume(player, [
{ id: 'mxt:spirit_power', amount: 10 },
{ id: 'mxt:fire_aura', amount: 'level + 2' }
])
if (result.committed()) {
console.info(`Deducted: ${result.amounts()}`)
} else {
console.warn(`Insufficient resource: ${result.failedResource()}`)
}When any one of the resources is insufficient, none of them is deducted. Each entry of a MxtResources.consume array is a resource cost, so its fields are id and amount. The single Cost API also accepts the typed form with a resource field:
// Consume a resource. Resource cost in MxtResources.consume, and the id shorthand of the cost API.
{ id: 'mxt:spirit_power', amount: 10 }
// Consume a resource, typed form accepted by the single Cost API.
{ type: 'mxt:resource', resource: 'mxt:spirit_power', amount: 10 }
// Consume items. items accepts an item ID, an item tag, or an ItemMatcher object.
{ type: 'mxt:item', items: ['minecraft:emerald', '#c:mystic_gems'], amount: 2 }Casting an Ability
const result = MxtAbilities.use(player, 'example:fireball')
if (result.failure() !== null) console.warn(String(result.failure()))Only an ability the entity already holds can be cast this way, and the call only takes effect on the server.
Publishing and Waiting for a Trigger Signal
A script can raise the same signal a data pack ability raises, and another script can wait for it. Subscriptions are runtime-only, so arm them from a runtime hook and re-arm them after a reload:
// kubejs/server_scripts/mxt_triggers.js
const KEY = 'example:toxicity_watch'
function onPillTaken(signal) {
const toxicity = signal.context().formula().explicit('toxicity')
if (!Number.isNaN(toxicity)) console.info(`${signal.type()} with toxicity ${toxicity}`)
}
function watch(entity) {
// Re-registering the same key replaces the previous subscription.
if (!MxtTriggers.has(entity, KEY)) {
MxtTriggers.subscribe(entity, 'example:pill_taken', KEY, onPillTaken)
}
}
EntityEvents.spawned(event => watch(event.entity))
// /reload re-evaluates this script and drops its subscriptions; this re-arms the players
// who are still online.
ServerEvents.tick(event => event.server.players.forEach(watch))
// Publish from your own logic: a command handler, a quest hook, or an item use.
function publishPillTaken(player, toxicity) {
MxtTriggers.publish(player, 'example:pill_taken', {
pill: 'example:returning_pill',
toxicity
})
}
// A quest step that must fire once removes itself after the first matching signal.
function watchFirstPill(player) {
MxtTriggers.subscribeOnce(player, 'example:pill_taken', 'example:first_pill', signal => {
console.info(`first pill taken at ${signal.gameTime()}`)
})
}