Examples

Example scripts for various things you can do with KubeJS

FTB Quests Integration

onEvent('ftbquests.custom_task.75381f79', event => {
  log.info('Custom task!')
  event.checkTimer = 20
  event.check = (task, player) => {
    if (player.world.daytime && player.world.raining) {
      task.progress++
    }
  }
})

onEvent('ftbquests.custom_reward.e4f76908', event => {
  log.info('Custom reward!')
  event.player.tell('Hello!')
})

// specific object completion
onEvent('ftbquests.completed.d4f36905', event => {
  if (event.player) {
    event.notifiedPlayers.tell(Text.of(`${event.player.name} completed... something!`).green())
  }
})

// generic 'quest' object completion. Note: There isnt actually a way to get reliable title on server side, so dont use event.object.title
onEvent('ftbquests.completed', event => {
  if (event.player && event.object.objectType.id === 'quest') {
    event.notifiedPlayers.tell(Text.of(`${event.player.name} completed a quest!`).blue())
  }
})

// object with tag 'ding' completion
onEvent('ftbquests.completed.ding', event => {
  event.onlineMembers.playSound('entity.experience_orb.pickup')
})

onEvent('entity.death', event => {
  if(event.server
  && event.source.actual
  && event.source.actual.player
  && event.source.actual.mainHandItem.id === 'minecraft:wooden_sword'
  && event.entity.type === 'minecraft:zombie') {
    event.source.actual.data.ftbquests.addProgress('12345678', 1)
  }
})

Reflection / Java access

Very limited reflection is possible, but is not recommended. Use it in cases when KubeJS doesnt support something.

In 1.18.2+ internal Minecraft classes are remapped to MojMaps at runtime, so you don't have to use obfuscated names if accessing internal Minecraft fields and methods.

 

An example of adding a block with a custom material, built using reflection to get the MaterialJS class, then make a new instance of that with amethyst sounds and material properties from internal Minecraft classes.

// Startup script, 1.18.2
const MaterialJS = java("dev.latvian.mods.kubejs.block.MaterialJS")
const Material = java('net.minecraft.world.level.material.Material')
const SoundType = java('net.minecraft.world.level.block.SoundType')

amethystMaterial = new MaterialJS('amethyst', Material.AMETHYST, SoundType.AMETHYST) // f_164531_ and f_154654_ are the respective obfuscated names of these fields, for older versions

//This builder uses 1.18.2 syntax, it will not work in 1.16 or 1.18.1
onEvent('block.registry', event => {
	event.create('amethyst_slab', 'slab')
		.material(amethystMaterial)// Use the new MaterialJS instance we created as the material
		.tagBlock('minecraft:crystal_sound_blocks')
		.tagBlock('minecraft:mineable/pickaxe')
		.requiresTool(true)
		.texture('texture', 'minecraft:block/amethyst_block')
})

This does come at a cost, it takes 1-2 seconds to load this script, instead of the normal milliseconds. You should import your classes at the top of the script, instead of in an event, especially if the event gets triggered more than once.

Painter API

About

Painter API allows you to draw things on the screen, both from server and directly from client. This can allow you to create widgets from server side or effects on screen or in world from client side.

Currently it doesn't support any input, but in future, in-game menus or even GUIs similar to Source engine ones will be supported.

Paintable objects are created from NBT/Json objects and all have an id. If id isn't provided, a random one will be generated. Objects x and z are absolute positions based on screen, but you can align elements in one of the corners of screen. You can bulk add multiple objects in one json object. All properties are optional, but obviously some you should almost always override like size and position for rectangles.

paint({...}) is based on upsert principle - if object doesn't exist it will create it (if the object also contains valid type), otherwise, update existing:

You can bulk update/create multiple things in same object:

You can remove object with remove: true, bulk remove multiple objects or remove all objects:

These methods have command alternatives:

If the object is re-occuring, it's recommended to create objects at login with all of its static properties and visible: false, then update it later to unhide it. Painter objects will be cleared when players leave world/server, if its persistent, then it must be re-added at login every time.

