-
Notifications
You must be signed in to change notification settings - Fork 41
Plugin API
This section will explain how to use the various components of the plugin API.
- What is the API?
- Event interception functions
- Helper properties & functions
- Attribute delegates
- Shop building
- Loot tables
The API is a set of properties, functions, and classes that provide scripting functionality. It acts as a bridge between Java and Kotlin, caching interpreted listeners that events will be posted to.
The rest of this section will elaborate on the specific functionality that the API provides for scripts.
These kinds of functions listen for events of a specific type. They use event receivers for all functions, which means that they have direct access to all event functions and properties.
import api.predef.*
on(LoginEvent::class) {
// We can use 'plr' directly from 'LoginEvent'
// In other words, 'this' = 'LoginEvent'
plr.sendMessage("You have just logged in! Welcome!")
}
The main event interception function is on
. It does not terminate events by default.
import api.predef.*
// Event will continue traversing!
on(CommandEvent::class) {
plr.sendMessage("Command entered was $name!")
}
To terminate events explicitly, use this.terminate()
import api.predef.*
on(CommandEvent::class) {
plr.sendMessage("Command entered was $name!")
terminate() // Event will stop traversing!
}
Implicit terminations happen when on.filter
or optimized Matcher
functions are used.
import api.predef.*
on(WidgetFirstItemClickEvent::class)
.filter { widgetId == 5068 }
.then { ... } // Run and terminate event if condition is satisfied
// Optimized matcher functions
cmd("mypos", RIGHTS_DEV) { ... }
button(4100) { ... }
However, streamlined filtering that does not terminate the event is still possible through on.condition
.
import api.predef.*
// Does not terminate the event.
on(WidgetFirstItemClickEvent::class)
.condition { widgetId == 5068 }
.then { ... }
Please keep in mind that keys can only be matched to one event listener. Subsequent match attempts with the same key will throw DuplicateMatchException
.
import api.predef.*
// A DuplicateMatchException will be thrown here!!
button(1) { doSomething() }
button(1) { doSomethingElse() }
This behavior exists so that matches won't be 'silently' overwritten by future code.
For more information, see the api.event
package.
Helper properties and functions are available to every script by declaring import api.predef.*
.
For more information, see the api.predef
package.
Attributes are temporary (transient) or permanent (persistent) values assigned to players. Delegates allow us to access attributes as if they were properties.
The syntax for using them is as follows
var Player.myAttribute by Attr<Int>("my_attribute")
fun example(plr: Player) {
plr.myAttribute = rand(1, 10)
plr.sendMessage("my_attribute value is ${plr.myAttribute}")
}
A delegate also exists for creating compact attribute timers.
var Player.myTimer by Stopwatch("my_timer")
fun example(plr: Player) {
// Satisfied if uninitialized or time since last reset > 2000ms
if(plr.myTimer > 2000) {
plr.sendMessage("someFunction can only be called once every 2000ms")
// Reset the timer
plr.myTimer = -1
}
}
The api.shop
package uses receiver functions to create a compact and elegant DSL for building shops.
A simple shop declaration with no items or open listeners looks like
shop {
name = "Test Shop"
buy = BuyPolicy.NONE // Shop won't buy any player's items
restock = RestockPolicy.SLOW // Items will restock very slow
currency = Currency.COINS // Takes coins
}
We can stock shops with items by using sell
shop("Test Store") {
buy = BuyPolicy.NONE
restock = RestockPolicy.SLOW
currency = Currency.COINS
sell {
"Abyssal whip" x 10
"Dragon scimitar" x 15
noted {
"Lobster" x 500
}
...
}
}
And finally, we can add some event listeners using open
. The properties inside are forwarded to optimized Matcher
functions (and have the same names).
shop("Test Store") {
buy = BuyPolicy.NONE
restock = RestockPolicy.SLOW
currency = Currency.COINS
sell {
"Abyssal whip" x 10
"Dragon scimitar" x 15
noted {
"Lobster" x 500
}
...
}
open {
// NPC(520) second click opens this shop
npc2 = 520
// Object(2772) first click opens this shop
object1 = 2772
}
}
For more complex shop opening logic, an explicit event listener can be declared instead.
Loot tables are the equivalent to Runescape drop tables. These are used for a variety of things including
- Treasure trails
- NPC drops
- Barrows
- Crystal chest
To create a loot table, use lootTable
. A basic table looks like so
lootTable {
rarity(COMMON) { // Use 'rarity' blocks to begin listing items
"Tinderbox" x 1
"Hammer" x 1 // Then list item names (or identifiers) and amounts
}
rarity(VERY_RARE) {
"Abyssal whip" x 1
"Coins" x 100_000..200_000
"Death rune" x 1000..2000
}
}
To add noted items, use noted
blocks
lootTable {
rarity(COMMON) {
"Tinderbox" x 1
"Hammer" x 1
}
rarity(UNCOMMON) {
"Rune kiteshield" x 1..3
"Dragon dagger" x 1..2
}
rarity(VERY_RARE) {
"Abyssal whip" x 1
"Coins" x 100_000..200_000
"Death rune" x 1000..2000
// Noted block makes items noted, with underlying rarity
noted {
"Iron ore" x 25..50
"Gold ore" x 5..10
}
}
}
To select items from a loot table, use pick
and pickAll
.
val myLootTable = ... ;
on(ServerLaunchEvent::class) {
// 'pick' rolls on a single random item
println(myLootTable.pick()?.itemDef?.name)
// 'pickAll' rolls on ALL items
for(item in myLootTable.pickAll()) {
println(item.itemDef.name)
}
}
For more information, see the api.item
package.