> For the complete documentation index, see [llms.txt](https://docs.allout.game/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.allout.game/all-out-docs/ru/skripting/syntax.md).

# Начало работы с CSL

CSL — это собственный язык программирования All Out, созданный для того, чтобы писать многопользовательские игры было так же просто, как создавать однопользовательские игры.

CSL — это собственный язык сценариев All Out. Он статически типизирован и больше всего похож на Go/Odin — за исключением **игровое состояние автоматически синхронизируется с сервера на клиентов**.

{% hint style="info" %}
Вам не нужно писать RPC, SyncVars или собственную репликацию. Мы сделаем это за вас!
{% endhint %}

## Ваш первый скрипт (`main.csl`)

Когда вы создаёте новый проект, All Out генерирует `main.csl` в папке `scripts/` .

```go
import "core:ao"

// ============================================================================
// Глобальный цикл жизни
// ============================================================================

ao_before_scene_load :: proc() {
    // Зарегистрируйте определения предметов, валют и т. д.
    // Выполняется после создания пустой Scene, до загрузки содержимого сцены.
}

ao_start :: proc() {
    // Вызывается один раз при запуске сцены.
}

ao_update :: proc(dt: float) {
    // Вызывается каждый кадр.
}

ao_late_update :: proc(dt: float) {
    // Вызывается каждый кадр после ao_update.
}

ao_draw :: proc(dt: float) {
    // Только для визуального оформления. Интерактивный UI относится к ao_update/ao_late_update.
}

// ============================================================================
// Цикл жизни игрока
// ============================================================================

Player :: class : Player_Base {
    ao_start :: method() {
    }

    ao_update :: method(dt: float) {
    }

    ao_late_update :: method(dt: float) {
    }

    ao_draw :: method(dt: float) {
        // Только для визуального оформления. Никогда не интерактивный UI.
    }

    ao_end :: method() {
    }
}
```

Например, выводите сообщение в лог при подключении каждого игрока:

```go
Player :: class : Player_Base {
    ao_start :: method() {
        log_info("hello %", {this.get_username()});
    }
}
```

Если вам нужно более глубокое объяснение того, когда выполняются эти функции, см. [Цикл жизни игры/кадра](/all-out-docs/ru/skripting/game-frame-lifecycle.md).

## Импорты

Ваш `main.csl` должен импортировать `core:ao` и любые папки, которые вы создаёте (например, `ui/`, `abilities/`, и т. д.).

```go
import "core:ao"
import "ui"
```

Импорты указывают на папки, а не на отдельные файлы. Импорт папки включает `.csl` файлы в этой папке. Импорты внутри этой папки разрешаются относительно неё:

```go
// main.csl
import "core:ao"
import "abilities"

// abilities/projectiles.csl
import "helpers"
```

{% hint style="warning" %}
В большинстве проектов, **импортируйте в `main.csl` только**. Не разбрасывайте импорты по множеству файлов — в итоге у вас возникнут запутанные проблемы с порядком/видимостью.
{% endhint %}

## Объявления (переменные и константы)

Объявления связывают имя со значением.

### Переменные

```go
// Общий вид
<name>: <type> = <expression>;
```

Можно опустить либо `<type>` либо `<expression>` :

```go
// Явный тип
my_value: int = 42;

// Вывод типа
my_value := 42; // выведен как int

// Нулевая инициализация (то же, что my_value := 0;)
my_value: int;
```

### Константы

Константы используют `::` и должны быть константами времени компиляции. Это могут быть скаляры, строки, типы, значения процедур, массивы или составные литералы:

```go
MAX_PLAYERS :: 12;

Spawn_Desc :: struct {
    name: string;
    pos: v2;
}

DEFAULT_SPAWNS: []Spawn_Desc : {
    {"Синий", {-4, 0}},
    {"Красный", {4, 0}},
};
```

Это недопустимо (потому что `a` не является константой времени компиляции):

```go
a := 123;
b :: a; // ошибка компиляции
```

Инициализаторы глобальных переменных тоже должны быть константами времени компиляции. Используйте `ao_before_scene_load` либо `ao_start` для инициализации во время выполнения.

### Глобальные переменные

Глобальные переменные используют тот же синтаксис объявления, что и локальные. Их можно оставить с нулевой инициализацией или инициализировать константами времени компиляции, включая структуры, массивы, значения процедур и `typeid` значения:

```go
score_total: int;
spawn_names: []string = {"Синий", "Красный"};
default_type: typeid = int;

start_round :: proc() {
}

on_match_start: proc() = start_round;
```

Глобальные переменные изменяемы и сохраняются на протяжении жизни экземпляра скрипта. Hotload сохраняет их текущие значения, поэтому изменённый инициализатор применяется только после перезапуска игры; hotload действительно применяет изменения к `::` константам, поэтому объявляйте таблицы данных только для чтения как константы. Не используйте глобальные переменные для игрового состояния отдельных игроков.

## Типы

### Примитивные типы

* Знаковые целые: `s8`, `s16`, `s32`, `s64`
* Беззнаковые целые: `u8`, `u16`, `u32`, `u64`
* Логические: `bool`
* С плавающей точкой: `f32`, `f64`
* Псевдонимы:
  * `int` == `s64`
  * `uint` == `u64`
  * `float` == `f32`
* Векторы: `v2`, `v3`, `v4`
* `string`
* `typeid`
* `any`

### Числовые литералы

Дробные литералы могут включать или опускать ноль перед десятичной точкой. Например, `.52` и `-.52` эквивалентны `0.52` и `-0.52`:

```go
opacity := .75;
offset := v2{-.52, 1.22};
```

### Типы векторов

`v2` имеет `.x`, `.y`; `v3` добавляет `.z`; `v4` добавляет `.w` — все поля float:

```go
pos: v2 = {10, 20};        // x=10, y=20
color: v4 = {1, 0, 0, 1};  // красный с alpha=1
offset := v3{1, 4, 9};     // вывод типа
```

## Структуры и классы

Структуры — это **значимые типы** (копируются при присваивании). Классы — это **ссылочные типы** (вы создаёте их с помощью `new`).

### Структуры (значимые типы)

```go
Food_Definition :: struct {
    name: string;
    food_value: int;
}

food: Food_Definition;
food.name = "Apple";
food.food_value = 10;
```

### Классы (ссылочные типы)

```go
Foo :: class {
    value: int = 10;
    position: v2 = {12, 34};
}

foo := new(Foo);
```

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

```go
Enemy :: class {
    health: int = 100;
    speed: float = 3.0;
}

Boss :: class : Enemy {
    health = 500;
    speed = 1.5;
}
```

### Наследование

Структуры/классы могут наследоваться от других структур/классов:

```go
Animal :: class {
    name: string;
    age: int;
}

Dog :: class : Animal {
    breed: string;
}

dog := new(Dog);
dog.name = "Buddy";
dog.age = 5;
dog.breed = "Labrador";
```

Методы производного класса с тем же именем затеняют унаследованные методы. Вызовы не виртуальны: поиск метода использует статический тип получателя, поэтому вызов через значение базового типа никогда не выберет метод с тем же именем из производного типа.

Производные классы предоставляют доступное только для чтения поле `base` , которое рассматривает тот же объект как его непосредственный родительский тип. Это позволяет явно начинать поиск метода с родительского типа:

```go
Animal :: class {
    speak :: method() {
        log_info("животное");
    }
}

Dog :: class : Animal {
    speak :: method() {
        base.speak();
        log_info("собака");
    }
}

dog := new(Dog);
dog.speak();        // Dog.speak
dog.base.speak();   // Animal.speak

animal: Animal = dog;
animal.speak();     // Animal.speak, а не Dog.speak
```

`base` не является зарезервированным словом. Локальное объявление с именем `base` обычно затеняет унаследованное поле.

## Процедуры и методы

### Процедуры (`proc`)

```go
add :: proc(a: int, b: int) -> int {
    return a + b;
}

result := add(2, 4); // 6
```

Процедуры, возвращающие несколько значений, должны заключать типы возвращаемых значений в скобки:

```go
get_thing :: proc() -> (Thing, bool) {
    return g_thing, true;
}

thing, ok := get_thing();
```

Используйте ту же форму с разделением запятыми с `=` для присваивания результатов существующим местам назначения. Присваивание может потреблять либо одно выражение с несколькими возвращаемыми значениями, либо по одному выражению на каждое место назначения:

```go
thing: Thing;
ok := false;
thing, ok = get_thing();
a, b = b, a;
```

Количество мест назначения должно совпадать с количеством значений. Правые выражения вычисляются слева направо, прежде чем будет разрешено или записано какое-либо место назначения. Это делает обмен значений безопасным, и `_` можно игнорировать значение:

```go
a, b = make_pair(b, a);
_, ok = get_thing();
```

Разделённые запятыми выражения справа поддерживаются специально операторами присваивания; они не создают кортежное значение, которое можно использовать в другом выражении.

Процедуры — обычные значения, и их можно присваивать/хранить как любое другое значение:

```go
op := proc "op_add" (a: int, b: int) -> int { return a + b; };
op = proc "op_mul" (a: int, b: int) -> int { return a * b; };
```

Каждой процедуре нужна уникальная идентичность, чтобы hotloading мог сопоставлять старый и новый код. Именованные объявления (`add :: proc(...)`) получают это автоматически. Значение proc остаётся анонимным — по одному анонимному proc на область видимости допустимо, но если в области видимости их больше одного, дайте каждому уникальное имя ссылки с помощью `:=`/`=` proc "name" (...) `или компилятор сообщит о конфликте идентификатора ссылки.` или компилятор сообщит о конфликте идентификатора ссылки.

### Методы (`method`)

Используйте `method()` внутри структуры/класса. Методы имеют неявный `this` параметр-ссылку.

```go
Dog :: class {
    name: string;

    bark :: method() {
        log_info("% говорит гав!", {name});
    }
}

dog := new(Dog);
dog.name = "Buddy";
dog.bark();
```

### Доступ к полям vs вызовы методов

Используйте `.` для полей и методов:

```go
hp := player.health;        // доступ к полю
player.respawn();          // вызов метода
entity.set_local_scale({2, 2});
```

{% hint style="info" %}
Любую процедуру можно вызвать как «метод», если её первый параметр соответствует типу получателя. Настоящие методы и поля с типом proc на типе имеют приоритет, прежде чем CSL перейдёт к подходящей свободной процедуре.
{% endhint %}

## Массивы

В CSL есть несколько типов, похожих на массивы, которыми вы будете пользоваться постоянно:

* **Фиксированные массивы**: `[4]int`
* **Срезы / управляемые массивы**: `[]T` (часто используются как «только для чтения» представление массива)
* **Динамические массивы**: `[..]T` (список с изменяемым размером)
* **Неуправляемые массивы**: `[^]T` (используются в сигнатурах встроенного API, таких как `format_string`, `log_info`, и т. д. — передавайте значения как `{a, b, c}`)

Динамические массивы можно инициализировать составным литералом. Они предоставляют `.data`, `.count`, а также `.capacity`, и используют синтаксис вызова метода для операций:

```go
numbers: [..]int = {10, 20};
numbers.append(30);

log_info("количество: %", {numbers.count});
log_info("первый: %", {numbers[0]});
```

Полное руководство (включая шаблоны удаления) см. [Массивы и коллекции](/all-out-docs/ru/skripting/arrays-and-collections.md).

## Поток управления

### Если / иначе

```go
if hp <= 0 {
    die();
} else if hp < 25 {
    Notifier.notify(player, "Мало здоровья!");
} else {
    // всё хорошо
}
```

### Switch

Используйте `default:` для ветки по умолчанию. Ветки поддерживают **несколько значений** (через запятую) и **диапазоны**. Никакого проваливания в стиле C.

```go
Item_Tier :: enum {
    COMMON;
    UNCOMMON;
    RARE;
    EPIC;
    LEGENDARY;
}

get_tier_color :: proc(tier: Item_Tier) -> v4 {
    switch tier {
        case .COMMON:              return {0.7, 0.7, 0.7, 1.0};
        case .UNCOMMON:            return {0.3, 0.8, 0.3, 1.0};
        case .RARE, .EPIC:         return {0.3, 0.5, 1.0, 1.0};
        case .LEGENDARY:           return {1.0, 0.8, 0.2, 1.0};
        default:                   return {1, 1, 1, 1};
    }
}

// Ветки диапазонов
get_difficulty :: proc(level: int) -> string {
    switch level {
        case 1..5:          return "Легко";
        case 6..<11:        return "Средне";
        case 11..20, 25:    return "Сложно";
        default:            return "Неизвестно";
    }
}
```

`..` включает обе границы. `..<` исключает верхнюю границу.

### while / for

```go
while condition {
    // ...
}

// Включающий диапазон: 0..9 включает 9
for i: 0..9 {
    log_info("i=%", {i});
}

// Полуоткрытый диапазон: 0..<count исключает count
for i: 0..<items.count {
    log_info("элемент %", {items[i]});
}

// Обратная итерация
for i: 0..<items.count #reverse {
    log_info("элемент в обратном порядке %", {items[i]});
}

// Перебор элементов массива/среза
for item: my_items {
    // ...
}

// С переменной индекса
for item, i: my_items {
    log_info("элемент % по индексу %", {item, i});
}

// Перебор пользовательских итераторов (часто для сущностей/компонентов)
for enemy: component_iterator(Enemy) {
    enemy.update_ai();
}
```

Циклы на основе пользовательских итераторов `for` требуют `next :: method() -> bool` и `current` поля.

## Приведение типов

Используйте `expr.(T)` либо `cast(T)expr` для приведения:

```go
a := 123.4;
b := a.(int);   // 123
c := cast(float)b; // 123.0
```

Когда целевой тип уже известен, CSL может вывести его автоматически:

```go
i: int = a.();
j: int = cast a;
```

Процедурные значения нельзя приводить к другой сигнатуре процедуры или к/из значений, не являющихся процедурами. Для намеренной низкоуровневой переинтерпретации сигнатуры используйте unsafe-объявление: `#unsafe retyped := (&callback).(*Other_Callback).*`.

Приведения классов должны оставаться в пределах одной иерархии наследования. Восходящие приведения не выполняют работы в рантайме; нисходящие приведения проверяют конкретный тип объекта и выбрасывают ошибку при несоответствии. Значения null для классов остаются null при нисходящем приведении.

## Передача по ссылке: `ref` (предпочтительно)

Когда нужно изменить параметр, **предпочитайте `ref`** сырым указателям. Взятие сырого адреса с помощью `&` требует `#unsafe` контекста. Одно не заключённое в фигурные скобки unsafe-объявление остаётся в окружающей области видимости.

```go
update_position :: proc(pos: ref v2, velocity: v2, dt: float) {
    pos.x += velocity.x * dt;
    pos.y += velocity.y * dt;
}

my_pos := v2{0, 0};
update_position(ref my_pos, {10, 5}, 0.16);
```

## Замыкания и колбэки

Литералы процедур могут захватывать параметры и локальные переменные внешней области видимости:

```go
invoke_twice :: proc(callback: proc()) {
    callback();
    callback();
}

count_twice :: proc(initial: int) -> int {
    value := initial;
    invoke_twice(proc() {
        value += 1;
    });
    return value;
}
```

Захватывающие литералы процедур являются выражениями колбэков с областью видимости. Их можно вызывать только немедленно или передавать напрямую параметру процедуры, который принимает захваты. Этот параметр может вызвать колбэк или напрямую передать его другому параметру, принимающему захваты. Захватывающие процедуры нельзя присваивать переменным, возвращать или помещать в поля, глобальные переменные, коллекции или другое хранилище.

Неаннотированный параметр процедуры может получить захватывающую процедуру, поэтому на него распространяется то же ограничение на вызов или передачу дальше. Помечайте параметр `#no_captures` когда процедуре нужно сохранять его, возвращать или иным образом с ним работать. Тогда вызывающие могут передавать только не захватывающие процедуры; храните постоянное состояние в явном userdata:

```go
Callback_State :: class {
    value: int;
}

Callback :: proc(userdata: Object);
saved: Callback;
saved_userdata: Object;

install :: proc(userdata: Object, #no_captures callback: Callback) {
    saved_userdata = userdata;
    saved = callback;
}

increment :: proc(userdata: Object) {
    state := userdata.(Callback_State);
    state.value += 1;
}

state := new(Callback_State);
install(state, increment);
```

Аннотация является частью контракта принимающей процедуры и может также встречаться в типе процедуры, например `Retainer :: proc(userdata: Object, #no_captures callback: Callback)`. Процедура с захватами и обычная процедура по-прежнему имеют одинаковое двухсловное представление процедура/окружение; у обычной процедуры окружение равно нулю. Вызовы завершаются ошибкой, если для передачи процедуры с захватами параметру используется низкоуровневая переинтерпретация. `#no_captures` параметру.

## Информация о типах (типы как значения)

`typeid` значения можно передавать полиморфным процедурам:

```go
default_of :: proc($T: typeid) -> T {
    t: T;
    return t;
}

a := default_of(int);      // 0
b := default_of(string);   // ""
c := default_of([4]int);   // {0, 0, 0, 0}
```

`#type_info(T)` возвращает данные отражения для типа. Используйте его `поле id` при ведении логов или форматировании имени типа:

```go
ti := #type_info(Thing);
log_info("тип: %", {ti.id});
```

## Лучшие практики (CSL в All Out)

* **Избегайте глобального состояния игрового процесса.** Подключаются несколько игроков — храните состояние каждого игрока в `Player` вместо этого.
* **Рисуйте весь UI игрока из `Player.ao_late_update`.** Обертывайте это в `is_local_or_server()`.
* **Используйте `is_local()` только для визуальных переопределений, специфичных для игрока.** Не изменяйте внутри него состояние игрового процесса или UI.
* **Настройки по умолчанию — в первую очередь для мобильных устройств.** Не полагайтесь на ввод с клавиатуры/мыши, если ваша игра явно не ориентирована на ПК.
* **Если вы не уверены в синтаксисе или API**, откройте соответствующий `.csl_engine` файл в `api_references/` в вашем проекте.
