Ikar BotHardShiftRoadmapContactGet
Examples

Examples

Working pieces to assemble a script from: farming, consumables, buffs, routes, dialogs.

On this page

Ready-made pieces you can assemble a working script from. Unless noted otherwise, everything works both in the built-in editor and in a jar plugin.

Skill and item ids in the examples are illustrative — substitute your own.

The loop skeleton

The backbone of any script: re-read the world every iteration and yield control with delay.

Kotlin
override suspend fun run(bot: L2Bot) {
    bot.log("Started: ${bot.user.name}")

    while (true) {
        val me = bot.user

        if (me.dead) {
            delay(3000)
            continue
        }
        if (me.sitting) bot.stand()

        // ... iteration logic ...

        delay(500)
    }
}

Farming: kill a mob and pick up your drop

Kotlin
override suspend fun run(bot: L2Bot) {
    while (true) {
        val mob = bot.npcs.nearest { it.attackable && !it.dead && it.distToSelf <= 1500 }
        if (mob == null) {
            delay(1000)
            continue
        }

        if (mob.distToSelf > 300) bot.moveToByGeo(mob, timeoutMs = 5000)

        bot.attack(mob)
        bot.waitEvent<ScriptEvent.Died>(20_000) { it.oid == mob.oid }

        val drop = bot.drops.nearest { it.isMine && it.distToSelf <= 250 }
        if (drop != null) {
            if (drop.distToSelf > 60) bot.moveToByGeo(drop, timeoutMs = 4000)
            bot.pickup(drop)
        }

        delay(300)
    }
}

waitEvent returns null on timeout — the loop simply moves on and picks a target again.

Consumables by threshold

Kotlin
private suspend fun useConsumables(bot: L2Bot) {
    val me = bot.user
    if (me.hpPercent < 0.5f && me.item(1061) != null) bot.useItem(1061)
    if (me.mpPercent < 0.3f && me.item(728) != null) bot.useItem(728)
}

me.item(itemId) returns null when the item isn't in the inventory, so no pointless command goes out.

Keeping buffs up

Kotlin
private val myBuffs = listOf(1204, 1085, 1068)

private suspend fun keepBuffs(bot: L2Bot) {
    if (bot.user.sitting || bot.user.isCasting) return

    for (id in myBuffs) {
        if (bot.user.hasBuff(id)) continue

        val skill = bot.user.skill(id) ?: continue
        if (!skill.ready) continue

        bot.castSkill(id)
        delay(1200)
    }
}

skill.ready means "off cooldown, not passive, not blocked". It does not check whether you have the MP — add bot.user.mp > … if you need that.

Healing the most wounded party member

Kotlin
private suspend fun healParty(bot: L2Bot, healSkillId: Int, threshold: Float) {
    if (bot.user.isCasting || bot.user.sitting) return

    val skill = bot.user.skill(healSkillId) ?: return
    if (!skill.ready) return

    val hurt = bot.party
        .filter { it.hpPercent < threshold }
        .minByOrNull { it.hpPercent } ?: return

    bot.castSkillByOid(healSkillId, hurt.oid)
}

Party members have no template id, so the target is given strictly by oid — through castSkillByOid.

A geodata route

Kotlin
override suspend fun run(bot: L2Bot) {
    val route = listOf(
        Triple(82500, 148000, -3470),
        Triple(83700, 148900, -3400),
        Triple(84600, 147500, -3390),
    )

    bot.disableAutomation()
    try {
        while (true) {
            for ((x, y, z) in route) {
                bot.log("Heading to $x / $y")
                if (!bot.moveToByGeo(x, y, z, timeoutMs = 0)) {
                    bot.log("Didn't make it — taking the next point")
                }
                delay(500)
            }
        }
    } finally {
        bot.enableAutomation()
    }
}

timeoutMs = 0 removes the deadline: the command waits as long as the walk takes. It still cannot hang forever — once replanning attempts run out it returns false.

Automation is switched off for the walk, otherwise it would pull the character towards its own goals at the same time. finally restores it even if the script is stopped.

Talking to an NPC

Kotlin
private suspend fun talk(bot: L2Bot, npcId: Int, option: String): Boolean {
    if (!bot.openDialog(npcId)) return false
    delay(300)
    if (!bot.selectDialog(option)) return false
    delay(300)
    return true
}

If the target is already selected — by hand or by a previous command — the dialog opens without naming an NPC:

Kotlin
bot.openDialog()
bot.log(bot.dialogText)      // HTML of the open dialog
bot.selectDialog(1)          // first entry

