This is an old revision of the document!
The client scripting interface can be used to control a Crossfire client using an external program (script). This interface can be used to extend the client or automate repetitive tasks (see sample scripts).
The script is started from the client and communicates by reading information from standard input and writing commands to standard output. A full list of commands can be found below.
Remember to chmod +x the script so that its executable. In the client type script <pathname>
The script should start.
see also: Windows-specific notes
If you write you script in Java it gets a bit more tricky but no worries its not that hard. We are going to create a shell script that will run our scripts in Java for us.
#!/bin/bash cd /dir/to/java.class java $*
Remember to chmod +x this script so that its executable. Now to run your script this is all you have to do is “script /path/to/bashScript blah” blah being your .class file - .class and /path/to/bashScript being the executable bash script.
(If this section causes you to consider puking by the second line, jump to the next section.)
The script is a program which is external to the client. It can be written in a wide range of languages. This can be C, Java, Perl, Python, Bash script, php, anything you can think about. How can this be possible? Let's take a look at what happens when you type “echo hello world” on a shell prompt. Sure it writes on your terminal “hello world”. And if you are in a graphical shell prompt? It appears in the graphical console! Some process changed your request of writing to screen to a complex process of get some system font and render the line in the specified window at the specified position. All this because when you ask to “write hello world to screen” you ask, really, to “write hello world to the standard output device”. This standard output device is called stdout. There is also the stdin, which most of the time is your keyboard and stderr, the standard error device being most of the time the same as stdout.
Ok, and now? Now what we use is exactly the same trick as the graphical console. when the client runs a script, it changes the script's stdin and replace the keyboard input with it's own orders (using a pipe). And it changes the stdout so instead of writing to screen, the script sends data to the client (using another pipe). And this is how any language can be used. Because every language can write to the screen and read from the keyboard!
Learn to say hello
Here we go for the first script. We will do quite simple things. We will ask our character to say “hello world” around. The script will be written in C because we simply need to choose a language. The script is quite simple:
#include <stdio.h> int main() { puts("issue 1 1 say Hello world!\n"); }
Name it first.c, compile it and launch it in your command shell:
tchize@Urd:~$ /home/tchize/script/first issue 1 1 say hello world
No surprise in the output (notice the \n at the end in source file). Now we are going to run it in the client. Start the client and log in your prefered server. When it's done, type in the following command:
scripts
Client will say you have no running scripts. That's good. Now type:
script <path_to_my_first_script>
where path_to_my_first_script is the location of your first script. For example I typed:
script /home/tchize/script/first
and you character says hello to the world. Nice, isn't it? Now try this yourself with the following script:
int main(){ printf("issue 1 1 shout hello world!\n"); printf("issue 1 1 shout I use scripts!\n"); }
Do you get the idea? every printf you make is a command to the client scripting interface. So now let's look at this command. It begins with issue followed by 2 integers. The command issue is part of the interacting of the Client Scripting Interface. It allows a script to send any command the player could send. There are lots of them i won't explain here. Just issue the command 'help commands' to get the list. What are those 2 integers? The first one is repeat. It typically is used for dropping items, but can be used in other cases. The second integer, 1 or 0, is must send. If it is one, the command must be sent, if it is zero, command may be lost in the client-server process. Most user will set this to 1.
Spy Kids
What to do next? As you can see, our script doesn't wait very long after issuing commands. And it doesn't get information from the client at all. In fact it just hopes it is really speaking to the client. We are going to write a simple script which will issue a command to the client and then gets the result. We are going to spy!
Use the following script and run it in client. Ensure you ran the client in a console or you won't see any result!
#include <stdbool.h> #include <stdio.h> #define LENGTH 200 int main() { char buffer[LENGTH]; // Ask the client for a copy of all commands sent to the server. fprintf(stdout, "monitor\n"); fflush(stdout); // Read the commands and print to standard error. while (fgets(buffer, LENGTH, stdin) != NULL) { fputs(buffer, stderr); } return 0; }
Now move a bit in the game. A few steps are enough! Look at your console, you should see something like this:
monitor 0 0 east monitor 0 0 east monitor 0 0 east monitor -1 0 run 7 monitor -1 1 run_stop
If you type the command scripts in your client you will see our script is still running.
Let's look more closely at the code. We define a character buffer and a length. We will use the buffer to read what the client sends to the script. Then our script sends to the client the command monitor (don't forget the \n). This command asks the client to give the script a copy of all commands sent from the client to the server. Now each time a command is sent from client to server, the script will get a “monitor <command>” string.
fflush(stdout)
The stdout has something called a buffer. When you write to output device, it's not immediatly sent to it. For performance reasons, successive print to stdout are grouped. Most of the time, \n is enough to force sending of data, but we ensure all data are sent to client by flushing the stdout (force empty buffer). In the future, when you think client didn't get a command but the script did send it, ensure you flushed stdout.
Then comes a loop. This loop will read from stdin (where client puts informations for the script) and copy them to stderr (our only access to console since stdout is a connection to client). Because I don't want to use scanf I used the binary read and write commands. Stdin is the file handle 0 and stderr is file handle 2. We first read up to 200 char from stdin and if we read something we write it to stderr. If we didn't read anything, that means we have lost the client (shouldn't happen) and we simply exit.
Since we asked to monitor all commands from client to server, we get them. These commands are our move commands and they use the same format as issue. If you run our first script while this second script is still running, you will still say hello world, but you'll get the following in your console:
monitor 1 1 say hello world
So client sends us lines made of
monitor <repeat> <must_send> <command>
Now kill the script by typing command
scriptkill <pathtoscript>
or
scriptkill <number>
Then type scripts to ensure it's stopped (or to figure out the number of the script). Edit it, comment the line printf(“monitor\n”), compile and run the script again. Move and look at console. You see nothing. The script didn't ask anything so the client didn't tell it anything. Your script will only get that for which it asks.
Now try the following code:
#include <stdio.h> int main (){ char buf[200]; int len; printf ("watch stats\n"); fflush (stdout); for(;;){ len=read(0,buf,200); if(len) write(2,buf,len); else exit(-1); } }
This time we are requesting commands sent from server to client. But there are far more of them. So watch takes as argument the beginning of all commands we want to get. Here we want every stat command from client. And in our console we see, when running script:
watch stats food 398 watch stats food 397 watch stats food 396 watch stats food 395
Wow! This mean we know when food is low and we can ask our script to invoke restoration (remember the issue command?) when our character's food gets below 50! No starvation anymore.
There are two things you can still do with Script. The first is to request a bit of informations. The client then tells the script what it wants to know. The second thing is triggering an action of the script from client interface. The command scripttell allows player to say something to a script. The script will get the exact command typed by player. See below for command list.
Here is a list of command the script can send to client.
<box round | Data Type Table>
Data Type | Comment |
---|---|
range | Return the type and name of the currently selected range attack |
stat <type> | Return the specified stats |
stat stats | Return Str,Con,Dex,Int,Wis,Pow,Cha |
stat cmbt | Return wc,ac,dam,speed,weapon_sp |
stat hp | Return hp,maxhp,sp,maxsp,grace,maxgrace,food |
stat xp | Return level,xp,skill-1 level,skill-1 xp,… |
stat resists | Return resistances |
weight | Return maxweight, weight |
flags | Return flags (fire, run) |
items inv | Return a list of items in the inventory, one per line |
items actv | Return a list of inventory items that are active, one per line |
items on | Return a list of items under the player, one per line |
items cont | Return a list of items in the open container, one per line |
map pos | Return the players x,y within the current map |
map near | Return the 3×3 grid of the map centered on the player |
map all | Return all the known map information |
map <x> <y> | Return the information about square x,y in the current map (relative to player position) |
</box>
in order to get the result of the request into your script's stdin you have to “watch request <data type>” before you do the request.
Here is an incomplete list of information strings send by client to script. Those informations are sent only because the client asked them, except for scripttell.
This command has it's particularities. I'll try to detail all I've encountered so far.
Usage:
issue [<repeat> [<must_send>]] <command>
The next examples should clarify the use of numeric parameters.
Some examples that work:
issue 0 0 apply issue 0 1 apply issue 1 0 apply issue 1 1 apply issue 1 1 stay f issue 0 0 stay f issue 1 1 south issue 0 0 south issue 0 0 examine bag issue 1 0 get nugget (works as 'get 1 nugget') issue 2 0 get nugget (works as 'get 2 nugget') issue 1 1 get nugget (works as 'get 1 nugget') issue 2 1 get nugget (works as 'get 2 nugget') issue 0 0 get nugget (works as 'get nugget') issue lookat 2 3 (works as left clicking on the spot 2 tiles to your right and 3 tiles down, coords are relative to player position)
Some examples that do NOT work:
issue apply issue 0 apply issue 1 apply issue 2 0 apply (works only once, not twice as expected) issue 2 1 apply (works only once, not twice as expected) issue get 4 nugget issue 4 1 south (works only once, not 4 times as expected) issue 4 0 south (works only once, not 4 times as expected) issue 1 1 lookat 2 3 issue 0 1 lookat 2 3 issue 0 0 lookat 2 3
add a complete set of commands that work with <repeat> and another of those that work without it.
Scripting works thanks to a patch from archaios. Known issues are: