From The Mana World
(Redirected from Scripting)

<metakey>Script Language, Code, Source, eAthena Script, AST</metakey> <metadesc>The Mana World Scripting Language Intro</metadesc>

TMW's scripting wiki pages:

Legacy:Scripting Basics - Legacy:Scripting Standards - Legacy:Scripting Reference

For questions feel free to contact us on IRC and/or on our forum.

These are articles that require updating, moving to archive or deletion (see also Template:Delete).


General structure

In this document, all paths are relative to the working directory of the server (with current data, world/map/) The script settings are initialized by the following line in conf/map_athena.conf, which is read by map_config_read(cfgName)

import: npc/scripts.conf

npc/scripts.conf is a manually-maintained file that loads functions, and then defers to the automatically-generated npc/_import.txt for map-local things. It is also used to enable holiday quests.

Besides "import:", the only other relevant option is "npc:", which schedules an script file to load (though it may not necessarily contain an npc). There's also "delnpc:", which removes it from the list of things to load. As a special exception, "npc: clear" or "delnpc: all" will empty the list.

You should not need "npc: clear" or any form of "delnpc:"

In addition, item scripts (equip and use) are be loaded from db/itemdb.txt, but this is only a script-body, not a script file. equip scripts are evaluated every time the players stats need to be recalculated (which happens a lot); they must not modify the player's equipment.

script files

The function that actually loads the list of script files is do_init_npc().

Each line in a file must have less than 1020 bytes.