Currently available objects

Underlined objects are not something you can create

Root

(available for all objects)

rectangle
gradient
text
item

Properties

Available Unit variables
Available Unit constants

Examples

onEvent('player.logged_in', event => {
	event.player.paint({
		example_rectangle: {
			type: 'rectangle',
			x: 10,
			y: 10,
			w: 50,
			h: 20,
			color: '#00FF00',
			draw: 'always'
		},
		last_message: {
			type: 'text',
			text: 'No last message',
			scale: 1.5,
			x: -4,
			y: -4,
			alignX: 'right',
			alignY: 'bottom',
			draw: 'always'
		}
	})
})

onEvent('player.chat', event => {
	// Updates example_rectangle x value and last_message text value to last message + contents from event
	event.player.paint({example_rectangle: {x: '(sin((time() * 1.1)) * (($screenW - 32) / 2))', w: 32, h: 32, alignX: 'center', texture: 'kubejs:textures/item/diamond_ore.png'}})
	event.player.paint({last_message: {text: `Last message: ${event.message}`}})
	// Bulk update, this is the same code as 2 lines above, you can use whichever you like better
	// event.player.paint({example_rectangle: {x: 120}, last_message: {text: `Last message: ${event.message}`}})
	event.player.paint({lava: {type: 'atlas_texture', texture: 'minecraft:block/lava_flow'}})
})

Units

This page describes all functions and operations available for units

Usage

Most basic unit is plain number, such as '1' or '4.5'.

You can use variables with $ like '$example'.

Each function requires name parenthesis and comma separated arguments e.g. 'min(PI, $example)'.

You can combine as many as you want, e.g. 'min(PI, 10 + $example)'.

You can do pretty complex infix, e.g. 'atan2($mouseY, $mouseX) - HALF_PI - HALF_PI / 2'.

Constants
Operations
Functions

Network Packets

This script shows how to use network packets:

// Listen to a player event, in this case item right-click
// This goes in either server or client script, depending on which side you want to send the data packet to
onEvent('item.right_click', event => {
  // Check if item was right-clicked on client or server side
  if (event.server) {
    // Send data {test: 123} to channel "test_channel_1". Channel ID can be any string, but it's recommended to keep it to snake_case [a-z_0-9].
    // Receiving side will be client (because its sent from server).
    event.player.sendData('test_channel_1', { test: 123 })
  } else {
    // It's not required to use a different channel ID, but it's recommended.
    // Receiving side will be server (because its sent from client).
    event.player.sendData('test_channel_2', { test: 456 })
  }
})

// Listen to event that gets fired when network packet is received from server.
// This goes in a client script
onEvent('player.data_from_server.test_channel_1', event => {
  log.info(event.data.test) // Prints 123
})

// Listen to event that gets fired when network packet is received from client.
// This goes in a server script
onEvent('player.data_from_client.test_channel_2', event => {
  log.info(event.data.test) // Prints 456
})

Starting Items

This server script adds items on first time player joins, checking stages. GameStages mod is not required

// Listen to player login event
onEvent('player.logged_in', event => {
  // Check if player doesn't have "starting_items" stage yet
  if (!event.player.stages.has('starting_items')) {
    // Add the stage
    event.player.stages.add('starting_items')
    // Give some items to player
    event.player.give('minecraft:stone_sword')
    event.player.give(Item.of('minecraft:stone_pickaxe', "{Damage: 10}"))
    event.player.give('30x minecraft:apple')
  }
})

FTB Utilities Rank Promotions

With this script you can have FTB Utilities roles that change over time.

Is for 1.12 only. Requires FTB Utilities.

