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:
| Type | Prefix | Description | Examples |
|---|---|---|---|
| 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
globalwhenever 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
- Set:
- 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.ephmis 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}"printsvar: {$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 $varBraces 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%becomes0.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:
!andnotare illegal. - Early Return: Use
stopto immediately end the script.
Loops
| Loop Type | Description | Example Syntax |
|---|---|---|
repeat | Fixed iterations. | repeat 5 with $iter |
while | Runs while condition is true. | while $count < 10 with $iter |
over | Iterates through collections/arrays. | over @all with @plr |
forever | Infinite loop. Must include wait. | forever with $iter |
- Loop Control:
break(exit loop) andcontinue(skip iteration). withKeyword: 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 Type | Syntax Example | Description |
|---|---|---|
| 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 falsefollowed bystopto cancel the base game event. - Event Variables: Provided via C# reflection, but may not always exist. Use
-- requireto validate. - ProjectMER Events: Use
serhelp pmereventsto list the events exposed by the installed ProjectMER version. Schematic event values use SER'sMERSchematicreference type and can be passed directly toMER.*methods.
7. Quick Reference Cheat Sheet
Top Essential Methods
| Category | Methods |
|---|---|
| Communication | Broadcast, Hint, Cassie |
| Player Control | GiveItem, ClearInventory, SetRole, SetSize, GiveEffect |
| Health & Damage | Kill, Damage, Heal, SetHealth, SetMaxHealth, Explode |
| Environment | CloseDoor, OpenDoor, LockDoor, UnlockDoor |
| Movement | TPPlayer, TPPosition |
| Utility | AmountOf, Take, Random, Chance, SetRoundLock, SetLobbyLock, SetPlayerData, GetPlayerData, HasPlayerData |
Top Essential Events
| Event | Variables Provided | Common Use Cases |
|---|---|---|
| RoundStarted | None | Round logic. |
| WaitingForPlayers | None | Server systems. |
| Death | @evPlayer, @evAttacker, $evOldRole, *evOldPosition | Kill streaks, death rewards. |
| Hurt | @evPlayer, @evAttacker, $evDamage | Hit reactions, damage tracking. |
| Joined | @evPlayer | Welcome messages, tutorial hints. |
| ChangedRole | @evPlayer, $evOldRole, $evNewRole | Class 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