Расширенные Особенности и Механики

Помимо базового скриптинга, SScript имеет мощные расширенные возможности для сложной логики, обработки ошибок и оптимизации.


Обработка Ошибок с Try-Catch

Базовый Синтаксис

try:
    // Код который может не промыть
    result = http_get("https://api.example.com/data")
    data = result.json
    log data.score
catch err:
    // Обработать ошибку
    log "Error occurred: " + err
end

Обычные Ошибки для Перехвата

try:
    // Неверный путь к файлу
    content = file_read("nonexistent.txt")
catch err:
    log "File not found: " + err
end

try:
    // Timeout сети
    resp = http_get("https://slow-server.com")
catch err:
    log "HTTP error: " + err
end

try:
    // Ошибка парсинга JSON
    obj = json_parse("invalid json")
catch err:
    log "JSON parse failed: " + err
end

try:
    // Деление на нуль или несовместимость типов
    result = 10 / 0
catch err:
    log "Math error: " + err
end

Детали Сообщения об Ошибке

Когда ошибка поймана, err содержит:

  • Тип ошибки (FileNotFound, HttpTimeout, JsonParseError и т.д.)
  • Сообщение (понятное описание)
  • Номер строки (где произошла ошибка)

Пример:

try:
    run "invalid command syntax"
catch err:
    log "Error: " + err  // "Error: CommandSyntaxException at line 42"
end

Селекторы Игроков

SScript выполняет селекторы Minecraft во время выполнения, чтобы найти игроков.

Поддерживаемые Селекторы

// Все онлайн игроки
players = get_targets("@a")

// Все онлайн игроки в пределах 100 блоков
nearby = get_targets("@a[distance=..100]")

// Игроки с определенным тегом
verified = get_targets("@a[tag=verified]")

// Игрок с наименьшим расстоянием
loser = get_target("@a[sort=nearest,limit=1]")

Свойства Селектора

Селектор Эффект Пример
distance Диапазон в блоках @a[distance=0..50], @a[distance=..100]
tag Имеет/нет тег @a[tag=admin], @a[tag=!banned]
scores Баллы в объективе @a[scores={kills=5..}]
gamemode Выживание/Творчество/и т.д @a[gamemode=survival]
limit Макс результатов @a[limit=5]
sort ближайший/случайный/дальний @a[sort=nearest]
name Имя игрока @a[name=Steve]
level Уровень опыта @a[level=10..]

Динамические Селекторы

func find_admins():
    admins = get_targets("@a[tag=admin]")
    log "Found " + str(len(admins)) + " admins"
    return admins
end

on player_join(player):
    nearby = get_targets("@a[distance=0..50,gamemode=survival]")
    for p in nearby:
        tellraw(p.name, player.name + " is nearby!")
    end
end

Локальный и глобальный чат

*.mixin.ss может отменить исходный чат и перенаправить его в local/global режим.

on player_chat(player, message):
    if starts_with(message, "[l]"):
        run "execute at " + player.name + " run tellraw @a[distance=..50] " + json_stringify({"text": player.name + ": " + trim(substring(message, 3))})
        return false
    else:
        run "tellraw @a " + json_stringify({"text": player.name + ": " + message})

        http_post(
            "https://discord.com/api/webhooks/REPLACE_ME",
            {
                "content": player.name + ": " + message,
                "username": "SScript Chat"
            }
        )

        return false
    end
end

Если нужен только local chat без webhook, оставь только ветку с run.


Структура Объекта Игрока и NBT Данные

Объекты игроков содержат данные NBT Minecraft извлеченные во время выполнения:

Базовые Свойства

player.name           // "Steve"
player.uuid           // "a1b2c3d4-..." (уникальный ID)
player.health         // 20.0 (0-20)
player.max_health     // 20.0
player.food_level     // 10 (0-20)
player.saturation     // 5.2 (десятичная)
player.x_pos          // 100.5 (float)
player.y_pos          // 64.2
player.z_pos          // -50.1
player.dimension      // "minecraft:overworld"
player.gamemode       // "survival", "creative", "adventure", "spectator"
player.level          // 42 (уровень XP)
player.experience     // 0.8 (0.0-1.0, десятичная часть)

Продвинутые Свойства

player.on_ground      // true/false
player.flying         // true/false
player.sneaking       // true/false
player.sprinting      // true/false
player.swimming       // true/false
player.fall_distance  // 3.2 (блоки упав)
player.fire           // 0 (тики в огне)
player.air            // 300 (оставшийся воздух под водой)
player.pose           // "standing", "swimming", "falling" и т.д

Пример с Инвентарем

on player_join(player):
    health = player.health
    level = player.level
    
    if health < 5:
        log player.name + " has low health: " + str(health)
    end
    
    if level >= 30:
        tag_add(player.name, "high_level")
    end
end

HTTP Headers и Пользовательские Запросы

Базовые GET/POST (Авто Headers)

// GET запрос - авто Content-Type: application/x-www-form-urlencoded
resp = http_get("https://api.example.com/data")

// POST запрос - авто Content-Type: application/json если body это JSON
resp = http_post("https://api.example.com/data", {"key": "value"})

Полный Запрос с Пользовательскими Headers

response = http_request(
    "POST",
    "https://api.example.com/users",
    {
        "Authorization": "Bearer token123",
        "X-Custom-Header": "myvalue"
    },
    {"name": "Steve", "score": 100}
)

if response.ok:
    log "Request succeeded: " + str(response.status)
else
    log "Request failed: " + str(response.status)
end

