# Getting Started with CSL

CSL is All Out’s custom scripting language. It’s statically typed and feels most similar to Go/Odin — except **gameplay state is automatically synced from the server to clients**.

{% hint style="info" %}
You don’t need to write RPCs, SyncVars, or custom replication. We handle these for you!
{% endhint %}

## Your first script (`main.csl`)

When you create a new project, All Out generates a `main.csl` in your project’s `scripts/` folder.

```go
import "core:ao"

// ============================================================================
// Global lifecycle
// ============================================================================

ao_before_scene_load :: proc() {
    // Register item definitions, currencies, etc.
    // Runs before the scene is created.
}

ao_start :: proc() {
    // Called once when the scene starts.
}

ao_update :: proc(dt: float) {
    // Called every frame.
}

ao_late_update :: proc(dt: float) {
    // Called every frame after ao_update.
}

// ============================================================================
// Player lifecycle
// ============================================================================

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

    ao_update :: method(dt: float) {
    }

    ao_late_update :: method(dt: float) {
    }

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

For example, log a message when each player joins:

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

If you want a deeper explanation of when these functions run, see [Game/Frame Lifecycle](/scripting/game-frame-lifecycle.md).

## Imports

Your `main.csl` should import `core:ao` and any folders you create (like `ui/`, `abilities/`, etc).

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

Imports point at folders, not individual files. A folder import includes the `.csl` files in that folder. Imports inside that folder are resolved relative to it:

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

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

{% hint style="warning" %}
In most projects, **import in `main.csl` only**. Don’t sprinkle imports across lots of files — you’ll eventually end up with confusing order/visibility issues.
{% endhint %}

## Declarations (variables and constants)

Declarations bind a name to a value.

### Variables

```go
// General form
<name>: <type> = <expression>;
```

Either `<type>` or `<expression>` can be omitted:

```go
// Explicit type
my_value: int = 42;

// Type inference
my_value := 42; // inferred to int

// Zero-init (same as my_value := 0;)
my_value: int;
```

### Constants

Constants use `::` and must be compile-time constants. They can be scalars, strings, types, procedure values, arrays, or compound literals:

```go
MAX_PLAYERS :: 12;

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

DEFAULT_SPAWNS: []Spawn_Desc : {
    {"Blue", {-4, 0}},
    {"Red", {4, 0}},
};
```

This is invalid (because `a` is not compile-time constant):

```go
a := 123;
b :: a; // compile error
```

Global variable initializers must also be compile-time constants. Use `ao_before_scene_load` or `ao_start` for runtime initialization.

### Global variables

Globals use the same declaration syntax as locals. They can be left zero-initialized or initialized with compile-time constants, including structs, arrays, procedure values, and `typeid` values:

```go
score_total: int;
spawn_names: []string = {"Blue", "Red"};
default_type: typeid = int;

start_round :: proc() {
}

on_match_start: proc() = start_round;
```

Globals are mutable and persist for the lifetime of the script instance. Avoid using them for per-player gameplay state.

## Types

### Primitive types

* Signed integers: `s8`, `s16`, `s32`, `s64`
* Unsigned integers: `u8`, `u16`, `u32`, `u64`
* Booleans: `bool`
* Floats: `f32`, `f64`
* Aliases:
  * `int` == `s64`
  * `uint` == `u64`
  * `float` == `f32`
* Vectors: `v2`, `v3`, `v4`
* `string`
* `typeid`
* `any`

### Vector types

`v2` has `.x`, `.y`; `v3` adds `.z`; `v4` adds `.w` — all float fields:

```go
pos: v2 = {10, 20};        // x=10, y=20
color: v4 = {1, 0, 0, 1};  // red with alpha=1
offset := v3{1, 4, 9};     // type inference
```

## Structs and classes

Structs are **value types** (copies on assignment). Classes are **reference types** (you allocate them with `new`).

### Structs (value types)

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

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

### Classes (reference types)

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

foo := new(Foo);
```

Class fields can have default values. A derived class can override inherited defaults without redeclaring the field:

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

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

### Inheritance

Structs/classes can inherit from other structs/classes:

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

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

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

## Procedures and methods

### Procedures (`proc`)

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

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

Procedures are normal values and can be assigned/stored like any other value:

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

### Methods (`method`)