events.listen('player.tick', function (event) {
  // This check happens every 20 ticks, a.k.a every second
  if (event.player.server && event.player.ticksExisted % 20 === 0) {
    var rank = event.player.data.ftbutilities.rank
    events.post('test_event', {testValue: rank.id})
    var newRank = ftbutilities.getRank(rank.getPermission('promotion.next'))

    if (newRank) {
      var timePlayed = event.player.stats.get('stat.playOneMinute') / 20 // Seconds player has been on server
      var timeRequired = newRank.getPermissionValue('promotion.timer').getInt()

      if (timeRequired > 0 && timePlayed >= timeRequired && rank.addParent(newRank)) {
        if (!events.postCancellable('ftbutilities.rank.promoted.' + newRank.id, {'player': event.player, 'rank': newRank})) {
          event.player.tell('You have been promoted to ' + newRank.getPermission('promotion.name') + '!')
        }
        ftbutilities.saveRanks()
      }
    }
  }
})

// When player gets promoted to 'trusted' rank, give them gold ingot (uncomment the line)
events.listen('ftbutilities.rank.promoted.trusted', function (event) {
  // event.data.player.give('minecraft:gold_ingot')
})

3 example roles in ranks.txt:

[player]
power: 1
default_player_rank: true
promotion.name: Player
promotion.next: newcomer
promotion.timer: 5
command.ftbutilities.rtp: false
command.ftbutilities.home: false

[newcomer]
power: 5
promotion.name: Newcomer
promotion.next: regular
promotion.timer: 15
ftbutilities.chat.name_format: <&aNewcomer &r{name}>
command.ftbutilities.rtp: true

[regular]
power: 10
promotion.name: Regular
promotion.next: trusted
promotion.timer: 30
ftbutilities.chat.name_format: <&9Regular &r{name}>
command.ftbutilities.home: true

After 5 seconds of play time, player will be promoted to newcomer.
After 15 seconds (or 10 since previous role) they will be promoted to regular.
After 30 seconds (or 15 since previous role) they will be promoted to trusted, etc.

Clearlag 1.12

This script removes all items from world every 30 minutes. Only works in 1.12.

// Create item whitelist filter that won't be deleted with clearlag
var whitelist = Ingredient.matchAny([
  'minecraft:diamond', // Adds diamond to whitelist
  'minecraft:gold_ingot',
  '@tinkersconstruct', // Adds all items from tinkerscontruct to whitelist
  'minecraft:emerald'
])

// Create variable for last clearlag result
var lastClearLagResult = Utils.newList()
// Create variable for total number of items
var lastTotalClearLagResult = Utils.newCountingMap()

// Create new function that clears lag
var clearLag = server => {
  // Get a list of all entities on server with filter that only returns items
  var itemList = server.getEntities('@e[type=item]')
  // Create new local map for item counters
  var lastResult = Utils.newCountingMap()
  // Clear old result lists
  lastClearLagResult.clear()
  lastTotalClearLagResult.clear()
  // Iterate over each entity in itemList and add item counters
  itemList.forEach(entity => {
    if (!whitelist.test(entity.item)) {
      // Get the name of item
      var key = entity.item.name
      // Add to entity count
      lastResult.add(key, 1)
      // Add to total item count
      lastTotalClearLagResult.add(key, entity.item.count)
      // Kill the item entity
      entity.kill()
    }
  })

  // Update and sort last result list
  lastClearLagResult.addAll(lastResult.entries)
  lastClearLagResult.sort(null)

  // Tell everyone how many items will be removed
  server.tell([
    Text.lightPurple('[ClearLag]'),
    ' Removed ',
    lastTotalClearLagResult.totalCount,
    ' items. ',
    Text.yellow('Click here').click('command:/clearlagresults'),
    ' for results.'
  ])
}

// Listen for server load event
events.listen('server.load', event => {
  // Log message in console
  event.server.tell([ Text.lightPurple('[ClearLag]'), ' Timer started, clearing lag in 30 minutes!' ])
  // Schedule new task in 30 minutes
  event.server.schedule(MINUTE * 30, event.server, callback => {
    // Tell everyone on server that items will be removed
    callback.data.tell([ Text.lightPurple('[ClearLag]'), ' Removing all items on ground in one minute!' ])
    // Schedule a subtask that will clear items in one minute
    callback.data.schedule(MINUTE, callback.data, callback2 => {
      clearLag(callback2.data)
    })
    // Re-schedule this task for another 30 minutes (endless loop)
    callback.reschedule()
  })
})