Объект Response

response.ok         // true если 200-299 статус
response.status     // 200, 404, 500 и т.д.
response.text       // Сырое тело ответа как строка
response.json       // Парсенный JSON объект (если JSON ответ)
response.headers    // Map ответных headers

Пример: Интеграция с API

func fetch_player_stats(player_name):
    try:
        resp = http_get("https://api.example.com/players/" + player_name)
        if resp.ok:
            stats = resp.json
            return stats  // {"kills": 10, "deaths": 2, "wins": 5}
        else
            log "API error: " + str(resp.status)
            return null
        end
    catch err:
        log "Network error: " + err
        return null
    end
end

on player_join(player):
    stats = fetch_player_stats(player.name)
    if stats:
        tellraw(player.name, "Kills: " + str(stats.kills))
    end
end

Методы Списков

Списки/массивы поддерживают методы для манипуляции:

.add(value) — Добавить Элемент

items = []
items.add("apple")
items.add("banana")
items.add("apple")

log len(items)  // 3

.remove(index) — Удалить по Индексу

items = ["a", "b", "c"]
items.remove(1)      // Удалить "b"

log items           // ["a", "c"]
log len(items)      // 2

Практический Пример

func collect_online_staffs():
    all_players = get_targets("@a")
    staff_list = []
    
    for player in all_players:
        if has_tag(player.name, "staff"):
            staff_list.add(player.name)
        end
    end
    
    return staff_list
end

on player_break_block(player, block):
    staffs = collect_online_staffs()
    
    for staff_name in staffs:
        tellraw(staff_name, player.name + " broke " + block.id)
    end
end

Постоянство Глобальных Переменных

Глобальные переменные сохраняются в sscripts/globals.json и сохраняются при перезагрузке сервера.

Автоматическое Поведение Сохранения

  • Изменения debounced: сохраняются максимум каждые 2 секунды
  • Не сохраняются сразу (чтобы избежать I/O перегрузки)
  • Сохраняются при мягком выключении сервера

Ручные Триггеры Сохранения

set_global("important_data", {"version": 3})
// Будет направлено на диск в течение 2 секунд

// Немедленное сохранение (на следующем tick):
// Выключение сервера вызывает сохранение

Пример: Постоянство Статов Игрока

func record_kill(player_name):
    current = num(get_global("kills_" + player_name))
    set_global("kills_" + player_name, current + 1)
    
    // Автоматически сохранено на диск
end

on player_death(player, location):
    if location.killer:
        record_kill(location.killer)
    end
end

on server_start:
    // Загрузить сохраненные статистики убийств
    log "Loading persistent stats..."
    // (статистика загружена из globals.json)
end

Ограничения Производительности и Пределы

Пределы Выполнения

Лимит Значение Последствие
Макс запущенные процессы 500 Не может создать больше (ставится в очередь вместо)
Процессы созданные за tick 20 Макс 20 новых процессов/tick (предотвращает всплеск лага)
Операторы за tick 50 Процесс ограничен до 50 операторов/tick (распределены по нескольким никам)
Итерации цикла 1,000,000 Бесконечные циклы разрываются (лимит безопасности)
While loop guard Включен Предотвращает убегающие циклы
HTTP timeout 60 секунд Долгие запросы отменяются после 60s
File I/O timeout 30 секунд Файловые операции timeout

Оптимизация Памяти

//  ПЛОХО: Создает 100k список сразу
on server_start:
    huge_list = get_blocks(-1000, 0, -1000, 1000, 100, 1000)
end

//  ХОРОШО: Обработать блоки меньшими кусками
on server_start:
    wait process_region_1()
    wait process_region_2()
    wait process_region_3()
end

func process_region_1():
    blocks = get_blocks(-1000, 0, -1000, 0, 100, 1000)
    log "Processed region 1: " + str(len(blocks))
end

Бюджет Tick

Каждый тик сервера (50ms игровое время), SScript процессы завершают:

  • Макс 20 новых процессов могут быть созданы
  • Каждый запущенный процесс выполняет ~50 операторов
  • Оставшееся время выполняет нормальную логику Minecraft

Последствие: Долгие задачи автоматически распределяются на несколько тиков.


Разрешение Пути Файла

Файлы разрешаются относительно корня сервера:

// Эти эквивалентны (оба создают sscripts/data/players.json):
file_write_json("sscripts/data/players.json", data)
file_write_json("sscripts/data/players.json", data)  // Из корня сервера

// Эти НЕ эквивалентны:
file_write_json("data.json", data)           // Создает в корне сервера
file_write_json("logs/script.log", data)     // Создает в корне сервера/logs

Лучшая практика: Начинайте пути с sscripts/ для организации.


Отладка Расширенных Особенностей

Включить Вывод Отладки

/sscript debug on

Это логирует:

  • Каждый вызов функции
  • Присваивание переменных
  • Оценку селекторов
  • Отправленные HTTP запросы
  • Файловые I/O операции

Трассировка Асинхронного Выполнения

func slow_fetch(player):
    log "[FETCH] Starting for " + player
    wait http_get("https://api.example.com")
    log "[FETCH] Completed for " + player
end

on player_join(player):
    log "[EVENT] Player join: " + player.name
    slow_fetch(player)
    log "[EVENT] Handler complete"
end

// Вывод:
// [EVENT] Player join: Steve
// [EVENT] Handler complete
// [FETCH] Starting for Steve
// [FETCH] Completed for Steve

Смотрите Также


This site uses Just the Docs, a documentation theme for Jekyll.