User Tools

Site Tools


client_side_scripting:client_scripting_interface-basic_howto

Client Scripting Interface

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 communicates with the client by reading information from standard input and writing commands to standard output. Scripts can be written in any programming language. The same client scripting interface is used by the GTKv2 client and the JXClient. Any discrepancy should be reported as a bug.

Using Scripts in the Client

Several client commands are available to start, stop, list, and communicate with scripts:

  • script <path>: start a script located at <path>.
  • scripts: list currently running scripts, along with their numerical IDs
  • scripttell <id> <string>: send text to currently running script <id>
  • scriptkill <id>: stop currently running script <id>
  • JXClient only:
    • scriptkillall: stop all currently running scripts
    • Unique alphanumeric substrings of <path> may be used as <id> so that script names may be used in lieu of numbers

Writing Scripts

The script must be executable (e.g. chmod +x with an appropriate shebang). For some languages, it may be necessary to write a wrapper script to start the program. Additionally, on Windows systems where the shebang is not supported, the player must provide a means of running such a script. For example script python <path-to-script> if you are using python.

Hello World

Save the following script in a file, mark it executable, and run it in the client using the script command:

  #!/usr/bin/env python
  print("issue 1 1 say Hello, world!")

The issue interface command runs a given command as if a user typed it into the command window. It takes 3 arguments: repeat, the number of times to repeat the command (typically used for dropping items); must_send, an integer either zero or one; and command, the command to execute.

Monitoring

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.

A strange C command

fflush(stdout)

The stdout has something called a buffer. When you write to output device, it's not immediately 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 information 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>

(The JXClient additionally recognizes partial script names as well as numbers and has an additional command scriptkillall to stop all currently running scripts in one go.)

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.

Reference

There are two things you can still do with scripts. The first is to request a bit of information. 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.

Commands to client

Here is a list of command the script can send to client.

  • watch <command type> - watch the given command from server-client protocol. <command type> specifies the commands we want to watch. Set to empty to get all commands.
    • Besides what is already documented, watch also supports “tick”, “drawextinfo” (for most servers), and “drawinfo” (for very old servers)
    • The “tick” argument will provide the script with “watch tick <integer>”, as a tick of the server's clock.
    • “drawextinfo” provide the script with text printed in the Messages text area. The format is “watch drawextinfo <integer> <integer> <message>”. For some very old servers (none of them which are online at the time of writing), “drawextinfo” is not supported and you must fall back to “drawinfo”, which has the arguments “watch drawinfo <integer> <message>”.
  • unwatch <command type> - unwatch the given command from server-client protocol. <command type> specifies the commands we want to watch. Set to empty to get all commands.
  • request <data type> - Request a piece of information from client memory. Following is a table of <data type> allowed:

<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)
player Return the player's tag and title
skills Return a list of all skill names, one per line (see also stat xp)
spells Return a list of known spells, one per line
stat paths Return spell paths: attuned, repelled, denied

Note: player, skills, spells, stat path were added between versions 1.11 and 1.50 of the GTK2 client.

  • issue <repeat> <must_send> <command> - send <command> to server on behalf of client. <repeat> is the number of times to execute command <must_send> tells whether or not the command must sent at all cost (1 or 0). <repeat> and <must_send> are optional parameters. See The Issue Command for more details.
  • draw <color> <text> - draw the following text on client interface with given color. Usefull for debugging and may help you to forget about using the stderr.
  • monitor - start monitoring commands send from client to server. Doesn't take any parameter. This goes to the script's stdin automatically.
  • unmonitor - stop monitoring commands send from client to server. Doesn't take any parameter.
  • Special case ISSUE commands (these are issued without repeat and mustsend arguments):
    • issue apply <tag> - Applies the object corresponding to <tag>.
    • issue take <tag> [<count>] - Retrieves the object corresponding to <tag>. If <count> is given, it represents how many items to take. By default all items are retrieved.
    • issue drop <tag> [<count>] - Discards the object corresponding to <tag>. If <count> is given, it represents how many items to drop. By default all items are discarded.
    • issue mark <tag> - Marks the object corresponding to <tag>.
    • issue lock <newstate> <tag> - Locks or unlocks the object corresponding to <tag> in your inventory depending on the value of <newstate>. Valid values are 1 for lock and 0 for unlock.

Information from client

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.

  • scripttell <scriptnumber> <additional data> - user send special command to this script specifically
  • monitor <repeat> <must_send> <command>
  • monitor mark <tag>
  • monitor lock <new state> <tag> - If monitor is on, <command> is a command send to server by the client with given repeat. mark and lock are special cases.
  • watch <command> <data> - You have put a watch on command or a part of command (like A to watch for AddMe command). This command was send by server and your are notified of it. Content of <data> vary on command and maybe very complex
  • request map <x> <y> <darkness> <need_update> <have_darkness> <need_resmooth> <cleared> smooth <face_bottom> <face_middle> <face_top> heads <face_bottom> <face_middle> <face_top> tails <face_bottom> <face_middle> <face_top> - Bunch of informations about square <x>,<y>
  • request map <x> <y> unknown - error occured.
  • request map pos <x> <y> - Tells script current position of player
  • request map end - Marks the end of a complete map transfer from client to script. Helpful.
  • FIXME NOTE more information strings to be added here, incomplete list

The issue command

This command has it's peculiarities.

Usage:

issue [<repeat> [<must_send>]] <command>
  • <repeat> - Times the command should be issued (not repeated after it's initial execution). It's an optional numeric parameter. (Doesn't always work as expected.)
  • <must_send> - May be 1 or 0. If set to 1 the command will always be sent to the server. Otherwise it may be discarded under certain circumstances.

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
  • FIXME Move “issue <cmd> <tag>” documentation here, better formatted.

Platform-Specific Notes

Known issues are:

  • If you want to run a Perl script for instance, you need to issue 'perl <script_name.pl>, even if .pl is correctly seen as perl script.
  • If script doesn't output anything, try turning off buffering (in perl $| = 1) or flush your pipe, or add a sleep at end of program. It seems Windows closes pipes at script termination before client gets time to grab output.

A Few Thanks

Scripting was rendered initially functional with a patch from archaios and is being kept current by GTKv2 client maintainers Partmedia and crowbert, and JXClient maintainer Ragnor.

client_side_scripting/client_scripting_interface-basic_howto.txt · Last modified: 2021/06/19 08:12 by boingman