Use `method()` inside a struct/class. Methods have an implicit `this` reference parameter.

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

    bark :: method() {
        log_info("% says bark!", {name});
    }
}

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

### Field access vs method calls

Use `.` for both fields and methods:

```go
hp := player.health;        // field access
player.respawn();          // method call
entity.set_local_scale({2, 2});
```

{% hint style="info" %}
Any procedure can be called as a “method” if its first parameter matches the receiver type. Real methods and procedure-valued fields on the type win before CSL falls back to a matching free procedure.
{% endhint %}

## Arrays

CSL has a few “array-like” types you’ll use constantly:

* **Fixed arrays**: `[4]int`
* **Slices / managed arrays**: `[]T` (often used as “read-only view” into an array)
* **Dynamic arrays**: `[..]T` (resizable list)
* **Unmanaged arrays**: `[^]T` (used in built-in API signatures like `format_string`, `log_info`, etc — pass values as `{a, b, c}`)

Dynamic arrays expose `.data`, `.count`, and `.capacity`, and use method-call syntax for operations:

```go
numbers: [..]int;
numbers.append(10);
numbers.append(20);

log_info("count: %", {numbers.count});
log_info("first: %", {numbers[0]});
```

For a full guide (including removal patterns), see [Arrays and Collections](/scripting/arrays-and-collections.md).

## Control flow

### If / else

```go
if hp <= 0 {
    die();
} else if hp < 25 {
    Notifier.notify(player, "Low health!");
} else {
    // all good
}
```

### Switch

Use `default:` for the default clause. Cases support **multiple values** (comma-separated) and **ranges**. No C-style fallthrough.

```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};
    }
}

// Range cases
get_difficulty :: proc(level: int) -> string {
    switch level {
        case 1..5:          return "Easy";
        case 6..<11:        return "Medium";
        case 11..20, 25:    return "Hard";
        default:            return "Unknown";
    }
}
```

`..` includes both endpoints. `..<` excludes the upper endpoint.

### While / for

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

// Inclusive range: 0..9 includes 9
for i: 0..9 {
    log_info("i=%", {i});
}

// Half-open range: 0..<count excludes count
for i: 0..<items.count {
    log_info("item %", {items[i]});
}

// Reverse iteration
for i: 0..<items.count #reverse {
    log_info("reverse item %", {items[i]});
}

// Iterate array/slice elements
for item: my_items {
    // ...
}

// With index variable
for item, i: my_items {
    log_info("item % at index %", {item, i});
}

// Iterate custom iterators (common for entities/components)
for enemy: component_iterator(Enemy) {
    enemy.update_ai();
}
```

Custom iterator-based `for` loops require a `next :: method() -> bool` and a `current` field.

## Casting

Use `expr.(T)` or `cast(T)expr` to cast:

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

When the target type is already known, you can let CSL infer it:

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

## Passing by reference: `ref` (preferred)

When you need to modify a parameter, **prefer `ref`** over raw pointers.

```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);
```

## Callbacks: function pointers + userdata (no closures)

CSL does not have closures. Inline `proc(...) { ... }` cannot capture surrounding variables.

To carry context, pair callbacks with a `userdata: Object` field:

```go
Player :: class : Player_Base {
    on_death_userdata: Object;
    on_death: proc(player: Player, userdata: Object);

    die :: method() {
        if on_death != null {
            on_death(this, on_death_userdata);
        }
    }
}

Death_Tracker :: class : Component {
    count: int;

    ao_start :: method() {
        player := entity.get_component(Player);
        player.on_death_userdata = this;
        player.on_death = proc(player: Player, userdata: Object) {
            tracker := userdata.(Death_Tracker);
            tracker.count += 1;
        };
    }
}
```

## Type info (types as values)

`typeid` values can be passed to polymorphic procs:

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

## Best practices (CSL in All Out)

* **Avoid global gameplay state.** Multiple players connect — store per-player state on `Player` instead.
* **Separate cosmetic vs gameplay logic.** Use `is_local()` for local-only UI/particles, and `is_local_or_server()` for gameplay inputs that must run on server + local client.
* **Mobile-first defaults.** Don’t rely on keyboard/mouse input unless your game is explicitly PC-focused.
* **If you’re unsure about syntax or APIs**, check the `api_reference/` folder generated in your project (it contains the latest `core.csl` surface).


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.allout.game/scripting/syntax.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