// Doesnt work in 1.16+!
// Register commands
events.listen('command.registry', event => {
  // Register new OP command /clearlag, that instantly runs clearlag
  event
    .create('clearlag')
    .op()
    .execute(function (sender, args) {
      clearLag(sender.server)
    })
    .add()

  // Register new non-OP command /clearlagresults, that displays stats of all removed items from previous /clearlag
  event
    .create('clearlagresults')
    .execute((sender, args) => {
      sender.tell([ Text.lightPurple('[ClearLag]'), ' Last clearlag results:' ])

      lastClearLagResult.forEach(entry => {
        var total = lastTotalClearLagResult.get(entry.key)

        if (entry.value == total) {
          sender.tell([ Text.gold(entry.key), ': ', Text.red(entry.value) ])
        } else {
          sender.tell([ Text.gold(entry.key), ': ', Text.red(entry.value), ' entities, ', Text.red(total), ' total' ])
        }
      })
    })
    .add()
})

Scheduled Server Events

At server load, you can schedule anything to happen at later time. Within callback handler you can also call callback.reschedule() to repeat this event after initial timer or callback.reschedule(newTime) to change it.

Whatever you pass as 2nd argument will be returned in callback as data.

The example script restarts server after 2 hours but notifies players 5 minutes before that.

onEvent('server.load', function (event) {
  event.server.schedule(115 * MINUTE, event.server, function (callback) {
    callback.data.tell('Server restarting in 5 minutes!')
  })
  
  event.server.schedule(120 * MINUTE, event.server, function (callback) {
    callback.data.runCommand('/stop')
  })
})

Running Commands

Preface

Sometimes, you might want to run a command (such as /tell @a Hi!), in your code.

Most always, there is better method, but sometimes, you just don't want to learn more complicated topics, and just run a command.

Basic Usage

The most basic usage would be to call runCommand() from a server class.

Utils.server.runCommand(`tell @a Hi!`)

If this command returns a message (usually an error) that is normally placed chat, it will be logged. This is not desired outside of debugging situations.

 So instead you can use the following to not log these messages.

Utils.server.runCommandSilent(`tell @a Hi!`)

If the server is not loaded at the time this is ran, then the code will not work.

Although you can use player.runCommandSilent(), it is not recommend as the command runs with the players permission level.

Using the execute command

Commands are ran in the default dimension (the overworld usually) at 0, 0, 0

To get around this, you can use the execute command:

//This example makes a bedrock box around creepers when they spawn
onEvent('entity.spawned', event => {
	if (event.entity.type != "minecraft:creeper") return // the following code only runs when creepers are spawned
	event.server.runCommandSilent(`execute in ${event.entity.level.dimension} positioned ${event.entity.x} ${event.entity.y} ${event.entity.z} run fill ~-1 ~-1 ~-1 ~1 ~2 ~1 bedrock hollow`)
})

 

Spawning Entities

Basics

Overview

Spawning entities consists of 3 steps:

Making a variable to store the entity

Example

level is just a placeholder, in your code it needs to be defined, for many events you can use event.level in place of level and it will work

You can create a entity from a block instead of level, and this is often preferred to learn that, scroll to that section afterward

let myEntity = level.createEntity("cow")
Breaking down the example

Modifying the properties

Example
myEntity.x = 0
myEntity.y = 69
myEntity.z = 0
myEntity.motionY = 0.1
myEntity.noGravity = true
Breaking Down the Example

Spawning the entity

Example
myEnity.spawn()

With understanding from the previous sections you should be able to figure out what this does.

It get myEntity, then calls the method .spawn().

This spawn() method creates the entity in the world.