The first thing this does for each files is skip lines that consist only of comments (// at the the very beginning of the line), compress runs of spaces to a single space (I think), and normalizes the runs of tabs-and-pipes into a single tab (for compatibility purposes). That's probably more details than a "Basics" page should have.

Then, it tries to match either 3 or 4 words separated by tabs (in current data, normalized from pipes) or separated by spaces (huh?).

It checks whether the first word not one of "-" or "function", and scans for a mapname terminated by a comma. If the map is not on the server, or the mapname is too long, it skips to the next line. This will probably cause bad things to happen.

The second word must be one of "warp", "shop", "script", "monster", or "mapflag". There is also "duplicate..." but that shouldn't be used. All of these require 4 words except "mapflag", which may have only 3 in some cases.

If the second word is "script" and the first word is "function", it is a function; otherwise it is the second word.

warp

npc_parse_warp(w1, w2, w3, w4)

The first word is mapname, x, y. Spaces are optional after the commas. The mapname can contain anything but a comma.

The fourth word is xs, ys,mapname, to_x, to_y. There must not be a space before mapname, or it will be considered part of it.

w3 is the name of the warp.

shop

npc_parse_shop(w1, w2, w3, w4) w1 is mapname, x, y, dir.

w4 is repeated ,<id>:<value> or ,<name>:<value> for the items in the shop.

w3 is the name of the shop NPC.

monster

npc_parse_mob(w1, w2, w3, w4)

w1 is mapname,x,y,xs,ys where xs and ys are optional and default to 0. This represents the spawn area.

w4 is mobclass, num, delay1, delay2,eventname where the delays and eventname are optional and default to 0 and "".

num times, a new mob spawner is created. w3 is the name; "--en--" or "--ja--" means to copy the appropriate name from the mob db.

(A mob spawner is just a mob that automatically respawns after it is killed, after a certain delay)

mapflag

npc_parse_mapflag(w1, w2, w3, w4)

w1 is mapname

w3 is one of: nosave nomemo noteleport nowarp nowarpto noreturn monster_noteleport nobranch nopenalty pvp pvp_noparty pvp_noguild pvp_nightmaredrop pvp_nocalcrank gvg gvg_noparty nozenypenalty notrade noskill nopvp noicewall snow fog sakura leaves rain no_player_drops town

Many of those don't necessarily work.

if w3 is nosave, w4 is "SavePoint" or mapname,x,y

if w3 is pvp_nightmaredrop, w4 is arg1, arg2, per; arg1 may be "random" or an item id; arg2 may be "inventory", "equip", or "all".

script

npc_parse_script(w1, w2, w3, w4, <...>)

w1 is "-" or mapname, x, y, dir

w4 must have a comma.

Then a script body is parsed from { to }. See below.

If the npc is on a map, w4 is class,xs,ys or just class.

If class is negative but the NPC is on a map, the first line of the body may be used as an event called exname.

w3 is name or name::exname. If exname is not provided, it is the same as name.

The parsed script body is scanned for labels starting with "On" (not case sensitive), and each of these is made available as an event called exname::labelname

Then it looks for labels of the form OnTime<ticks>, case sensitive, and these are registered as timer events.

function

npc_parse_function(w1, w2, w3, w4, <...>)

A script body is parsed from { to }.

Then the script is registered as a function (for use with callfunc) with the name w3, which currently has a maximum of 49 characters.

Script Body

To avoid too much header depth, this is at the top level. Of course, it is also where you spend most of your time.

A script body start with a { and goes to the first }. It may occur in a script or function, an item use or equip script, or in the magic file.

The function that handles this is parse_script(), and it returns some bytecode.

Inside a script body, there is label: line; or just line;

line is empty or command args. Commands are checked for number of arguments.

args is 0 to 128 expressions. There is a warning if args are not separated by commas, except after the condition expression of the if command.

Note that if statements *are* properly nested, so this can be used for short-circuiting since the && and || operators don't do it.

An expression is a subexpression with -1 precedence

A subexpression can be a bare - (i.e, followed by a comma or semicolon after skipping spaces), which is interpreted as a label meaning the next line.

A subexpression can be a -, !, or ~ followed by an expression with 100 precedence. This does what you expect, except for function calls.

A subexpression can be a simple expression, followed by an operator with the following precedences:

6 + - 7 * / % 8 ( <function call> 1 && 5 & >> << 0 || 4 | 3 ^ 2 == != >= > <= <

If it is a function call, the left simple expression must be a function, and 0 to 128 arguments are parsed followed by a ). Functions are checked for number of parameters.

If it is not a function call, another subexpression is parsed with the given priority.

A simple expression may be a parenthesized subexpression with -1 precedence.

A simple expression may be an integer, with optional sign. Integers may be decimal, octal, or hexadecimal.

A simple expression may be a string literal, delimited by "". There's something funky with backslashes. A string must not contain an embedded newline.

Otherwise, a string is a name, which may be a function, a label, or a variable. If a name is not a function and is followed by a [, it is translated into a call to the getelementofarray function (which returns a reference). The subexpression has precedence -1 and must be followed by a ].

Note that there is much magic to make labels work properly before they're declared.

Variables

A variable is not a builtin function or label, and matches the following:

An optional $, an option @ or l, two optional # (in which case there should be no $ or @), a sequence of alnum or _, and an optional $.

The meanings are as follows:

Prefix Suffix meaning functions
Any name found in "db/const.txt" of type 0 is flattened at script compile time. N/A
Any name found in db/const.txt of type 1 is a special player paramter, and special logic is used to get/set it. pc_readparam, pc_setparam
A permanent variable attached to the character object, stored by the char-server in "save/athena.txt" pc_readglobalreg, pc_setglobalreg
@ or l A temporary variable attached to a player. Reset when the player logs out or when the server restarts. Don't start variable names with l, this will be removed! pc_readreg, pc_setreg
@ or l $ A temporary variable attached to a player. Reset when the player logs out or when the server restarts. Don't start variable names with l, this will be removed! pc_readregstr, pc_setregstr
$ A global permanent variable, stored by the map-server in "save/mapreg.txt". mapreg_db search, mapreg_setreg
$ $ A global permanent variable, stored by the map-server in "save/mapreg.txt". mapregstr_db search, mapreg_setregstr
$@ A global temporary variable. This is important for scripts which are called with no RID attached. mapreg_db search, mapreg_setreg
$@ $ A global temporary variable. This is important for scripts which are called with no RID attached. mapregstr_db search, mapreg_setregstr
# A permanent account-based variable, stored by the char-server in "save/accreg.txt". pc_readaccountreg, pc_setaccountreg
## A permanent account-based variable, stored by the login-server in "save/account.txt". These used to be broken, but should work now. You shouldn't use these until we get implement worlds on the main server. pc_readaccountreg2, pc_setaccountreg2

The $ suffix indicates a string, and is only allowed for player temporary, global persistent, and global temporary variables.

Arrays

Variables of player temporary, global persistent, or global temporary scope may be arrays.

@array[0] is equivalent to @array, but you should always include the [0] if you use an array.

Array indices go from 0 to 255.

Old Introduction

This page is made after Fredzilla's "Athena Script Commands, A reference manual for the eAthena scripting language" originally located at: http://buwinow5.tripod.com/ . More recent versions can be found at: http://eathena-project.googlecode.com/svn/trunk/doc/script_commands.txt , and http://eathena.ws/board/ but they are unlikely to be compatible with actual TMW versions of the server

This document is a reference manual for all the scripting commands and functions available in current eAthena SVN. It is not a simple tutorial. When people tell you to "Read The F***ing Manual", they mean this.

The information was mostly acquired through looking up how things actually work in the source code of the server, which was written by many people over time, and lots of them don't speak English and never left any notes - or are otherwise not available for comments. As such, anything written in here might not be correct, it is only correct to the best of our knowledge, which is limited.

This document is poorly structured and rather messy in general. In fact, further cleaning up and reordering this document is probably pointless, due to upcoming switch to Lua scripting language, which will rid us of most of the problems mentioned herein and make a new manual necessary. But while we have this one, we should make the most of it, and it might be helpful in making sure the new Lua engine can actually do everything useful that the old engine could.

This is not a place to teach you basic programming. This document will not teach you basic programming by itself. It's more of a reference for those who have at least a vague idea of what they want to do and want to know what tools they have available to do it. We've tried to keep it as simple as feasible, but if you don't understand it, getting a clear book on programming in general will help better than yelling around the forum for help.

A little learning never caused anyone's head to explode.

Structure

The commands and functions are listed in no particular order:

  • Name of the command and how to call it.

Descriptive text

   Small example if possible. Will usually be incomplete, it's there just to 
   give you an idea of how it works in practice.

To find a specific command, use Ctrl+F, (or whatever keys call up a search function in whatever you're reading this with) put an * followed by the command name, and it should find the command description for you.

If you find anything omitted, please respond. :)

Syntax

Throughout this document, wherever a command wants an argument, it is given in <angle brackets>. This doesn't mean you should type the angle brackets. :) If an argument of a command is optional, it is given in {curly brackets}. You've doubtlessly seen this convention somewhere, if you didn't, get used to it, that's how big boys do it. If a command can optionally take an unspecified number of arguments, you'll see a list like this:

command <argument>{,<argument>...<argument>}

This still means they will want to be separated by commas.

Where a command wants a string, it will be given in "quotes", if it's a number, it will be given without them. Normally, you can put an expression, like a bunch of functions or operators returning a value, in (round brackets) instead of most numbers. Round brackets will not always be required, but they're often a good idea.

Wherever you refer to a map name, it's always mapname.gat or mapname.afm if you are using AFM maps, (if you don't know what they are, you aren't using them) and not just "mapname". While some commands do know that if you didn't give ".gat", it should add it, it's pretty tricky to tell which ones they are.

Script loading structure

Scripts are loaded by the map server as referenced in the 'conf/map_athena.conf' configuration file, but in the default configuration, it doesn't load any script files itself. Instead, it loads the file npc/scripts_main.conf which itself contains references to other files. The actual scripts are loaded from txt files, which are linked up like this:

npc: <path to a filename>

Any line like this, invoked, ultimately, by map_athena.conf will load up the script contained in this file, which will make the script available. No file will get loaded twice, to prevent possible errors.

Another configuration file option of relevance is:

delnpc: <path to a filename>

This will unload a specifiled script filename from memory, which, while seemingly useless, may sometimes be required.

Whenever // is encountered in a line upon reading, everything beyond this on that line is considered to be a comment and is ignored. This works wherever you place it.

Upon loading all the files, the server will execute all the top-level commands in them. No variables exist yet at this point, no commands can be called other than those given in this section. These commands set up the basic server script structure - create NPC objects, spawn monster objects, set map flags, etc. No code is actually executed at this point except them. The top-level commands the scripting are pretty confusing, since they aren't structured like you would expect commands, command name first, but rather, normally start with a map name.

  • The confusing tab symbols in the top-level commands, used to divide their arguments have been replaced by the symbol "|"


Top Level Commands

Here is a list of valid top-level commands:

Set a map flag:

<map name>|mapflag|<flag>

This will, upon loading, set a specified map flag on a map you like. These are normally in files inside 'conf/mapflag' and are loaded first, so by the time the server's up, all the maps have the flags they should have. Map flags determine the behavior of the map regarding various common problems, for a better explanation, see 'setmapflag'.

Create a permanent monster spawn:

<map name>,<x1>,<y1>,<x2>,<y2>|monster|<monster name>{,<level>}|<mob id>,<amount>,<delay1>,<delay2>,<event name>

Map name is the name of the map the monsters will spawn on. x1/y1-y1/y2 is a square of map coordinates which will limit where they will initially spawn. Putting zeros instead of these coordinates will spawn the monsters randomly. It's not certain whether monsters will later be able to venture out of this square when randomly moving or not. (Can anyone confirm?)

Monster name is the name the monsters will have on screen, and has no relation whatsoever to their names anywhere else. It's the mob id that counts, which identifies monster record in 'mob_db.txt' database of monsters. If the mob name is given as "--ja--", the 'japanese name' field from the monster database is used, (which, in eAthena, actually contains an english name) if it's "--en--", it's the 'english name' from the monster database (which contains an uppercase name used to summon the monster with a GM command).

If you add 4000 to the monster ID, the monster will be spawned in a 'big version', (monster size class will increase) and if you add 2000, the 'tiny version' of the monster will be created. This will not, however, make the monster spawn with a bigger or smaller sprite, like with @monstersmall/@monsterbig GM commands. Monster size class relates only to the damage calculation.

Amount is the amount of monsters that will be spawned when this command is executed, it is affected by spawn rates in 'battle_athena.conf'.

Delay1 and delay2 are the monster respawn delays - the first one counts the time since a monster defined in this spawn was last respawned and the second one counts the time since the monster of this spawn was last killed. Whichever turns out to be higher will be used. If the resulting number is smaller than a random value between 5 and 10 seconds, this value will be used instead. (Which is normally the case if both delay values are zero.) If both delay values are -1, the monster will never respawn upon death until the server restarts. The times are given in 1/1000ths of a second.

Level overrides the monster's level from the monster id database, if it is 0, the level from the database is used.

Event name is an event label that will be triggered every time a monster of that spawn is killed. If you do not wish to define such an event, put '0' there. For a full description, of how monster kill events work, see the 'monster' command.

Define a warp point

<from map name>,<fromX>,<fromY>,<facing>|warp|<warp name>|<spanx>,<spany>,<to map name>,<toX>,<toY>

This will define a warp NPC that will warp a player between maps, and while most arguments of that are obvious, some deserve special mention.

SpanX and SpanY will make the warp sensitive to a character who didn't step directly on it, but walked into a zone which is centered on the warp from coordinates and is SpanX in each direction across the X axis and SpanY in each direction across the Y axis.

Warp NPC objects also have a name, because you can use it to refer to them later with 'enablenpc'/'disablenpc'

Facing of a warp object is irrelevant, it is not used in the code and all current scripts have a zero in there.

Define an NPC object:

<map name>,<x>,<y>,<facing>|script|<NPC Name>|<sprite id>,{<code>}
<map name>,<x>,<y>,<facing>|script|<NPC Name>|<sprite id>,<triggerX>,<triggerY>,{<code>}

This will place an NPC object on a specified map at the specified location, and is a top-level command you will use the most in your custom scripting. The NPCs are triggered by clicking on them, and/or by walking in their trigger area, if defined, see that below.

Facing is a direction the NPC sprite will face in. Not all NPC sprites have different images depending on the direction you look from, so for some facing will be meaningless. Facings are counted counterclockwise in increments of 45 degrees, where 0 means facing towards the top of the map. (So to turn the sprite towards the bottom of the map, you use facing 4, and to make it look southeast it's facing 5.)

Sprite id is the sprite number used to display this particular NPC. For a full list of sprite id numbers see http://kalen.s79.xrea.com/npc/npce.shtml You may also use a monster's ID number instead to display a monster sprite for this NPC. It is possible to use a job sprite as well, but you must first define it as a monster sprite in 'mob_avail.txt', a full description on how to do this is for another manual. A '-1' sprite id will make the NPC invisible (and unclickable). A '111' sprite id will make an NPC which does not have a sprite, but is still clickable, which is useful if you want to make a clickable object of the 3D terrain.

TriggerX and triggerY, if given, will define an area, centered on NPC and spanning triggerX cells in every direction across X and triggerY in every direction across Y. Walking into that area will trigger the NPC. If no 'OnTouch:' special label is present in the NPC code, the execution will start from the beginning of the script, otherwise, it will start from the 'OnTouch:' label.

NPC name is kinda special, because it's not only the name of NPC you will see on screen. It's formatted this way:

<Screen name>{#<Extra name identifier>}{::<Label name>}

The extra identifier is there that you can make an npc with an invisible name (just omit the screen name, but keep the identifier name) and so that you can refer to several NPCs which have the same name on screen, which is useful to make an NPC that relocates depending on special conditions, for example - you define several NPC objects and hide all except one. ('Hunter#hunter1','Hunter#hunter2'...) The extra name identifiers will let your code tell them apart.

Label name is used to duplicate NPC objects (more on that below).

The complete NPC name (Screen name + extra identifier) may not exceed 24 characters. The label name is counted separately but also limited to 24 characters.

The code part is the script code that will execute whenever the NPC is triggered. It may contain commands and function calls, descriptions of which compose most of this document. It has to be in curly brackets, unlike elsewhere where we use curly brackets, these do NOT signify an optional parameter.

Define a 'floating' NPC object

-|script|-1,{<code>}

This will define an NPC object not triggerable by normal means. This would normally mean it's pointless since it can't do anything, but there are exceptions, mostly related to running scripts at specified time, which is what these floating NPC objects are for. More on that below.

Define a shop NPC

<map name>,<x>,<y>,<facing>|shop|<NPC Name>|<sprite id>,<itemid>:<price>{,<itemid>:<price>...}

This will define a shop NPC, which, when triggered (which can only be done by clicking) will cause a shop window to come up. No code whatsoever runs in shop NPCs and you can't change the prices otherwise than by editing the script itself. (No variables even exist at this point of scripting, so don't even bother trying to use them.)

The item id is the number of item in the 'item_db.txt' database. If Price is set to -1, the 'buy price' given in the item database will be used. Otherwise, the price you gave will be used for this item, which is how you create differing prices for items in different shops.

Define a function object

function|script|<function name>|{
<code>
}

This will define a function object, callable with the 'callfunc' command (see below). This object will load on every map server separately, so you can get at it from anywhere. It's not possible to call the code in this object by anything other than the 'callfunc' script command.

The code part is the script code that will execute whenever the function is called with 'callfunc'. It has to be in curly brackets, unlike elsewhere where we use curly brackets, these do NOT signify an optional parameter.

Script Body

What a RID is and why do you need to know

Most scripting commands and functions will want to request data about a character, store variables referenced to that character, send stuff to the client connected to that specific character. Whenever a script is invoked by a character, it is passed a so-called RID - this is the account ID number of a character that caused the code to execute by clicking on it, walking into it's OnTouch zone, or otherwise.

If you are only writing common NPCs, you don't need to bother with it. However, if you use functions, if you use timers, if you use clock-based script activation, you need to be aware of all cases when a script execution can be triggered without a RID attached. This will make a lot of commands and functions unusable, since they want data from a specific character, want to send stuff to a specific client, want to store variables specific to that character, and they would not know what character to work on if there's no RID. Unless you use attachrid to explicitly attach a character to the script first.

Whenever we say invoking character, we mean the character who's RID is attached to the running script.

But what about GID?

GID stands for the Game ID of something, this can either be the GID obtained through mobspawn (mob control commands) or the account ID of a character. In the manaplus client, you can select a player, NPC or Mob (with Q, A, or N key for example) and press F10, then select the target tab


Item and pet scripts

Each item in the item database has two special fields - EquipScript and UseScript. The first is script code run every time a character equips the item, with the RID of the equipping character. Every time they unequip an item, all temporary bonuses given by the script commands are cleared, and all the scripts are executed once again to rebuild them. This also happens in several other situations (like upon login) but the full list is currently unknown.

UseScript is a piece of script code run whenever the item is used by a character by doubleclicking on it.

Not all script commands work properly in the item scripts. Where commands and functions are known to be meant specifically for use in item scripts, they are described as such.

Every pet in the pet database has a PetScript field, which determines pet behavior. It is invoked wherever a pet of the specified type is spawned. (hatched from an egg, or loaded from the char server when a character who had that pet following them connects) This may occur in some other situations as well. Don't expect anything other than commands definitely marked as usable in pet scripts to work in there reliably.

Numbers

Beside the common decimal numbers, which are nothing special whatsoever (though do not expect to use fractions, since ALL numbers are integer in this language), the script engine also handles hexadecimal numbers, which are otherwise identical. Writing a number like '0x<hex digits>' will make it recognised as a hexadecimal value. Notice that 0x10 is equal to 16. Also notice that if you try to 'mes 0x10' it will print '16'.

This is not used much, but it pays to know about it.

Variables and scope

The meat of every programming language is variables - places where you store data.

Variables are divided into global (not attached to any specific RID, and independent of whoever triggered the object) and local (attached to a specific character object or a specific account object). They are further divided into permanent (they come back when the server resets) and temporary (they only persist until the server dies). This is what's called variable scope. :)

Unlike in more advanced languages, all temporary variables are essentially 'global', but not in the sense described above - if one NPC sets a temporary variable, even if it is character based, if that character triggers another NPC object, the variable will still be there, so you should be careful and set the variables you mean to be temporary to something sensible before using them. It also pays to keep variable names descriptive and reasonably long.

In the eAthena scripting language, variable names are not case sensitive.

Variables are divided into and uniquely identified by the combination of:

prefix determines the scope and extent (or lifetime) of the variable
name an identifier consisting of '_' and alphanumeric characters
postfix determines the type of the variable: integer or string

Scope can be:

global global to all servers (to be verified)
local local to the server (to be verified)
account attached to the account of the character identified by RID
character attached to the character identified by RID
npc attached to the NPC
scope attached to the scope of the instance

Extent can be:

permanent They still exist when the server resets.
temporary They cease to exist when the server resets.
Prefix: Variable scope
" " Thats right, nothing before a variable, this a permanent variable attached to the character object.
"@" A temporary version of a character-based variable.
SVN versions before 2094 revision and RC5 version will also treat 'l' as a temporary variable prefix, so beware of having variable names starting with 'l' (lowercase L), they will also be considered temporary, even if you didn't mean them to be!(to be verified)
"$" A global permanent variable.
They are stored in "save\mapreg.txt" file and are the only kind of variables stored in a text file in the SQL version.
"$@" A global temporary variable.
This is important for scripts which are called with no RID attached, that is, not triggered by a specific character object.
"#" A permanent account-based variable.
They are stored with all the account data in "save\accreg.txt" in TXT versions and in the SQL versions in the 'global_reg_value' table.


There's also a '##' variable prefix, which denotes some kind of account-based variable, (it gets sent to the char server for storage too) but it is not certain just what makes it different from a regular '#' variable and whether it works completely at all. There is no such thing as a temporary account-based variable. (to be verified)

"##" A permanent global account variable stored by the login server.
They are stored in "save\account.txt" and in the SQL versions in the 'global_reg_value' table, using type 1. The only difference you will note from normal # variables is when you have multiple char-servers connected to the same login server. The # variables are unique to each char-server, while the ## variables are shared by all these char-servers.

All of the above variables store numbers. They can store positive and negative numbers, but only whole numbers (so don't expect to do any fractional math). You can also store a string in a variable, but this means naming it specially to denote it contains text rather than a number:

Postfix: integer or string
nothing integer variable, can store positive and negative numbers,
but only whole numbers (so don't expect to do any fractional math)
"$" string variable, can store text

Examples:

name permanent character integer variable
name$ permanent character string variable
@name temporary character integer variable
@name$ temporary character string variable
$name permanent global integer variable
$name$ permanent global string variable
$@name temporary global integer variable
$@name$ temporary global string variable
.name NPC integer variable
.name$ NPC string variable
.@name scope integer variable
.@name$ scope string variable
#name permanent local account integer variable
#name$ permanent local account string variable
##name permanent global account integer variable
##name$ permanent global account string variable

Some variables are special, that is, they are already defined for you by the scripting engine. You can see the full list somewhere in 'db/const.txt', which is a file you should read, since it also allows you to replace lots of numbered arguments for many commands with easier to read text. The special variables most commonly used are all permanent character-based variables:


Common Special Variables
StatusPoint Amount of status points remaining.
BaseLevel Current base level
SkillPoint Amount of skill points remaining
Class Current job
Upper 1 if the character is an advanced job class.
Zeny Current amount of zeny
Sex Character's gender, 0 if female, 1 if male.
Weight The weight the character currently carries.
MaxWeight The maximum weight the character can carry.
JobLevel Character's job level
BaseExp The amount of base experience points the character has.
Notice that it's zero (or close) if the character just got a level.
JobExp Same for job levels
NextBaseExp Amount of experience points needed to reach the next base level.
NextJobExp Same for job levels.
Hp Current amount of hit points.
MaxHp Maximum amount of hit points.
Sp Current spell points.
MaxSp Maximum amount of spell points.
BaseJob This is sneaky, apparently meant for baby class support.
This will supposedly equal Job_Acolyte regardless of whether the character is an acolyte or a baby acolyte, for example.
Karma The character's karma. Karma system is not fully functional, but this doesn't mean this doesn't work at all. Not tested.
Manner The character's manner rating. Becomes negative if the player utters words forbidden through the use of 'manner.txt' client-side file.


While these behave as variables, do not always expect to just set them - it is not certain whether this will work for all of them. Whenever there is a command or a function to set something, it's usually preferable to use that instead. The notable exception is Zeny, which you can and often will address directly -setting it will make the character own this number of Zeny. If you try to set Zeny to a negative number, the script will be terminated with an error.

  • If a variable was never set, it is considered to equal zero (for number variables) or an empty string ("", nothing between the quotes) for string variables. Once you set it to that, the variable is as good as forgotten forever, and no trace remains of it even if it was stored with character or account data. (to be verified)

Arrays

Arrays (in eAthena at least) are essentially a set of variables going under the same name. You can tell between the specific variables of an array with an 'array index', a number of a variable in that array:

<variable name>[<array index>]

Variables stored in this way, inside an array, are also called 'array elements'. Arrays are specifically useful for storing a set of similar data (like several item IDs for example) and then looping through it. You can address any array variable as if it was a normal variable:

set @arrayofnumbers[0],1;

You can also do sneaky things like using a variable (or an expression, or even a value from an another array) to get at an array value:

set @x,100;
set @arrayofnumbers[@x],10;
   

This will make @arrayofnumbers[100] equal to 10.

Notice that index numbering always starts with 0. Arrays cannot hold more than 256 variables. (So the last one can't have a number higher than 255)

And array indices probably can't be negative. Nobody tested what happens when you try to get a negatively numbered variable from an array, but it's not going to be pretty. :)

Arrays can, naturaly, store strings:

@menulines$[0] is the 0th element of the @menulines$ array of strings. Notice the '$', normally denoting a string variable, before the square brackets that denotes an array index.

Variable type availability

There are some important restrictions on which kinds of variables, depending on their storage location and type, will actually work:


Variable type availability
VarType Norm Array
$Str$ OK! OK!
$@Str$ OK! OK!
@Str$ OK! OK!
#Str$ FAIL! FAIL!
Str$ FAIL! FAIL!
$Int OK! OK!
$@Int OK! OK!
@Int OK! OK!
#Int OK! FAIL!
Int OK! FAIL!


In short, this means two important things:

  1. Account-based and character-based variables cannot form arrays, regardless of whether they are string or integer.
    While the script engine will allow you to define such variables, they will not actually be stored as arrays as you expect, which can lead to hard-to-debug errors.
  2. Account-based and character-based variables may not store strings. Which is a real pain.

Special variables

Only those special variables not related directly to specific script commands are listed here. For a list of those others, see 'getmapxy', 'getinventorylist', 'menu', 'select', 'warpwaitingpc'.


PC_DIE_COUNTER this permanent character-based variable is automatically incremented every time that character dies.
jobchange_level this permanent character-based variable is automatically set to the job level the character had before the job change, regardless of whether the job change was performed through the script command or the GM command.
CLONE_SKILL this permanent character-based variable stores the ID of the skill that has been copied with the Plagiarism Rogue skill, if any such skill has been copied.


Operators

Operators are things you can do to variables and numbers. They are either the common mathematical operations or conditional operators


+ will add two numbers. If you try to add two strings, the result will be a string glued together at the +. You can add a number to a string, and the result will be a string. No other math operators work with strings.
- will subtract two numbers.
* will multiply two numbers.
/ will divide two numbers. Note that this is an integer division rounding down, i.e. 7/2 is not equal 3.5, it's equal 3. (quotient)
% will give you the remainder of the division. 7%2 is equal to 1.


There are also conditional operators. This has to do with the conditional command 'if' and they are meant to return either 1 if the condition is satisfied and 0 if it isn't. (That's what they call 'boolean' variables. 0 means 'False'. Anything except the zero is 'True' Odd as it is, -1 and -5 and anything below zero will also be True.)

You can compare numbers to each other and you compare strings to each other, but you can not compare numbers to strings.


== Is true if both sides are equal. For strings, it means they are the same.
>= True if the first value is equal to, or greater than, the second value.
<= True if the first value is equal to, or less than, the second value
> True if the first value greater than the second value
< True if the first value is less than the second value
!= True if the first value IS NOT equal to the second one


Examples:

1==1 is True.
 1<2 is True while 1>2 is False.
 @x>2 is True if @x is equal to 3. But it isn't true if @x is 2.

Only '==' and '!=' have been tested for comparing strings. Since there's no way to code a seriously complex data structure in this language, trying to sort strings by alphabet would be pointless anyway.

Comparisons can be stacked in the same condition:


&& - Is True if and only if BOTH sides are true.
|| Is True if either side of this expression is True.


1=1 && 2=2 is True.
1=1 && 2=1 is False.
1=1 || 2=1 is True.

Binary logical operators work only on numbers:


<< Left shift.
>> Right shift.
& And.
Or.
^ Xor.


If you don't know what these five mean, don't bother, you don't need them. Whether '!' works as a binary not operator for numbers has not been tested.

Labels

Within executable script code, some lines can be labels:

<label name>:

Labels are points of reference in your script, which can be used to route execution with 'goto', 'menu' and 'jump_zero' commands, invoked with 'doevent' and 'donpcevent' commands and are otherwise essential. A label's name may not be longer than 22 characters. (23rd is the ':'.) There is some confusion in the source about whether it's 22, 23 or 24 all over the place, so keeping labels under 22 characters could be wise.

In addition to labels you name yourself, there are also some special labels which the script engine will start execution from in all scripts it finds them in if a special event happens:

OnClock<hour><minute>:
OnHour<hour>:
On<weekday><hour><minute>:
OnDay<month><day>:

This will execute when the server clock hits the specified date or time. Hours and minutes are given in military time. ('0105' will mean 01:05 AM). Weekdays are Sun, Mon, Tue, Wed, Thu, Fri, Sat. Months are 01 to 12, days are 01 to 31. (Remember the zero. :)

OnInit:
OnInterIfInit:
OnInterIfInitOnce:

OnInit will execute every time the scripts loading is complete, including when they are reloaded with @reloadscript command. OnInterIfInit will execute when the map server connects to a char server, OnInterIfInitOnce will only execute once and will not execute if the map server reconnects to the char server later.

OnAgitStart:
OnAgitEnd:
OnAgitInit:
OnAgitEliminate:
OnAgitBreak:

OnAgitStart will run whenever the server shifts into WoE mode, whether it is done with @agitstart GM command or with 'AgitStart' script command. OnAgitEnd will do likewise for the end of WoE. OnAgitInit will run when castle data is loaded from the char-server by the map server. (Notice that it won't run when you @reloadscript.) OnAgitBreak runs in all NPCs of a map when an Emperium is destroyed. While it is explicitly defined as an event to run when it breaks whenever an Emperium is spawned, it has some builtin code support for it, so it's not certain whether you can have that event named anything else. OnAgitEliminate is similar in that respect, and it runs when an Emperium is destroyed in a castle that is currently not owned by a guild.

No RID will be attached while any of the abovementioned labels are triggered, so no character or account-based variables will be accessible, until you attach a RID with 'attachrid' (see below).

OnTouch:

This label will be executed if a trigger area is defined for the NPC object it's in. If it isn't present, the execution will start from the beginning of the NPC code. The RID of the triggering character object will be attached.

OnPCKilledEvent:
OnPCKillEvent:
OnPCDieEvent:

These special labels will be invoked if you have set 'event_script_type' value in your 'script_athena.conf' to 1, and you can change their names by altering the configuration options in 'script_athena.conf'. Otherwise, they are only available as special NPC object names. For more information, see 'npc/sample/PCLoginEvent.txt'


PCDieEvent triggers every time a character is killed by anything, with their character ID as RID. If used as an NPC object name, this object name will be triggered. If used as a label, 'OnPCDieEvent', will be triggered as a label.
PCKillEvent triggers every time a character kills another character, with the killer's ID as RID. The ID of the killed character is set in the special character-based permanent variable '@victimrid' (and the level as @victimlvl) on the killer character. If used as a label, 'OnPCKillEvent', will be triggered as a label.This event will be executed before PCDieEvent, if both are defined.
PCKilledEvent triggers every time a character is killed by another character, with the killed character's ID as RID. The ID of the killing character is set in the special character-based permanent variable '@killerrid' on the killed character. If used as a label, 'OnPCKilledEvent', will be triggered as a label.This event will be executed before PCKillEvent, if both are defined.


Only the special labels which are not associated with any script command are listed here. There are other kinds of labels which may be triggered in a similar manner, but they are described with their associated commands.