Ikar BotHardShiftПланыКонтактыСкачать
Примеры

Примеры

Готовые куски, из которых собирается скрипт: фарм, расходники, баффы, маршруты, диалоги.

На этой странице

Готовые куски, из которых собирается рабочий скрипт. Всё, кроме отдельно отмеченного, работает и во встроенном редакторе, и в плагине-jar.

Числовые id умений и вещей в примерах — иллюстративные, подставляй свои.

Каркас цикла

Основа любого скрипта: перечитывать мир на каждой итерации и уступать управление через delay.

Kotlin
override suspend fun run(bot: L2Bot) {
    bot.log("Старт: ${bot.user.name}")

    while (true) {
        val me = bot.user

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

        // ... логика итерации ...

        delay(500)
    }
}

Фарм: убить моба и подобрать свой дроп

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 возвращает null по таймауту — цикл просто пойдёт дальше и выберет цель заново.

Расходники по порогу

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) вернёт null, если вещи в инвентаре нет, — лишней команды не будет.

Поддержание баффов

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 — «не на перезарядке, не пассивное, не заблокировано». Хватит ли MP, оно не проверяет: если нужно, добавь bot.user.mp > ….

Хил самого раненого в группе

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)
}

У участников группы нет template-id, поэтому цель задаётся строго по oid — через castSkillByOid.

Маршрут по геодате

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("Иду в $x / $y")
                if (!bot.moveToByGeo(x, y, z, timeoutMs = 0)) {
                    bot.log("Не дошёл — беру следующую точку")
                }
                delay(500)
            }
        }
    } finally {
        bot.enableAutomation()
    }
}

timeoutMs = 0 снимает дедлайн: команда ждёт столько, сколько идётся. Зависнуть навсегда она не может — исчерпав попытки перестроить маршрут, вернёт false.

Автоматизация выключается на время ходьбы, иначе она параллельно потянет персонажа к своим целям. finally возвращает её обратно даже при остановке скрипта.

Диалог с 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
}

Если цель уже выбрана — руками или предыдущей командой — диалог открывается без указания NPC:

Kotlin
bot.openDialog()
bot.log(bot.dialogText)      // HTML открытого диалога
bot.selectDialog(1)          // первый пункт

Ответ на приглашение в группу

Вариант для редактора — дождаться окна-вопроса:

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 — постоянный фоновый обработчик:

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) {
        // основная логика
        delay(500)
    }
}

Смерть и возврат в город

Kotlin
if (bot.user.dead) {
    bot.log("Умер — возвращаюсь в город")
    bot.goHome(RestartType.TOWN)
    delay(5000)
    continue
}

Перегруз

Kotlin
if (bot.user.weightPenalty > 0) {
    bot.log("Перегруз — иду разгружаться")
    bot.disableAutomation()
    bot.moveToByGeo(townX, townY, townZ, timeoutMs = 0)
    // ... продажа через диалог NPC ...
    bot.enableAutomation()
}

Обход персонажей аккаунта

restart() уводит на экран выбора, selectCharacter(slot) заводит в мир нужного. Слоты нумеруются с нуля, в том же порядке, что на экране выбора.

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

for (slot in slots) {
    if (!bot.restart()) {
        bot.log("Не удалось выйти на экран выбора")
        break
    }
    if (!bot.selectCharacter(slot)) {
        bot.log("Слот $slot не зашёл — пропускаю")
        continue
    }

    // Мир загружается не мгновенно: сразу после входа список NPC ещё пуст.
    delay(3000)
    bot.log("${bot.user.name}: рядом ${bot.npcs.size} NPC")
}

После смены персонажа мир — другой: user, инвентарь, скиллы и окружение не имеют ничего общего с прежними. Всё, что запомнил до смены (oid-ы, цели, дропы), выбрось и перечитай bot.* заново.

Собранный скрипт целиком

Плагин-jar со всеми кусками вместе.

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 запущен на ${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)
        }
    }
}

Не забудь зарегистрировать класс в META-INF/services/com.ikar.script.api.IkaScript — см. начало справочника.