Skip to main content

Language specification

SER scripts use .ser by default. .txt has identical semantics and exists for server hosts whose file managers do not let users open unknown file types.

Files are discovered recursively, but the base filename is a global script identifier. Only one example.ser or example.txt may exist across the entire SER script directory, regardless of subfolder.

1. Data Types & Variables

Variables must always include their specific prefix so the engine knows the data type. Create or update variables using =.

The Four Data Types

From most to least used:

TypePrefixDescriptionExamples
Player@Array of players@sender, @all, @evAttacker
Literal$Numbers, text, time, booleans, colors, enums$age = 10, $time = 5s, $role = "Scientist"
Reference*C# objects (e.g., rooms, items)*spawnRoom, *evRoom
Collection&A list of multiple items&inventory, &rooms

Variable Visibility and Lifetimes

SER does not have conventional lexical variable scopes. Each running script has one local-variable table. Blocks and inline functions control value lifetime, but do not create separate access namespaces.

  • Local (default): Stored in the current script's local table and removed when that script execution finishes. ($var = 10)
  • Global: Stored in the round-wide table and accessible to other scripts. Use global whenever creating or changing it; read it without the keyword. SER does not allow an active local and global with the same prefix and name.
    • Set: global $score = 100
    • Read: Print {$score}
    • Verify: if {VarExists $myGlobal} is false
  • Ephemeral (ephm): Added to the same script-local table, so it has normal local visibility while alive. Its containing statement owns its lifetime and removes it when that statement finishes. Loops remove their ephemeral values after each iteration. ephm is therefore lifetime-limited, not lexically private. (ephm $x = 1)
  • Function arguments: Temporarily added to the calling script's local table and removed when the function call finishes. They are not stored in a separate lexical environment.

2. Text, Math, & Syntax

