Creation/Dev/NPC-Server
The NPC Server allows for the serverside manipulation of scripts, players and objects. It allows scripts to run whilst no player is around, as well as allowing for players to pass data to and from scripts and players in other levels without any meddling with server strings.
Reproduced below is the official NPC Server documentation, formatted for this Wiki and slightly annotated to indicate changes and updates. Annotations appear within single square brackets and are printed in boldface [like this].
The NPC Server is a program which logins to the GServer similar to a normal user and controls NPCs in a specified set of levels. [The NPC Server presently controls all of the server's scripts, regardless of their location.] It runs in a loop, executes NPC scripts and sends the updates of the NPC attributes and player attributes to the GServer, which then sends it to the players if needed (only a few informations are sent to the player).
1. The NPC Server program consists of the following instructions:
- load options
- connect to GServer
- if connection can be established:
- load levels
- load NPCs from database
- while the connection to the GServer is up:
- get data from GServer; this contains following type of data:
- player attributes
- temporary level data (bombs, explosions, bush removing, items, ...)
- PMs
- game time
- levels to update/reload
- server. flags added/deleted
- run all NPC scripts
- send changed NPC attributes to the players
- send changed player attributes to the GServer & players
- wait till 0.1 seconds are over
- get data from GServer; this contains following type of data:
- if connection can be established:
2. The scripting on the NPC Server is different then the Graal client-side scripting. Some commands are missing (mainly temporary graphical / sound stuff), some commands are added (easier accessing of player attributes). In section 1 you might have seen that the minimum timeout is now 0.1 seconds because the NPC scripts only run all 0.1 seconds. [It is widely considered a bad habit to use 0.1 second timeouts on the serverside, whether you technically can or not, as it results in the script using lots of processor time. Unless unavoidable, you should not use timeouts this small.] There is a new set of events happening on the NPC Server:
- onCreated: the NPC script is run for the first time
- onInitialized: the NPC is reloaded (e.g. restart of NPC Server)
- onPlayerenters: a player enters the current level
- onPlayerleaves: the player leaves the current level (doesn't happen when he disconnects)
- onPlayertouchsme: the player touchs the NPC (see setshape command to make this working)
- onPlayerchats: the player chat text has been changed
- onNpcwarped: the NPC has entered a different level (see canwarp / canwarp2)
- onExploded: an explosion has hit the NPC
More events are added when needed.
All code of the NPCs are done server-side, except when you add a line //#CLIENTSIDE, then all following code is sent to the client. [This line serves to split the script into two halves. A common misconception is that there is also a //#SERVERSIDE statement. There isn't. Don't ask.] The client only reads attributes of the NPCs, but doesn't send it to other players when he changes it. So the client-side part of the script is for graphical stuff and music, not for things which must be secure or need to be synchronized with other players.
3. Following commands are supported by the NPC Server:
- destroy();
- if (condition) command(s);
- while (condition) command(s);
- for (command;condition;command) command(s);
- break;
- continue;
- sleep(time);
- set(flagname); [deprecated]
- unset(flagname); [deprecated]
- setstring(flagname,value); [deprecated]
- message(messagetext);
- setgif/setimg(filename);
- setgifpart/setimgpart(filename,offsetx,offsety,width,height);
- setshape(shapetype,width,height);
- drawoverplayer();
- drawunderplayer();
- dontblock();
- blockagain();
- show();
- hide();
- noplayeronwall();
- showcharacter();
- setcharprop(messagecode,value); [deprecated]
- setcharani(ganiname,parameter(s));
- setchargender(male/female);
- putbomb(power,x,y);
- putexplosion(radius,x,y);
- putexplosion2(power,radius,x,y);
- setarray(variablename,arraysize); [deprecated]
- canwarp();
- canwarp2();
- cannotwarp();
- callnpc(index,eventname);
- with (object) command(s);
- join(classname);
- warpto(levelname,x,y);
- tokenize(str);
- tokenize2(delimiters,str);
- setplayerprop(messagecode,value); [deprecated]
- setani(ganiname,parameter(s));
- setgender(male/female);
- say(signindex);
- enableweapons();
- disableweapons();
- toweapons(weaponname); [HIGHLY deprecated]
- addweapon(weaponname);
- removeweapon(weaponname);
- setlevel2(levelname,x,y);
- freezeplayer2();
- unfreezeplayer();
Explanation of functions:
- setshape():
- Because the NPC Server doesn't load images and so doesn't know the size of the NPC this is needed for letting the NPC Server know what the size of the NPC is so that it can automatically detect if the player touchs the NPC; at this time only rectangle shapes are supported, so you need to do setshape 1,width,height; [Width and height are expressed in pixels here, not tiles: one tile is a 16x16 block of pixels] (0 would be directly take the image, this will be supported on client-side too someday)
- noplayeronwall():
- When doing onwall() or onwall2() then the NPC Server doesn't check for players, this can be good if you want to do your own detection for near players or when the NPC shouldn't be stopped by players (like the Elephant, it alway has players on it so it couldn't move)
- setgender()/setchargender():
- simply changes the gender of the player/NPC
- canwarp()/canwarp2():
- This command only works for database NPCs, other NPCs are not allowed to go out of the level. [NPCs created with the putNPC2; command can also leave levels] By calling these commands, you enable automatic warping when the NPC touchs a link. canwarp is for NPCs which already know the way and want to be warped exactly like a player (good when you use the waytracker item for recording a way for police NPCs or so); canwarp2 is for overworld NPCs which move randomly, so this is using the normal links and check for onwall on the destination level, it also doesn't let the NPC go into small links (houses)
- cannotwarp():
- disables automatic warping again
- with(): [Not technically a function]
- Normally you can get informations about players by accessing players[i].xxx variables or attaching a (i) to message codes (like #a(i) gives you the account name of the i-th player in the current level), but when doing server-side NPCs then you also want to use all the commands you have for manipulating players aimed at special players, for this you can use the with command.
- with (players[i]) { setlevel2 worldl-17.nw,30,30; } will warp player with index i to the specified level,
- with (getplayer(Stefan)) { setplayerprop #3,head19.gif; } will give the player with accountname Stefan the head with filename head19.gif.
- When the requested player doesn't exist, then the commands inside { } will not be executed. You shouldn't do sleep/break/continue inside a with block, because then the old player is not reset when leaving the with block.
- Normally you can get informations about players by accessing players[i].xxx variables or attaching a (i) to message codes (like #a(i) gives you the account name of the i-th player in the current level), but when doing server-side NPCs then you also want to use all the commands you have for manipulating players aimed at special players, for this you can use the with command.
- with (NPCs):
- The commands with (NPCs[index]) ... and with (getNPC(name)) ... are similar to the 'with' commands which change the current player, they just set the current NPC. All this. variables and variables based on the NPC level are now read from the requested NPC. Only local script variables (like i,j) still work inside the 'with' brackets. When calling 'with' to change the current NPC it doesn't change the script itself, the commands and functions are still called from the original NPC.
- join():
- This normally loads a text file (join bomys; will load bomys.txt from the configured scripts folder) [on all recent playerworlds, this is the scripts/ folder in the file browser and can be more easily accessed through the NPC-Control's Class Browser] and adds it to the NPC script, so it works similar to an include. The internal representation is slightly different: each NPC has a list of scripts, the own script is parsed and added as the first script in this list; when you do join then another script is parsed and added to the script list; when the script for the requested class is already in the memory (used by another NPC) then it is not copied, it is just added to the script list; so when several NPCs are using the same class then it is only hold one time in the memory and the script object is put into the script listsof the NPCs; functions can be overwritten
- warpto():
- warps the NPC into another level, only works when it is a database NPC [or a putNPC2'd NPC]
- addweapon():
- This is like 'toweapons', but the script for the weapon is not taken from the current NPC, its taken from database, which means the weapon must already exist. This command is used in the trading house in Graal2001.
- removeweapon():
- just deletes a weapon
- tokenize()/tokenize2():
- This comes with a bunch of functions and message codes to support string parsing:
- str.length() - length of the string
- str.starts(part) - tests if 'str' is starting with 'part'
- str.pos(part) - gives the position of 'part' in 'str' (-1: doesn't appear in 'str')
- str.substring(start[,length]) - extracts a substring out of str
- str.trim() - removes the spaces at the beginning and at the end
- str.tokenize(delims); - divides the specified string into tokens, e.g. ("I am happy!").tokenize(" "); will produce 3 tokens (I, am, happy!)
- tokenscount - gives you the count of tokens produced with tokenize
- #t(index) - gives you a token
- This comes with a bunch of functions and message codes to support string parsing:
4. Variable types
There are several new variables types, here a list of all currently available types:
- Floating point numbers which can be used in normal assignments. They differ in some attributes: some are read-only, only available in a certain parts of the world / of the scripts, or are not saved.
- varname
- only available for the current script / class script
- this.varname
- for the current NPC; when this is a database NPC [or putNPC2'd NPC] then the this. variables are saved in the database, all other variables are dynamically and not saved
- level.varname
- for the current level (where the NPCs is in)
- server.varname
- server-wide variables (only for the current NPC Server)
- object attributes (NPCs, players, signs) like in normal graal, only one new variable (playerlastdead) which remembers the time of the last dead / login (works only on graal2001 yet)
- built-in variables
- varname
- Flags / string variables (can be used with set, unset, setstring and the #s message code)
- flags
- are for the current player and are stored in his account
- client.flags
- like normal flags, but will also be sent to the client and the client can change them
- this.flags
- are for the current NPC and are stored in database too when the NPC is a database NPC
- server.flags
- like on normal graal, they are server-wide and are also shared with other NPC Servers of the same world; [It is unknown what this comment means; serverstrings and flags are restricted to the playerworld upon which they were created] they are not sent to the player
- flags
5. NPC Control
Things that are good to know when using the NPC control:
- add NPC
- The id must be unique. All database NPCs (except built-in NPCs like the gralats) must have ids >=1000. The level name and position is the start position, when you do reset NPC then the NPC will be warped back to this position.
- reset NPC
- Resets all NPC attributes (except the script) and puts it back to the starting position.
- changing scripts
- The script of the NPC is updated, but when the NPC does sleep then it might not continue to run, because the NPC Server stops a running sleep, the return line might have changed. So you will sometimes need to do reset the NPCs afterwards.
- adding/changing classes
- This is for changing the scripts of classes which NPCs can join with 'join <classname>;'. When you add it/change it then it is saved in the scripts folder of the server. When NPCs already member of that class then they will automatically use the updated class script (because its only hold one time in the memory). When no NPC is using it then it will not appear in the class list. [Even when NPCs use a class, sometimes it refuses to appear in this list. Uploading a level which joins a class BEFORE the class is created seems to work quite well to alleviate this.]
- NPC attributes
- Gives a list of the variables and flags of the NPC, but these cannot be directly changed yet. [This is still true as of July 2004]
- local NPCs
- Dumps all variables of all scripts in a given level, including level. variables and local NPCs.
- reload level list
- In the NPC Server configuration you must specify a text file which lists all levels that are served by this NPC Server. When you add new levels and you want the NPCs working in that level then you need to add the new level names to the level list, then reload the level list to the NPC Server.
There are also some chat commands, type /help on the NPC control edit line to see a list of commands.
6. New functions
In the options file for the NPC Server you can specify a map file (like bigmap.txt for Graal.exe). With the functions onmapx(levelname) and onmapy(levelname) you can get the x/y position of a level on the map.