Note: myEntity is still a variable! So you may not use let myEntity again within the scope! However this variable is still linked to the entity so calling myEntity.motionY = 0.1 will still set the vertical motion of the entity. (This can be a useful thing, but bad if you are unaware)

Creating the entity from a block

You can also call createEntity from a block! This is handy if you want to spawn the entity in the position of a block.

let myEntity = block.createEntity("cow")

Again, block is just a place holder, you will need to change it to something else like maybe event.block for your code to work!

This does not spawn the entity in the center of the block, it just sets the entity's coordinates to that of the block, thus being misaligned

This code offsets the entity to be in the center of the block.

let myEntity = block.createEntity("cow")
myEntity.x+=0.5
myEntity.y+=0.5
myEntity.z+=0.5

Setting NBT

You can set the NBT to whatever you want! It's recommend using mergeFullNBT to do this.

myEntity.withNBT({VillagerData:{}})

myEntity.fullNBT.VillagerData = {} will not work, because .fullNBT is a beaned method, not a property! The only thing that the beaned method lets do is to be able to use let nbt = myEntity.fullNBT to set a variable to NBT to be read or use myEntity.fullNBT = {} to set all of it at once.

Note it is fullNBT not nbt, because kubejs uses nbt for a different purpose. A bit confusing, but it is what it is.

Item Entities

There are two ways to create item entities in KubeJS.

popItem

If you want to easily create the item from a certain block then you can use the popItem method.

Example
block.popItem('minecraft:diamond')

The item can be an Item.of() instead if you wish

createEntity("item")

Creating an item entity with a little more control be done identically to any other entity, except you get a couple more methods.

Example
let itemEntity = block.createEntity("item")
itemEntity.y+=0.8
itemEntity.x+=0.5
itemEntity.z+=0.5
itemEntity.item = Item.of("encahanted_book").enchant("thorns",2)
itemEntity.item.count = 1
itemEntity.pickupDelay = 600
itemEntity.noGravity = true
itemEntity.motionY = 0.08
itemEntity.spawn()

In this example

Examples

Spawns an endermite when braking dirt with a 5% chance

onEvent("block.break", event => {
	if (event.block.id != "minecraft:dirt" || Math.random()  > 0.05) return
  	//only if its dirt and only has 5% chance
  	let myEndermite = event.block.createEntity("endermite")
  	myEndermite.x += 0.5
  	myEndermite.y += 0.5
  	myEndermite.z += 0.5
  	myEndermite.spawn()
})

Turns gravel to sand and drops clay when right clicked with flint

onEvent('block.right_click', event => {
  if (event.block.id == 'minecraft:gravel' && event.item.id == 'minecraft:flint') {
    event.block.set('sand')
    event.item.count--
    event.block.popItem('clay')
  }
})

Overrides enchanting table behavior when clicking on it with an item in you hand. Instead will make the item float up a while, then fall back down.

onEvent('block.right_click', event => {
    if (event.block.id !='minecraft:enchanting_table') return
    if (event.item.count == 0) return
  	event.cancel()
    let item = event.item.copy()
    //if did not use .copy() the item would still be referencing the one in the hand, so setting the count to 1 would set the count in the hand to 1
    item.count = 1
  	event.item.count--
  	
    let itemEntity = event.block.createEntity('item')
    itemEntity.y+=0.8 // on the top of the encahnting table, not in it
    itemEntity.x+=0.5
    itemEntity.z+=0.5
    itemEntity.item = item
    itemEntity.item.count = 1
    itemEntity.pickupDelay = 100
    itemEntity.noGravity = true
    itemEntity.motionY = 0.08
    itemEntity.spawn()
  	
  	function callback (i) {
    	//changes the scope of itemEntity (otherwise if used 2 times in a row within 5 seconds, problems would occur)
    	event.server.scheduleInTicks(100, callback => { // this code runs 5 seconds later
    		i.noGravity = false
    	})
    }
  	callback(itemEntity)
})