An introduction to scripting
A script is a small piece of code that your server runs when something happens in your world: a player joins, says something, steps on a tile, kills a monster. Scripts are how a world gets its own quests, puzzles, shops, rules and surprises, beyond what maps and items can do alone.
You do not need to be a programmer to start. This guide writes a first script with you, then explains each idea you need for the next one.
Where scripts live#
Scripts are part of your world. They are stored in its database with its maps and items, so a backup of the world includes them, and there are no script files to keep track of.
You write them in Odyssey Server Admin, on the Scripting page. The server compiles a script the moment you save it, and it takes effect at once: no restart, and nobody is disconnected.
To see scripts a person needs the ViewScripts right, and to change them the EditScripts right. You give rights through roles, under Player administration.
Your first script#
This one greets every player who joins.
-
Open Scripting in Odyssey Server Admin and press New script.
-
Type
JoinGameas its Name, and choose JoinGame under Start from event hook. The editor starts you off with the right opening line. -
Make the script look like this:
void main(int player) { PlayerMessage(player, "Welcome to the server!", Yellow); } -
Press Save. The app answers that the script saved and compiled cleanly.
Join your server. The message appears in your chat the moment you enter the world.
Three things made that work, and they are the whole idea of scripting:
- The script's name is
JoinGame. The name decides when a script runs. The server looks for a script calledJoinGameeach time a player joins. - It has a
main. That is where the server starts.playeris handed to it by the server: the player who just joined. - It calls
PlayerMessage. That is one of nearly three hundred functions the server gives scripts, to read the world and change it.
The name decides when it runs#
Every moment a script can answer is called an event. Each event has a name, and a script with that name answers it. Events lists them all, and so does the Event hooks tab of the Scripting page.
Many events can be narrowed to one map, one item or one monster by putting its number after the name:
| Script name | Runs when |
|---|---|
JoinMap | A player enters any map. |
JoinMap12 | A player enters map 12. |
UseObj | A player uses any item. |
UseObj40 | A player uses item 40. |
Map12_3_7 | A player steps on the script tile at 3, 7 on map 12. |
You can have both the general script and a numbered one. Both are used.
Capital letters in a name do not matter. JoinGame, joingame and JOINGAME are the same script.
A script whose name is not an event's is fine too. Nothing runs it by itself, but other scripts and timers can, as you will see below.
What main is given#
Each event hands main some values. JoinGame hands over the player. MapSay, which runs when somebody speaks, hands over the player and what they said:
bool main(int player, string message)
The values are matched by name. Write only the ones you need, in any order, but spell them as the events page does. Starting a script from its event hook writes them all for you.
Most values are whole numbers (int). A few are text (string) or yes-or-no (bool).
Answering yes or no#
Some events ask the script a question: may this happen? Their main starts with bool and answers with return.
return true;means carry on. The server does what it would have done.return false;means stop. The message is not said, the item is not picked up, the monster does not attack.
This script, named MapSay5, listens on map 5 for a password. A player who says it is moved to another map, and nobody hears them say it:
bool main(int player, string message)
{
if (message.ToLower() == "open sesame")
{
PlayerWarp(player, 6, 5, 5);
return false;
}
return true;
}
An event whose main starts with void is only telling you something happened. It has no answer to give.
If a script is missing, or has been set aside because of a fault, the server carries on as if it had answered true.
Reading the world and changing it#
Functions that start with Get tell you something. Others do something. This JoinGame greets new characters differently from experienced ones:
void main(int player)
{
var name = GetPlayerName(player);
if (GetPlayerLevel(player) < 5)
{
PlayerMessage(player, $"Welcome, {name}! Stay near town until you are stronger.", Yellow);
}
else
{
PlayerMessage(player, $"Welcome back, {name}.", BrightGreen);
}
}
The $"..." form builds text with values dropped in between { and }.
The colours you can name are Black, Blue, Green, Cyan, Red, Magenta, Brown, Grey, DarkGrey, BrightBlue, BrightGreen, BrightCyan, BrightRed, BrightMagenta, Yellow and White.
Every function is in the script reference.
Remembering things: flags#
A script starts fresh every time it runs. Nothing it holds in a variable is there the next time. To remember something, put it in a flag: a numbered box that holds a whole number.
- Player flags belong to one character.
GetPlayerFlag(player, 10)reads that character's flag 10, andSetPlayerFlag(player, 10, 1)sets it. - World flags belong to the whole world.
GetFlag(3)andSetFlag(3, 1).
Each has flags numbered 0 to 255. Flags 0 to 127 are saved and are still there after a restart. Flags 128 to 255 are forgotten when the server stops, which makes them good for things that should not outlive the day.
This JoinGame counts a character's visits and gives a gift on the first:
void main(int player)
{
// Flag 10 counts how many times this character has joined.
var visits = GetPlayerFlag(player, 10) + 1;
SetPlayerFlag(player, 10, visits);
if (visits == 1)
{
GlobalMessage($"{GetPlayerName(player)} has joined us for the first time!", BrightCyan);
GiveObj(player, 1, 100);
}
else
{
PlayerMessage(player, $"This is visit number {visits}.", Grey);
}
}
GiveObj(player, 1, 100) gives 100 of item number 1. Use the number of an item in your own world.
Keep a list of which flag means what. Nothing stops two scripts using the same flag for different things.
Doing something later: timers#
A script cannot wait. It runs from start to finish at once, and the world stands still while it does. To make something happen later, ask for another script to be run:
void main(int player)
{
PlayerMessage(player, "The floor begins to shake...", BrightRed);
ScriptTimer(player, 5, "Collapse");
}
Five seconds later the server runs the script named Collapse and hands it that player. If the player has left by then, it does not run.
ScriptTimer counts in seconds, up to a day. MicroTimer counts in thousandths of a second, from 100 to 10,000. Timers are looked at twice a second, so half a second is as fine as they get. A timer is forgotten if the server restarts before it is due.
A script named MinuteTimer runs once every minute, for the world as a whole.
Scripts that help other scripts#
RunScript runs another script by name and hands it whatever you give it:
RunScript("GiveReward", player, 50);
The script it runs takes those values in the order given: void main(int player, int amount). If that script answers false, so does RunScript.
Inside one script, you can write your own functions beside main:
int Reward(int level)
{
return 50 + level * 10;
}
void main(int player)
{
GiveObj(player, 1, Reward(GetPlayerLevel(player)));
}
The language#
Scripts are written in C#, a widely used programming language. Anything that teaches C# teaches you how to write if, loops, variables and functions here.
A script is not a whole C# program. You write only main and any helpers, and the server supplies the rest. Along with the server's own functions you can use numbers and text, Math, dates and times, lists and dictionaries, and LINQ.
A script is deliberately shut in. It cannot read or write files, reach the network, start other programs, or touch anything else of the machine it runs on. If a script tries, the editor says so as it would for a typing mistake, for example:
Scripts cannot use 'System.IO.File'.
A script also cannot run forever. Each run has a generous budget of work, shared with any script it starts. One that goes past it, such as a loop that never ends, is stopped and set aside:
The script ran for more than 10,000,000 steps, or called itself too deeply, in one run, and was stopped.
When a script has a mistake#
The editor checks as you type. The Problems panel lists what is wrong and where, as Errors, Warnings and Info. Only errors stop a script from working.
You can save a script that has errors. It is kept, but quarantined: set aside, and skipped whenever its event comes up, as if it were not there. The list marks it Quarantined, and the Needs attention filter shows only the scripts that need you.
A script is also quarantined if it fails while running, for instance by dividing by zero, or if it runs past its budget. The banner at the top of the script says why.
To bring a script back:
- Fix what the banner or the Problems panel points at.
- Press Save. A script that compiles leaves quarantine by itself.
If nothing in the script needs changing, Clear quarantine compiles it again and puts it back to work.
Check all, above the list, compiles every script at once and quarantines the ones that fail.
Trying a script#
Run… runs the open script immediately, on the live server. The app asks for whatever values its main takes. Where one is a player, type the name of somebody who is online.
The scripts your world starts with#
A new world, and a converted one, start with ten scripts. They hold the game's arithmetic, so that you can change it:
| Script | What it works out |
|---|---|
PlayerDamage | How hard a player hits. |
PlayerArmor | What is left of a hit after a player's armour. |
PlayerHitsPlayer | The roll for one player's swing at another. |
PlayerHitsMonster | Whether a player's swing at a monster lands. |
MonsterHitsPlayer | Whether a monster's swing at a player lands. |
MonsterKillValue | The experience a monster is worth. |
ExperienceMultiplier | What experience-raising equipment adds. |
GuildPartyShare | How a kill's experience is shared in a guild party. |
Regeneration | How fast health, energy and mana come back. |
LevelUpGains | What rises when a character gains a level. |
Open one and read it: each begins by explaining its rule in plain words. Change the sums and save, and the game changes.
They are safe to experiment with. If one is deleted or quarantined, the server uses the rule it started with.
Where next#
- Events: every moment a script can answer, and what each hands to
main. - Script reference: every function a script can call.
- Bringing over legacy scripts: if your world came from the original Odyssey.