Text Interpolation & Comments

  • Basic Text: Enclosed in double quotes ("Hello!").
  • Interpolation ({}): Insert variables/properties into text ("Hello {$name}").
  • Escaping (~): Prevent interpolation ("var: ~{$var}" prints var: {$var}).
  • Newlines: Use <br />.
  • Comments (#): Must have a space after the pound sign (# Comment).

Expression Braces

Curly braces group an expression when it is used directly as part of another line. They tell the tokenizer exactly where a multi-token expression starts and ends.

  • Variables do not need braces. Their prefix ($, @, *, or &) identifies them as complete value tokens, so these forms are valid:

    if $var is "value"
    Reply $var

    Braces are still required when the variable is interpolated inside text, for example Reply "Value: {$var}".

  • Method calls require braces when used inline. A method name is an ordinary word and may or may not be followed by arguments, so the tokenizer cannot reliably determine where the expression ends. Group the complete call:

    if {RetMethod arg} is "value"
    Reply {ServerInfo name}
  • Property chains require braces when used inline. The chain contains multiple tokens, so group it in the same way:

    if {@plr -> name} is "Elektryk_Andrzej"
    Reply {@plr -> name}
  • Variable definitions already provide an expression boundary. Do not add braces around the right-hand side:

    $var = "value"
    $serverName = ServerInfo name
    $playerName = @plr -> name

In short: use braces when an inline expression could otherwise be mistaken for separate line arguments; omit them for a standalone assignment or a prefixed variable whose token boundary is unambiguous.

Math Expressions

  • Operators: +, -, *, /, % (Handled via NCalc 1.3.8).
  • Negative Values: Script parsing relies on whitespace, so negatives don't need parentheses (e.g., TPPosition @plr -37 313 -140).
  • Percent Sign (%): Divides a number by 100 (50% becomes 0.5).

3. Methods & Properties

Methods (Commands)

Methods perform actions and are written in PascalCase.

  • Syntax: MethodName Arg1 Arg2 (space separated) (e.g., Broadcast @all 5s "Hello")
  • Return Values: Can be stored directly ($name = ServerInfo name)
  • The Wildcard (*): Targets ALL of something, excluding players (CloseDoor *)
  • Omit Arguments (_): Skip optional arguments (*embed = Embed.Create "Title" _ _ "Author")

Properties (->)

Access internal data of values. Can be chained (e.g., @plr -> name -> length -> isOdd).

  • Player Properties: MUST strictly be one player. Use {AmountOf @all} if you need a length count.

  • Reference Validity: C# objects can become null. Always validate before use: if {*room -> isInvalid}.

  • Context Rules:

  • Variable definitions: The right-hand side is already a complete expression, so no braces are needed ($name = @plr -> name).

  • Inline conditions and method arguments: Use braces for method calls and property chains (if {RetMethod arg} is "value", if {@plr -> role} is "ClassD"). A prefixed variable can remain unbraced (if $role is "ClassD").

  • Enum Conversion: In methods, bare enum tokens work (SetRole @plr ClassD). In properties/conditions, enums are converted to strings (if {@plr -> role} is "ClassD").


4. Control Flow & Execution

Conditionals (if, elif, else)

Compare values using standard operators (is/==, isnt/!=, >, <, and/&&, or/||).

  • Note: ! and not are illegal.
  • Early Return: Use stop to immediately end the script.

Loops

Loop TypeDescriptionExample Syntax
repeatFixed iterations.repeat 5 with $iter
whileRuns while condition is true.while $count < 10 with $iter
overIterates through collections/arrays.over @all with @plr
foreverInfinite loop. Must include wait.forever with $iter
  • Loop Control: break (exit loop) and continue (skip iteration).
  • with Keyword: Assigns a name to the current item or iteration number.

Waiting & Yielding

  • wait: Pause for duration (wait 5s, wait 100ms).
  • wait_until: Pause until a condition is met (wait_until {AmountOf @all} > 0).

5. Functions & Errors

Functions

Must be hoisted (defined before use). The prefix in the name defines the return type($Name = literal, @Name = players, *Name = reference, &Name = collection, Name = nothing). Call using run.

func $Add with $a $b
return $a + $b
end
$sum = run $Add 5 3

Error Handling

Use attempt and on_error to catch exceptions without breaking the script.

attempt
PlayAudio "invalid speaker" "invalid clip name"
on_error with $msg
Print "Error: {$msg}"
end

6. Script Entry Points (Flags)

A file can contain multiple flagged script sections. Every !-- declaration starts a new independent script, and its section continues up to (but not including) the next !-- declaration. Named -- arguments belong to the nearest flag above them.

SER reads every script file when the plugin initializes and on each round restart. During recursive discovery, SER ignores files whose names start with # and does not search directories whose names start with #. Whenever a file-backed script is requested for execution, SER performs a targeted refresh of that file before running it. This applies to serrun, events, callbacks, custom commands, triggers, and calls from other scripts. Use the permission-protected serreload command when you want to refresh the entire directory immediately, including new bindings which have not had an opportunity to trigger. The complete physical file must compile and register successfully before any active section is replaced; otherwise SER reports the exact file error and keeps the last known-good version active. Use serstatus to inspect accepted, failed, disabled, excluded, and conflicting paths.

!-- OnEvent RoundStarted
Print "The round started"

!-- OnEvent Death
Print "A player died"

!-- CustomCommand status
Reply "The server is online"

The sections of a multi-section file named roundHandlers.ser can be addressed manually as roundHandlers:1, roundHandlers:2, and roundHandlers:3. A bare name is accepted only for flagless and single-section files. Only blank lines and comments may appear before the first flag in a multi-section file.

Flag TypeSyntax ExampleDescription
Utility(No flags in the file)Run manually via serrun or RunScript.
Custom Command!-- CustomCommand heal
-- availableFor RemoteAdmin
Binds the script to a custom command.
Event!-- OnEvent Dying
-- require @evPlayer
Triggers on a LabAPI game event.
ProjectMER!-- OnPMER SchematicSpawned
-- require *evSchematic
Triggers on an optional ProjectMER event.
  • Event Cancellation: Use IsAllowed false followed by stop to cancel the base game event.
  • Event Variables: Provided via C# reflection, but may not always exist. Use -- require to validate.
  • ProjectMER Events: Use serhelp pmerevents to list the events exposed by the installed ProjectMER version. Schematic event values use SER's MERSchematic reference type and can be passed directly to MER.* methods.

7. Quick Reference Cheat Sheet

Top Essential Methods

CategoryMethods
CommunicationBroadcast, Hint, Cassie
Player ControlGiveItem, ClearInventory, SetRole, SetSize, GiveEffect
Health & DamageKill, Damage, Heal, SetHealth, SetMaxHealth, Explode
EnvironmentCloseDoor, OpenDoor, LockDoor, UnlockDoor
MovementTPPlayer, TPPosition
UtilityAmountOf, Take, Random, Chance, SetRoundLock, SetLobbyLock, SetPlayerData, GetPlayerData, HasPlayerData

Top Essential Events

EventVariables ProvidedCommon Use Cases
RoundStartedNoneRound logic.
WaitingForPlayersNoneServer systems.
Death@evPlayer, @evAttacker, $evOldRole, *evOldPositionKill streaks, death rewards.
Hurt@evPlayer, @evAttacker, $evDamageHit reactions, damage tracking.
Joined@evPlayerWelcome messages, tutorial hints.
ChangedRole@evPlayer, $evOldRole, $evNewRoleClass transitions, spawn effects.

Common Scripting Patterns

Select Random Player:

@plr = Take @all 1

Check % Chance & SCP Team:

if {Chance 25%} and {@plr -> team} is "SCPs"
# 25% chance to run for SCPs
end

Player Data Management:

SetPlayerData @plr "kills" 5
$kills = GetPlayerData @plr "kills"

Event Cancellation:

!-- OnEvent ChangingRole
-- require @evPlayer $evNewRole

if $evNewRole is "ClassD"
# Do not allow ClassD to change roles
IsAllowed false
stop
end