Answering a party invitation

Editor-friendly version — wait for the confirmation window:

Kotlin
val ask = bot.waitEvent<ScriptEvent.ConfirmDialogReceived>(60_000)
if (ask != null) {
    val accept = ask.sender == "MyLeader"
    bot.confirmDialog(ask.msgId, ask.requestId, accept)
}

Jar plugin version — a permanent background handler:

Kotlin
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.collect

override suspend fun run(bot: L2Bot) = coroutineScope {
    launch {
        bot.events.filterIsInstance<ScriptEvent.ConfirmDialogReceived>().collect { ask ->
            bot.confirmDialog(ask.msgId, ask.requestId, ask.sender == "MyLeader")
        }
    }

    while (true) {
        // main logic
        delay(500)
    }
}

Death and going back to town

Kotlin
if (bot.user.dead) {
    bot.log("Died — returning to town")
    bot.goHome(RestartType.TOWN)
    delay(5000)
    continue
}

Overweight

Kotlin
if (bot.user.weightPenalty > 0) {
    bot.log("Overweight — going to unload")
    bot.disableAutomation()
    bot.moveToByGeo(townX, townY, townZ, timeoutMs = 0)
    // ... sell through an NPC dialog ...
    bot.enableAutomation()
}

Walking the characters on an account

restart() goes to the selection screen, selectCharacter(slot) brings the chosen one into the world. Slots are zero-based, in the same order as on the screen.

Kotlin
val slots = listOf(0, 1, 2)

for (slot in slots) {
    if (!bot.restart()) {
        bot.log("Could not reach the selection screen")
        break
    }
    if (!bot.selectCharacter(slot)) {
        bot.log("Slot $slot did not enter — skipping")
        continue
    }

    // The world does not load instantly: right after entering, the NPC list is still empty.
    delay(3000)
    bot.log("${bot.user.name}: ${bot.npcs.size} NPCs nearby")
}

After a switch the world is a different character: user, the inventory, the skills and the surroundings have nothing in common with the previous ones. Drop everything remembered before the switch (oids, targets, drops) and re-read bot.*.

The whole script

A jar plugin with all the pieces put together.

Kotlin
package com.example.myscript

import com.ikar.script.api.IkaScript
import com.ikar.script.api.L2Bot
import com.ikar.script.api.ScriptMeta
import com.ikar.script.api.model.nearest
import com.ikar.script.api.waitEvent
import com.ikar.script.protocol.ScriptEvent
import kotlinx.coroutines.delay

class Farmer : IkaScript {

    override val meta = ScriptMeta(id = "farmer", name = "Farmer")

    private val myBuffs = listOf(1204, 1085, 1068)

    override suspend fun run(bot: L2Bot) {
        bot.log("Farmer started on ${bot.user.name}")

        while (true) {
            val me = bot.user

            if (me.dead) {
                bot.goHome()
                delay(5000)
                continue
            }
            if (me.sitting) bot.stand()

            useConsumables(bot)
            keepBuffs(bot)

            val mob = bot.npcs.nearest { it.attackable && !it.dead && it.distToSelf <= 1500 }
            if (mob == null) {
                delay(1000)
                continue
            }

            if (mob.distToSelf > 300) bot.moveToByGeo(mob, timeoutMs = 5000)
            bot.attack(mob)
            bot.waitEvent<ScriptEvent.Died>(20_000) { it.oid == mob.oid }

            val drop = bot.drops.nearest { it.isMine && it.distToSelf <= 250 }
            if (drop != null) {
                if (drop.distToSelf > 60) bot.moveToByGeo(drop, timeoutMs = 4000)
                bot.pickup(drop)
            }

            delay(300)
        }
    }

    override fun onStop() {}

    private suspend fun useConsumables(bot: L2Bot) {
        val me = bot.user
        if (me.hpPercent < 0.5f && me.item(1061) != null) bot.useItem(1061)
        if (me.mpPercent < 0.3f && me.item(728) != null) bot.useItem(728)
    }

    private suspend fun keepBuffs(bot: L2Bot) {
        if (bot.user.isCasting) return
        for (id in myBuffs) {
            if (bot.user.hasBuff(id)) continue
            val skill = bot.user.skill(id) ?: continue
            if (!skill.ready) continue
            bot.castSkill(id)
            delay(1200)
        }
    }
}

Don't forget to register the class in META-INF/services/com.ikar.script.api.IkaScript — see the start of the reference.