1 /*
2  * Crossfire -- cooperative multi-player graphical RPG and adventure game
3  *
4  * Copyright (c) 1999-2013 Mark Wedel and the Crossfire Development Team
5  * Copyright (c) 1992 Frank Tore Johansen
6  *
7  * Crossfire is free software and comes with ABSOLUTELY NO WARRANTY. You are
8  * welcome to redistribute it under certain conditions. For details, please
9  * see COPYING and LICENSE.
10  *
11  * The authors can be reached via e-mail at <crossfire@metalforge.org>.
12  */
13 
14 /**
15  * @file
16  * Handles the client-side scripting interface.
17  *
18  * Each script is an external process that keeps two pipes open between the
19  * client and the script (one in each direction).  When the script starts, it
20  * defaults to receiving no data from the client.  Normally, the first command
21  * it sends to the client will be a request to have certain types of data sent
22  * to the script as the client receives them from the server (such as drawinfo
23  * commands).  The script can also request current information from the
24  * client, such as the contents of the inventory or the map data (either live
25  * or last viewed "fog-of-war" data).  The script can also send commands for
26  * the client to pass to the server.
27  *
28  * Script Commands:
29  *
30  * watch {command type}
31  *   whenever the server sends the given command type to the client, also send
32  *   a copy to the script.
33  *   Note that this checked before the client processes the command, so it will
34  *   automatically handle new options that may be added in the future.
35  *   If the command type is NULL, all commands are watched.
36  *
37  * unwatch {command type}
38  *   turn off a previous watch command.  There may be a slight delay in
39  *   response before the command is processed, so some unwanted data may
40  *   still be sent to the script.
41  *
42  * request {data type}
43  *   have the client send the given data to the script.
44  *
45  * issue [{repeat} {must_send}] {command}
46  *   issue the specified command to the server.
47  *   if {repeat} isn't numeric then the command is sent directly
48  *   For "lock" and "mark" only, the parameters are converted to binary.
49  *
50  * draw {color} {text}
51  *   display the text in the specified color as if the server had sent
52  *   a drawinfo command.
53  *
54  * monitor
55  *   send the script a copy of every command that is sent to the server.
56  *
57  * unmonitor
58  *   turn off monitoring.
59  *
60  * sync {#}
61  *   wait until the server has acknowledged all but {#} commands have been
62  *   received
63  *
64  * To implement this:
65  *
66  * Processing script commands: gtk/gx11.c:do_network() and
67  * x11/x11.c:event_loop() are modified to also watch for input from scripts
68  * in the select() call, in which case script_process(fd) in this file is
69  * called.
70  *
71  * Handling watches: common/client.c:DoClient() is modified to pass a copy
72  * of each command to script_watch() before checking for it in the table.
73  *
74  * Handling of monitor: common/player.c:send_command() is modified to pass
75  * a copy of each command to script_monitor() before sending to the server.
76  *
77  * Handling of requests: global variables are directly accessed from within
78  * this file.
79  *
80  * Handling of issues: send_command() is called directly.  Note that this
81  * command will be sent to any scripts that are monitoring output.
82  *
83  * Launching new scripts: common/player.c:extended_command() is extended to
84  * add a check for "script <scriptname>" as an additional command, calling
85  * script_init().  Also added is the "scripts" command to list all running
86  * scripts, the "scriptkill" command to terminate a script (close the pipes
87  * and assume it takes the hint), and the "scripttell" command to send a
88  * message to a running script.
89  */
90 
91 /*
92  * Include files
93  */
94 
95 /*
96  * This does not work under Windows for now.  Someday this will be fixed :)
97  */
98 
99 #include "client.h"
100 
101 #include <ctype.h>
102 
103 #ifndef WIN32
104 #include <errno.h>
105 #include <sys/types.h>
106 #include <sys/socket.h>
107 #include <sys/wait.h>
108 /* for SIGHUP */
109 #include <signal.h>
110 #endif
111 
112 #include "external.h"
113 #include "mapdata.h"
114 #include "p_cmd.h"
115 #include "script.h"
116 
117 /*
118  * Data structures
119  */
120 struct script {
121     char *name; /* the script name */
122     char *params; /* the script parameters, if any */
123 #ifndef WIN32
124     int out_fd; /* the file descriptor to which the client writes to the script */
125     int in_fd; /* the file descriptor from which we read commands from the script */
126 #else
127     HANDLE out_fd; /* the file descriptor to which the client writes to the script */
128     HANDLE in_fd; /* the file descriptor from which we read commands from the script */
129 #endif /* WIN32 */
130     int monitor; /* true if this script is monitoring commands sent to the server */
131     int num_watch; /* number of commands we're watching */
132     char **watch; /* array of commands that we're watching */
133     int cmd_count; /* bytes already read in */
134     char cmd[1024]; /* command from the script */
135 #ifndef WIN32
136     int pid;
137 #else
138     DWORD pid;   /* Handle to Win32 process ID */
139     HANDLE process; /* Handle of Win32 process */
140 #endif
141     int sync_watch;
142 };
143 
144 /*
145  * Global variables
146  */
147 static struct script *scripts = NULL;
148 
149 static int num_scripts = 0;
150 
151 /*
152  * Prototypes
153  */
154 static int script_by_name(const char *name);
155 
156 static void script_dead(int i);
157 
158 static void script_process_cmd(int i);
159 
160 static void send_map(int i, int x, int y);
161 
162 static void script_send_item(int i, const char *head, const item *it);
163 
164 
165 /*
166  * Functions
167  */
168 
169 #ifdef WIN32
170 
171 #define write(x, y, z) emulate_write(x, y, z)
172 #define read(x, y, z) emulate_read(x, y, z)
173 
emulate_read(HANDLE fd,char * buf,int len)174 static int emulate_read(HANDLE fd, char *buf, int len)
175 {
176     DWORD dwBytesRead;
177     BOOL rc;
178 
179     FlushFileBuffers(fd);
180     rc = ReadFile(fd, buf, len, &dwBytesRead, NULL);
181     if (rc == FALSE) {
182         return(-1);
183     }
184     buf[dwBytesRead] = '\0';
185 
186     return(dwBytesRead);
187 }
188 
emulate_write(HANDLE fd,const char * buf,int len)189 static int emulate_write(HANDLE fd, const char *buf, int len)
190 {
191     DWORD dwBytesWritten;
192     BOOL rc;
193 
194     rc = WriteFile(fd, buf, len, &dwBytesWritten, NULL);
195     FlushFileBuffers(fd);
196     if (rc == FALSE) {
197         return(-1);
198     }
199 
200     return(dwBytesWritten);
201 }
202 
203 #endif /* WIN32 */
204 
script_init(const char * cparams)205 void script_init(const char *cparams) {
206 #ifndef WIN32
207     int pipe1[2], pipe2[2];
208     int pid;
209     char *name, *args, params[MAX_BUF];
210 
211     if (!cparams) {
212         draw_ext_info(NDI_RED, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_SCRIPT,
213                       "Please specify a script to start. For help, type "
214                       "'help script'.");
215         return;
216     }
217 
218     /* cparams is a const value, so copy the data into a buffer */
219     strncpy(params, cparams, MAX_BUF - 1);
220     params[MAX_BUF - 1] = '\0';
221 
222     /* Get name and args */
223     name = params;
224     args = name;
225     while (*args && *args != ' ') {
226         ++args;
227     }
228     while (*args && *args == ' ') {
229         *args++ = '\0';
230     }
231     if (*args == 0) {
232         args = NULL;
233     }
234 
235     /* Open two pipes, one for stdin and the other for stdout. */
236     if (pipe(pipe1) != 0) {
237         draw_ext_info(NDI_RED, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_SCRIPT,
238                       "Unable to start script--pipe failed");
239         return;
240     }
241     if (pipe(pipe2) != 0) {
242         close(pipe1[0]);
243         close(pipe1[1]);
244 
245         draw_ext_info(NDI_RED, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_SCRIPT,
246                       "Unable to start script--pipe failed");
247         return;
248     }
249 
250     /* Fork */
251     pid = fork();
252     if (pid == -1) {
253         close(pipe1[0]);
254         close(pipe1[1]);
255         close(pipe2[0]);
256         close(pipe2[1]);
257         draw_ext_info(NDI_RED, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_SCRIPT,
258                       "Unable to start script--fork failed");
259         return;
260     }
261 
262     /* Child--set stdin/stdout to the pipes, then exec */
263     if (pid == 0) {
264         size_t i;
265         int r;
266         char *argv[256];
267 
268         /* Fill in argv[] */
269         argv[0] = name;
270         i = 1;
271         while (args && *args && i < sizeof(argv)/sizeof(*argv)-1) {
272             argv[i++] = args;
273             while (*args && *args != ' ') {
274                 ++args;
275             }
276             while (*args && *args == ' ') {
277                 *args++ = '\0';
278             }
279         }
280         argv[i] = NULL;
281 
282         /* Clean up file descriptor space */
283         r = dup2(pipe1[0], 0);
284         if (r != 0) {
285             fprintf(stderr, "Script Child: Failed to set pipe1 as stdin\n");
286         }
287         r = dup2(pipe2[1], 1);
288         if (r != 1) {
289             fprintf(stderr, "Script Child: Failed to set pipe2 as stdout\n");
290         }
291         for (i = 3; i < 100; ++i) {
292             close(i);
293         }
294 
295         /* Pass extra info to the script */
296         if ( cpl.name ) setenv("CF_PLAYER_NAME", cpl.name, 1);
297         if ( csocket.servername ) setenv("CF_SERVER_NAME", csocket.servername, 1);
298 
299         /* EXEC */
300         r = execvp(argv[0], argv);
301 
302         /* If we get here, then there's been an failure of some sort.
303          * In my case, it's often that I don't know what script name to
304          * give to /script, so exec() can't find the script.
305          *
306          * Forward the error back to the client, using the script pipes.
307          */
308 
309         printf("draw %d Could not start script: %s\n", NDI_RED, strerror(errno));
310         exit(1);
311     }
312 
313     /* Close the child's pipe ends */
314     close(pipe1[0]);
315     close(pipe2[1]);
316 
317     if (fcntl(pipe1[1], F_SETFL, O_NDELAY) == -1) {
318         LOG(LOG_WARNING, "common::script_init", "Error on fcntl.");
319     }
320 
321     /* g_realloc script array to add new entry; fill in the data */
322     scripts = g_realloc(scripts, sizeof(scripts[0])*(num_scripts+1));
323 
324     if (scripts == NULL) {
325         LOG(LOG_ERROR, "script_init",
326                 "Could not allocate memory: %s", strerror(errno));
327         exit(EXIT_FAILURE);
328     }
329 
330     scripts[num_scripts].name = g_strdup(name);
331     scripts[num_scripts].params = args ? g_strdup(args) : NULL;
332     scripts[num_scripts].out_fd = pipe1[1];
333     scripts[num_scripts].in_fd = pipe2[0];
334     scripts[num_scripts].monitor = 0;
335     scripts[num_scripts].num_watch = 0;
336     scripts[num_scripts].watch = NULL;
337     scripts[num_scripts].cmd_count = 0;
338     scripts[num_scripts].pid = pid;
339     scripts[num_scripts].sync_watch = -1;
340     ++num_scripts;
341 #else /* WIN32 */
342 
343     char *name, *args;
344     char params[ MAX_BUF ];
345     SECURITY_ATTRIBUTES saAttr;
346     PROCESS_INFORMATION piProcInfo;
347     STARTUPINFO siStartupInfo;
348     HANDLE hChildStdinRd, hChildStdinWr, hChildStdinWrDup, hChildStdoutRd;
349     HANDLE hChildStdoutWr, hChildStdoutRdDup, hSaveStdin, hSaveStdout;
350 
351     if (!cparams) {
352         draw_ext_info(NDI_RED, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_SCRIPT,
353                       "Please specifiy a script to launch!");
354         return;
355     }
356 
357     strncpy(params, cparams, MAX_BUF-1);
358     params[MAX_BUF-1] = '\0';
359 
360     /* Get name and args */
361     name = params;
362     args = name;
363     while (*args && *args != ' ') {
364         ++args;
365     }
366     while (*args && *args == ' ') {
367         *args++ = '\0';
368     }
369     if (*args == 0) {
370         args = NULL;
371     }
372 
373     saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
374     saAttr.bInheritHandle = TRUE;
375     saAttr.lpSecurityDescriptor = NULL;
376 
377     hSaveStdout = GetStdHandle(STD_OUTPUT_HANDLE);
378     if (!CreatePipe(&hChildStdoutRd, &hChildStdoutWr, &saAttr, 0)) {
379         draw_ext_info(NDI_RED, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_SCRIPT,
380                       "Script support: stdout CreatePipe() failed");
381         return;
382     }
383 
384     if (!SetStdHandle(STD_OUTPUT_HANDLE, hChildStdoutWr)) {
385         draw_ext_info(NDI_RED, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_SCRIPT,
386                       "Script support: failed to redirect stdout using SetStdHandle()");
387         return;
388     }
389 
390     if (!DuplicateHandle(GetCurrentProcess(), hChildStdoutRd, GetCurrentProcess(), &hChildStdoutRdDup, 0, FALSE, DUPLICATE_SAME_ACCESS)) {
391         draw_ext_info(NDI_RED, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_SCRIPT,
392                       "Script support: failed to duplicate stdout using DuplicateHandle()");
393         return;
394     }
395 
396     CloseHandle(hChildStdoutRd);
397 
398     hSaveStdin = GetStdHandle(STD_INPUT_HANDLE);
399     if (!CreatePipe(&hChildStdinRd, &hChildStdinWr, &saAttr, 0)) {
400         draw_ext_info(NDI_RED, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_SCRIPT,
401                       "Script support: stdin CreatePipe() failed");
402         return;
403     }
404 
405     if (!SetStdHandle(STD_INPUT_HANDLE, hChildStdinRd)) {
406         draw_ext_info(NDI_RED, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_SCRIPT,
407                       "Script support: failed to redirect stdin using SetStdHandle()");
408         return;
409     }
410 
411     if (!DuplicateHandle(GetCurrentProcess(), hChildStdinWr, GetCurrentProcess(), &hChildStdinWrDup, 0, FALSE, DUPLICATE_SAME_ACCESS)) {
412         draw_ext_info(NDI_RED, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_SCRIPT,
413                       "Script support: failed to duplicate stdin using DuplicateHandle()");
414         return;
415     }
416 
417     CloseHandle(hChildStdinWr);
418 
419     ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION));
420     ZeroMemory(&siStartupInfo, sizeof(STARTUPINFO));
421     siStartupInfo.cb = sizeof(STARTUPINFO);
422 
423     if (args) {
424         args[-1] = ' ';
425     }
426 
427     if (!CreateProcess(NULL, name, NULL, NULL, TRUE, CREATE_NEW_PROCESS_GROUP, NULL, NULL, &siStartupInfo, &piProcInfo)) {
428         draw_ext_info(NDI_RED, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_SCRIPT,
429                       "Script support: CreateProcess() failed");
430         return;
431     }
432 
433     CloseHandle(piProcInfo.hThread);
434 
435     if (args) {
436         args[-1] = '\0';
437     }
438 
439     if (!SetStdHandle(STD_INPUT_HANDLE, hSaveStdin)) {
440         draw_ext_info(NDI_RED, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_SCRIPT,
441                       "Script support: restoring original stdin failed");
442         return;
443     }
444 
445     if (!SetStdHandle(STD_OUTPUT_HANDLE, hSaveStdout)) {
446         draw_ext_info(NDI_RED, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_SCRIPT,
447                       "Script support: restoring original stdout failed");
448         return;
449     }
450 
451     /* g_realloc script array to add new entry; fill in the data */
452     scripts = g_realloc(scripts, sizeof(scripts[0])*(num_scripts+1));
453 
454     if (scripts == NULL) {
455         LOG(LOG_ERROR, "script_init",
456                 "Could not allocate memory: %s", strerror(errno));
457         exit(EXIT_FAILURE);
458     }
459 
460     scripts[num_scripts].name = g_strdup(name);
461     scripts[num_scripts].params = args ? g_strdup(args) : NULL;
462     scripts[num_scripts].out_fd = hChildStdinWrDup;
463     scripts[num_scripts].in_fd = hChildStdoutRdDup;
464     scripts[num_scripts].monitor = 0;
465     scripts[num_scripts].num_watch = 0;
466     scripts[num_scripts].watch = NULL;
467     scripts[num_scripts].cmd_count = 0;
468     scripts[num_scripts].pid = piProcInfo.dwProcessId;
469     scripts[num_scripts].process = piProcInfo.hProcess;
470     scripts[num_scripts].sync_watch = -1;
471     ++num_scripts;
472 #endif /* WIN32 */
473 }
474 
script_sync(int commdiff)475 void script_sync(int commdiff)
476 {
477     int i;
478 
479     if (commdiff < 0) {
480         commdiff +=256;
481     }
482     for (i = 0; i < num_scripts; ++i) {
483         if (commdiff <= scripts[i].sync_watch && scripts[i].sync_watch >= 0) {
484             char buf[1024];
485 
486             snprintf(buf, sizeof(buf), "sync %d\n", commdiff);
487             write(scripts[i].out_fd, buf, strlen(buf));
488             scripts[i].sync_watch = -1;
489         }
490     }
491 }
492 
script_list(void)493 void script_list(void)
494 {
495     if (num_scripts == 0) {
496         draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_SCRIPT,
497                       "No scripts are currently running");
498     } else {
499         int i;
500         char buf[1024];
501 
502         snprintf(buf, sizeof(buf), "%d scripts currently running:", num_scripts);
503         draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_SCRIPT, buf);
504         for (i = 0; i < num_scripts; ++i) {
505             if (scripts[i].params) {
506                 snprintf(buf, sizeof(buf), "%d %s  %s", i+1, scripts[i].name, scripts[i].params);
507             } else {
508                 snprintf(buf, sizeof(buf), "%d %s", i+1, scripts[i].name);
509             }
510             draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_SCRIPT, buf);
511         }
512     }
513 }
514 
script_kill(const char * params)515 void script_kill(const char *params)
516 {
517     int i;
518 
519     /* Verify that the number is a valid array entry */
520     i = script_by_name(params);
521     if (i < 0 || i >= num_scripts) {
522         draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_SCRIPT,
523                       "No such running script");
524         return;
525     }
526 #ifndef WIN32
527     kill(scripts[i].pid, SIGHUP);
528 #else
529     GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, scripts[i].pid);
530 #endif /* WIN32 */
531     draw_ext_info(NDI_RED, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_SCRIPT,
532                   "Killed script.");
533     script_dead(i);
534 }
535 
536 #ifdef WIN32
script_killall(void)537 void script_killall(void)
538 {
539     while (num_scripts > 0) {
540         GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, scripts[0].pid);
541         script_dead(0);
542     }
543 }
544 #endif /* WIN32 */
545 
script_fdset(int * maxfd,fd_set * set)546 void script_fdset(int *maxfd, fd_set *set)
547 {
548     *maxfd = 0;
549 #ifndef WIN32
550     int i;
551 
552     for (i = 0; i < num_scripts; ++i) {
553         FD_SET(scripts[i].in_fd, set);
554         if (scripts[i].in_fd >= *maxfd) {
555             *maxfd = scripts[i].in_fd+1;
556         }
557     }
558 #endif /* WIN32 */
559 }
560 
script_process(fd_set * set)561 void script_process(fd_set *set)
562 {
563     int i;
564     int r;
565 #ifdef WIN32
566     DWORD nAvailBytes = 0;
567     char cTmp;
568     BOOL bRC;
569     DWORD dwStatus;
570     BOOL bStatus;
571 #endif
572 
573     /* Determine which script's fd is set */
574     for (i = 0; i < num_scripts; ++i) {
575 #ifndef WIN32
576         if (FD_ISSET(scripts[i].in_fd, set))
577 #else
578         bStatus = GetExitCodeProcess(scripts[i].process, &dwStatus);
579         bRC = PeekNamedPipe(scripts[i].in_fd, &cTmp, 1, NULL, &nAvailBytes, NULL);
580         if (nAvailBytes)
581 #endif /* WIN32 */
582         {
583             /* Read in script[i].cmd */
584             r = read(scripts[i].in_fd, scripts[i].cmd+scripts[i].cmd_count, sizeof(scripts[i].cmd)-scripts[i].cmd_count-1);
585             if (r > 0) {
586                 scripts[i].cmd_count += r;
587             }
588 #ifndef WIN32
589             else if (r == 0 || errno == EBADF)
590 #else
591             else if (r == 0 || GetLastError() == ERROR_BROKEN_PIPE)
592 #endif
593             {
594                 /* Script has exited; delete it */
595                 script_dead(i);
596                 return;
597             }
598             /* If a newline or full buffer has been reached, process it */
599             scripts[i].cmd[scripts[i].cmd_count] = 0; /* terminate string */
600             while (scripts[i].cmd_count == sizeof(scripts[i].cmd)-1
601 #ifndef WIN32
602                     || strchr(scripts[i].cmd, '\n'))
603 #else
604                     || strchr(scripts[i].cmd, '\r\n'))
605 #endif /* WIN32 */
606             {
607                 script_process_cmd(i);
608                 scripts[i].cmd[scripts[i].cmd_count] = 0; /* terminate string */
609             }
610             return; /* Only process one script at a time */
611         }
612 #ifdef WIN32
613         else if (!bRC || (bStatus && (dwStatus != STILL_ACTIVE))) { /* Error: assume dead */
614             script_dead(i);
615         }
616 #endif /* WIN32 */
617     }
618 }
619 
script_watch(const char * cmd,const guint8 * data_initial,const int data_len,const enum CmdFormat format)620 void script_watch(const char *cmd, const guint8 *data_initial, const int data_len, const enum CmdFormat format)
621 {
622     int i;
623     int w;
624     int l, len;
625     const guint8 *data;
626 
627     /* For each script... */
628     for (i = 0; i < num_scripts; ++i) {
629         /* For each watch... */
630         for (w = 0; w < scripts[i].num_watch; ++w) {
631             len = data_len;
632             /* Does this command match our watch? */
633             l = strlen(scripts[i].watch[w]);
634             if (!l || strncmp(cmd, scripts[i].watch[w], l) == 0) {
635                 char buf[MAXSOCKBUF*3]; // Source is binary, dest is ASCII; 2-byte short can be 5 chars plus space
636 
637                 data = data_initial;
638                 if (!len) {
639                     snprintf(buf, sizeof(buf), "watch %s\n", cmd);
640                 } else
641                     switch (format) {
642                     case ASCII:
643                         snprintf(buf, sizeof(buf), "watch %s %s\n", cmd, data);
644                         break;
645 
646                     case SHORT_INT:
647                         snprintf(buf, sizeof(buf), "watch %s %d %d\n", cmd, GetShort_String(data), GetInt_String(data+2));
648                         break;
649 
650                     case SHORT_ARRAY: {
651                         int be;
652                         int p;
653 
654                         be = snprintf(buf, sizeof(buf), "watch %s", cmd);
655                         for (p = 0; p*2 < len; ++p) {
656                             be += snprintf(buf+be, sizeof(buf)-be, " %d", GetShort_String(data+p*2));
657                         }
658                         be += snprintf(buf+be, sizeof(buf)-be, "\n");
659                     }
660                     break;
661 
662                     case INT_ARRAY: {
663                         int be;
664                         int p;
665 
666                         be = snprintf(buf, sizeof(buf), "watch %s", cmd);
667                         for (p = 0; p*4 < len; ++p) {
668                             be += snprintf(buf+be, sizeof(buf)-be, " %d", GetInt_String(data+p*4));
669                         }
670                         be += snprintf(buf+be, sizeof(buf)-be, "\n");
671                     }
672                     break;
673 
674                     case STATS: {
675                         /*
676                          * We cheat here and log each stat as a separate command, even
677                          * if the server sent a bunch of updates as a single message;
678                          * most scripts will be easier to write if they only parse a fixed
679                          * format.
680                          */
681                         int be = 0;
682                         while (len) {
683                             int c; /* which stat */
684 
685                             be += snprintf(buf+be, sizeof(buf)-be, "watch %s", cmd);
686                             c = *data;
687                             ++data;
688                             --len;
689                             if (c >= CS_STAT_RESIST_START && c <= CS_STAT_RESIST_END) {
690                                 be += snprintf(buf+be, sizeof(buf)-be, " resists %d %d\n", c, GetShort_String(data));
691                                 data += 2;
692                                 len -= 2;
693                             } else if (c >= CS_STAT_SKILLINFO && c < (CS_STAT_SKILLINFO+CS_NUM_SKILLS)) {
694                                 be += snprintf(buf+be, sizeof(buf)-be, " skill %d %d %" G_GINT64_FORMAT "\n", c, *data, GetInt64_String(data+1));
695                                 data += 9;
696                                 len -= 9;
697                             } else
698                                 switch (c) {
699                                 case CS_STAT_HP:
700                                     be += snprintf(buf+be, sizeof(buf)-be, " hp %d\n", GetShort_String(data));
701                                     data += 2;
702                                     len -= 2;
703                                     break;
704 
705                                 case CS_STAT_MAXHP:
706                                     be += snprintf(buf+be, sizeof(buf)-be, " maxhp %d\n", GetShort_String(data));
707                                     data += 2;
708                                     len -= 2;
709                                     break;
710 
711                                 case CS_STAT_SP:
712                                     be += snprintf(buf+be, sizeof(buf)-be, " sp %d\n", GetShort_String(data));
713                                     data += 2;
714                                     len -= 2;
715                                     break;
716 
717                                 case CS_STAT_MAXSP:
718                                     be += snprintf(buf+be, sizeof(buf)-be, " maxsp %d\n", GetShort_String(data));
719                                     data += 2;
720                                     len -= 2;
721                                     break;
722 
723                                 case CS_STAT_GRACE:
724                                     be += snprintf(buf+be, sizeof(buf)-be, " grace %d\n", GetShort_String(data));
725                                     data += 2;
726                                     len -= 2;
727                                     break;
728 
729                                 case CS_STAT_MAXGRACE:
730                                     be += snprintf(buf+be, sizeof(buf)-be, " maxgrace %d\n", GetShort_String(data));
731                                     data += 2;
732                                     len -= 2;
733                                     break;
734 
735                                 case CS_STAT_STR:
736                                     be += snprintf(buf+be, sizeof(buf)-be, " str %d\n", GetShort_String(data));
737                                     data += 2;
738                                     len -= 2;
739                                     break;
740 
741                                 case CS_STAT_INT:
742                                     be += snprintf(buf+be, sizeof(buf)-be, " int %d\n", GetShort_String(data));
743                                     data += 2;
744                                     len -= 2;
745                                     break;
746 
747                                 case CS_STAT_POW:
748                                     be += snprintf(buf+be, sizeof(buf)-be, " pow %d\n", GetShort_String(data));
749                                     data += 2;
750                                     len -= 2;
751                                     break;
752 
753                                 case CS_STAT_WIS:
754                                     be += snprintf(buf+be, sizeof(buf)-be, " wis %d\n", GetShort_String(data));
755                                     data += 2;
756                                     len -= 2;
757                                     break;
758 
759                                 case CS_STAT_DEX:
760                                     be += snprintf(buf+be, sizeof(buf)-be, " dex %d\n", GetShort_String(data));
761                                     data += 2;
762                                     len -= 2;
763                                     break;
764 
765                                 case CS_STAT_CON:
766                                     be += snprintf(buf+be, sizeof(buf)-be, " con %d\n", GetShort_String(data));
767                                     data += 2;
768                                     len -= 2;
769                                     break;
770 
771                                 case CS_STAT_CHA:
772                                     be += snprintf(buf+be, sizeof(buf)-be, " cha %d\n", GetShort_String(data));
773                                     data += 2;
774                                     len -= 2;
775                                     break;
776 
777                                 case CS_STAT_EXP:
778                                     be += snprintf(buf+be, sizeof(buf)-be, " exp %d\n", GetInt_String(data));
779                                     data += 4;
780                                     len -= 4;
781                                     break;
782 
783                                 case CS_STAT_EXP64:
784                                     be += snprintf(buf+be, sizeof(buf)-be, " exp %" G_GINT64_FORMAT "\n", GetInt64_String(data));
785                                     data += 8;
786                                     len -= 8;
787                                     break;
788 
789                                 case CS_STAT_LEVEL:
790                                     be += snprintf(buf+be, sizeof(buf)-be, " level %d\n", GetShort_String(data));
791                                     data += 2;
792                                     len -= 2;
793                                     break;
794 
795                                 case CS_STAT_WC:
796                                     be += snprintf(buf+be, sizeof(buf)-be, " wc %d\n", GetShort_String(data));
797                                     data += 2;
798                                     len -= 2;
799                                     break;
800 
801                                 case CS_STAT_AC:
802                                     be += snprintf(buf+be, sizeof(buf)-be, " ac %d\n", GetShort_String(data));
803                                     data += 2;
804                                     len -= 2;
805                                     break;
806 
807                                 case CS_STAT_DAM:
808                                     be += snprintf(buf+be, sizeof(buf)-be, " dam %d\n", GetShort_String(data));
809                                     data += 2;
810                                     len -= 2;
811                                     break;
812 
813                                 case CS_STAT_ARMOUR:
814                                     be += snprintf(buf+be, sizeof(buf)-be, " armour %d\n", GetShort_String(data));
815                                     data += 2;
816                                     len -= 2;
817                                     break;
818 
819                                 case CS_STAT_SPEED:
820                                     be += snprintf(buf+be, sizeof(buf)-be, " speed %d\n", GetInt_String(data));
821                                     data += 4;
822                                     len -= 4;
823                                     break;
824 
825                                 case CS_STAT_FOOD:
826                                     be += snprintf(buf+be, sizeof(buf)-be, " food %d\n", GetShort_String(data));
827                                     data += 2;
828                                     len -= 2;
829                                     break;
830 
831                                 case CS_STAT_WEAP_SP:
832                                     be += snprintf(buf+be, sizeof(buf)-be, " weap_sp %d\n", GetInt_String(data));
833                                     data += 4;
834                                     len -= 4;
835                                     break;
836 
837                                 case CS_STAT_FLAGS:
838                                     be += snprintf(buf+be, sizeof(buf)-be, " flags %d\n", GetShort_String(data));
839                                     data += 2;
840                                     len -= 2;
841                                     break;
842 
843                                 case CS_STAT_WEIGHT_LIM:
844                                     be += snprintf(buf+be, sizeof(buf)-be, " weight_lim %d\n", GetInt_String(data));
845                                     data += 4;
846                                     len -= 4;
847                                     break;
848 
849                                 case CS_STAT_RANGE: {
850                                     int rlen = *data;
851                                     ++data;
852                                     --len;
853                                     be += snprintf(buf+be, sizeof(buf)-be, " range %*.*s\n", rlen, rlen, data);
854                                     data += rlen;
855                                     len -= rlen;
856                                     break;
857                                 }
858 
859                                 case CS_STAT_TITLE: {
860                                     int rlen = *data;
861                                     ++data;
862                                     --len;
863                                     be += snprintf(buf+be, sizeof(buf)-be, " title %*.*s\n", rlen, rlen, data);
864                                     data += rlen;
865                                     len -= rlen;
866                                     break;
867                                 }
868 
869                                 default:
870                                     be += snprintf(buf+be, sizeof(buf)-be, " unknown %d %d bytes left\n", c, len);
871                                     len = 0;
872                                 }
873                         }
874                     }
875                     break;
876 
877                     case MIXED:
878                         /* magicmap */
879                         /* mapextended */
880                         /* item1 item2 */
881                         /* upditem */
882                         /* image image2 */
883                         /* face face1 face2 */
884                         /* sound */
885                         /* player */
886                         /*
887                          * If we find that scripts need data from any of the above, we can
888                          * write special-case code as with stats.  In the meantime, fall
889                          * through and just give a hex dump.  Script writers should not
890                          * depend on that data format.
891                          */
892                     case NODATA:
893                     default: {
894                         int be;
895                         int p;
896 
897                         /*we may receive an null data, in which case len has no meaning*/
898                         if (!data) {
899                             len = 0;
900                         }
901                         be = snprintf(buf, sizeof(buf), "watch %s %d bytes unparsed:", cmd, len);
902                         for (p = 0; p < len; ++p) {
903                             be += snprintf(buf+be, sizeof(buf)-be, " %02x", data[p]);
904                         }
905                         be += snprintf(buf+be, sizeof(buf)-be, "\n");
906                     }
907                     break;
908                     }
909                 write(scripts[i].out_fd, buf, strlen(buf));
910             }
911         }
912     }
913 }
914 
script_monitor(const char * command,int repeat,int must_send)915 void script_monitor(const char *command, int repeat, int must_send)
916 {
917     int i;
918 
919     /* For each script... */
920     for (i = 0; i < num_scripts; ++i) {
921         /* Do we send the command? */
922         if (scripts[i].monitor) {
923             char buf[1024];
924 
925             snprintf(buf, sizeof(buf), "monitor %d %d %s\n", repeat, must_send, command);
926             write(scripts[i].out_fd, buf, strlen(buf));
927         }
928     }
929 }
930 
script_monitor_str(const char * command)931 void script_monitor_str(const char *command)
932 {
933     int i;
934 
935     /* For each script... */
936     for (i = 0; i < num_scripts; ++i) {
937         /* Do we send the command? */
938         if (scripts[i].monitor) {
939             char buf[1024];
940 
941             snprintf(buf, sizeof(buf), "monitor %s\n", command);
942             write(scripts[i].out_fd, buf, strlen(buf));
943         }
944     }
945 }
946 
script_tell(const char * params)947 void script_tell(const char *params)
948 {
949     int i;
950     char *p;
951 
952     if (params == NULL) {
953         draw_ext_info(NDI_RED, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_SCRIPT,
954                       "Which script do you want to talk to?");
955         return;
956     }
957 
958     /* Local copy for modifications */
959     char params_cpy[MAX_BUF];
960     snprintf(params_cpy, MAX_BUF-1, "%s", params);
961     p = strchr(params_cpy, ' ');
962     if (p == NULL) {
963         draw_ext_info(NDI_RED, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_SCRIPT,
964                       "What do you want to tell the script?");
965         return;
966     }
967     while (*p == ' ') {
968         *p++ = '\0';
969     }
970 
971     /* Find the script */
972     i = script_by_name(params_cpy);
973     if (i < 0) {
974         draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_SCRIPT,
975                       "No such running script");
976         return;
977     }
978 
979     /* Send the message */
980     write(scripts[i].out_fd, "scripttell ", 11);
981     write(scripts[i].out_fd, p, strlen(p));
982     write(scripts[i].out_fd, "\n", 1);
983 }
984 
script_by_name(const char * name)985 static int script_by_name(const char *name)
986 {
987     int i;
988     int l;
989 
990     if (name == NULL) {
991         return(num_scripts == 1 ? 0 : -1);
992     }
993 
994     /* Parse script number */
995     if (isdigit(*name)) {
996         i = atoi(name);
997         --i;
998         if (i >= 0 && i < num_scripts) {
999             return(i);
1000         }
1001     }
1002 
1003     /* Parse script name */
1004     l = 0;
1005     while (name[l] && name[l] != ' ') {
1006         ++l;
1007     }
1008     for (i = 0; i < num_scripts; ++i) {
1009         if (strncmp(name, scripts[i].name, l) == 0) {
1010             return(i);
1011         }
1012     }
1013     return(-1);
1014 }
1015 
script_dead(int i)1016 static void script_dead(int i)
1017 {
1018     int w;
1019 
1020     /* Release resources */
1021 #ifndef WIN32
1022     close(scripts[i].in_fd);
1023     close(scripts[i].out_fd);
1024 #else
1025     CloseHandle(scripts[i].in_fd);
1026     CloseHandle(scripts[i].out_fd);
1027     CloseHandle(scripts[i].process);
1028 #endif
1029     free(scripts[i].name);
1030     free(scripts[i].params);
1031     for (w = 0; w < scripts[i].num_watch; ++w) {
1032         free(scripts[i].watch[w]);
1033     }
1034     free(scripts[i].watch);
1035 
1036 #ifndef WIN32
1037     waitpid(-1, NULL, WNOHANG);
1038 #endif
1039 
1040     /* Move scripts with higher index numbers down one slot */
1041     if (i < (num_scripts-1)) {
1042         memmove(&scripts[i], &scripts[i+1], sizeof(scripts[i])*(num_scripts-i-1));
1043     }
1044 
1045     /* Update our count */
1046     --num_scripts;
1047 }
1048 
send_map(int i,int x,int y)1049 static void send_map(int i, int x, int y)
1050 {
1051     char buf[1024];
1052 
1053     if (!mapdata_contains(x, y)) {
1054         snprintf(buf, sizeof(buf), "request map %d %d unknown\n", x, y);
1055     }
1056     else {
1057     /*** FIXME *** send more relevant data ***/
1058     snprintf(buf, sizeof(buf), "request map %d %d  %d %c %c %c %c"
1059              " smooth %d %d %d heads %d %d %d tails %d %d %d\n",
1060              x, y, mapdata_cell(x, y)->darkness,
1061              mapdata_cell(x, y)->need_update ? 'y' : 'n',
1062              mapdata_cell(x, y)->have_darkness ? 'y' : 'n',
1063              mapdata_cell(x, y)->need_resmooth ? 'y' : 'n',
1064              mapdata_cell(x, y)->cleared ? 'y' : 'n',
1065              mapdata_cell(x, y)->smooth[0], mapdata_cell(x, y)->smooth[1], mapdata_cell(x, y)->smooth[2],
1066              mapdata_cell(x, y)->heads[0].face, mapdata_cell(x, y)->heads[1].face, mapdata_cell(x, y)->heads[2].face,
1067              mapdata_cell(x, y)->tails[0].face, mapdata_cell(x, y)->tails[1].face, mapdata_cell(x, y)->tails[2].face
1068             );
1069     }
1070     write(scripts[i].out_fd, buf, strlen(buf));
1071 }
1072 
1073 /**
1074  * Process a single script command from the given script. This function
1075  * removes the processed command from the buffer when finished.
1076  * @param i Index of the script to process a command from
1077  */
script_process_cmd(int i)1078 static void script_process_cmd(int i) {
1079     char cmd[1024];
1080     char *c;
1081 
1082     // Find the length of the command up to the trailing newline.
1083     int l = strcspn(scripts[i].cmd, "\n") + 1;
1084 
1085     // Copy a single command up until the newline into a buffer.
1086     g_strlcpy(cmd, scripts[i].cmd, l);
1087 
1088     // If a carriage return is present, trim it out as well.
1089     char *cr = strchr(cmd, '\r');
1090     if (cr != NULL) {
1091         *cr = '\0';
1092     }
1093 
1094     // Remove a single command from the script command buffer.
1095     if (l < scripts[i].cmd_count) {
1096         memmove(scripts[i].cmd, scripts[i].cmd + l, scripts[i].cmd_count - l);
1097         scripts[i].cmd_count -= l;
1098     } else {
1099         scripts[i].cmd_count = 0;
1100     }
1101 
1102     /*
1103      * Now the data in scripts[i] is ready for the next read.
1104      * We have a complete command in cmd[].
1105      * Process it.
1106      */
1107     /*
1108      * Script commands
1109      *
1110      * watch <command type>
1111      * unwatch <command type>
1112      * request <data type>
1113      * issue <repeat> <must_send> <command>
1114      * localcmd <command> [<params>]
1115      * draw <color> <text>
1116      * monitor
1117      * unmonitor
1118      */
1119     if (strncmp(cmd, "sync", 4) == 0) {
1120         c = cmd+4;
1121         while (*c && *c != ' ') {
1122             ++c;
1123         }
1124         while (*c == ' ') {
1125             ++c;
1126         }
1127         scripts[i].sync_watch = -1;
1128         if (isdigit(*c)) {
1129             scripts[i].sync_watch = atoi(c);
1130         }
1131         script_sync(csocket.command_sent - csocket.command_received); /* in case we are already there */
1132     } else if (strncmp(cmd, "watch", 5) == 0) {
1133         c = cmd+5;
1134         while (*c && *c != ' ') {
1135             ++c;
1136         }
1137         while (*c == ' ') {
1138             ++c;
1139         }
1140         c = g_strdup(c);
1141         scripts[i].watch = g_realloc(scripts[i].watch, (scripts[i].num_watch+1)*sizeof(scripts[i].watch[1]));
1142         scripts[i].watch[scripts[i].num_watch] = c;
1143         ++scripts[i].num_watch;
1144     } else if (strncmp(cmd, "unwatch", 7) == 0) {
1145         int w;
1146 
1147         c = cmd+7;
1148         while (*c && *c != ' ') {
1149             ++c;
1150         }
1151         while (*c == ' ') {
1152             ++c;
1153         }
1154         for (w = 0; w < scripts[i].num_watch; ++w) {
1155             if (strcmp(c, scripts[i].watch[w]) == 0) {
1156                 free(scripts[i].watch[w]);
1157                 while (w+1 < scripts[i].num_watch) {
1158                     scripts[i].watch[w] = scripts[i].watch[w+1];
1159                     ++w;
1160                 }
1161                 --scripts[i].num_watch;
1162                 break;
1163             }
1164         }
1165     } else if (strncmp(cmd, "request", 7) == 0) {
1166         c = cmd+7;
1167         while (*c && *c != ' ') {
1168             ++c;
1169         }
1170         while (*c == ' ') {
1171             ++c;
1172         }
1173         if (!*c) {
1174             return;    /* bad request */
1175         }
1176         /*
1177          * Request information from the client's view of the world
1178          * (mostly defined in client.h)
1179          *
1180          * Valid requests:
1181          *
1182          *   player       Return the player's tag and title
1183          *   range        Return the type and name of the currently selected range attack
1184          *   stat <type>  Return the specified stats
1185          *   stat stats   Return Str,Con,Dex,Int,Wis,Pow,Cha
1186          *   stat cmbt    Return wc,ac,dam,speed,weapon_sp
1187          *   stat hp      Return hp,maxhp,sp,maxsp,grace,maxgrace,food
1188          *   stat xp      Return level,xp,skill-1 level,skill-1 xp,...
1189          *   stat resists Return resistances
1190          *   stat paths   Return spell paths: attuned, repelled, denied.
1191          *   weight       Return maxweight, weight
1192          *   flags        Return flags (fire, run)
1193          *   items inv    Return a list of items in the inventory, one per line
1194          *   items actv   Return a list of inventory items that are active, one per line
1195          *   items on     Return a list of items under the player, one per line
1196          *   items cont   Return a list of items in the open container, one per line
1197          *   map pos      Return the players x,y within the current map
1198          *   map near     Return the 3x3 grid of the map centered on the player
1199          *   map all      Return all the known map information
1200          *   map <x> <y>  Return the information about square x,y in the current map
1201          *   skills       Return a list of all skill names, one per line (see also stat xp)
1202          *   spells       Return a list of known spells, one per line
1203          */
1204         if (strncmp(c, "player", 6) == 0) {
1205             char buf[1024];
1206 
1207             snprintf(buf, sizeof(buf), "request player %d %s\n", cpl.ob->tag, cpl.title);
1208             write(scripts[i].out_fd, buf, strlen(buf));
1209         } else if (strncmp(c, "range", 5) == 0) {
1210             char buf[1024];
1211 
1212             snprintf(buf, sizeof(buf), "request range %s\n", cpl.range);
1213             write(scripts[i].out_fd, buf, strlen(buf));
1214         } else if (strncmp(c, "weight", 5) == 0) {
1215             char buf[1024];
1216 
1217             snprintf(buf, sizeof(buf), "request weight %d %d\n", cpl.stats.weight_limit, (int)(cpl.ob->weight*1000));
1218             write(scripts[i].out_fd, buf, strlen(buf));
1219         } else if (strncmp(c, "stat ", 5) == 0) {
1220             c += 4;
1221             while (*c && *c != ' ') {
1222                 ++c;
1223             }
1224             while (*c == ' ') {
1225                 ++c;
1226             }
1227             if (!*c) {
1228                 return;    /* bad request */
1229             }
1230             /*
1231              *   stat stats   Return Str,Con,Dex,Int,Wis,Pow,Cha
1232              *   stat cmbt    Return wc,ac,dam,speed,weapon_sp
1233              *   stat hp      Return hp,maxhp,sp,maxsp,grace,maxgrace,food
1234              *   stat xp      Return level,xp,skill-1 level,skill-1 xp,...
1235              *   stat resists Return resistances
1236              */
1237             if (strncmp(c, "stats", 5) == 0) {
1238                 char buf[1024];
1239 
1240                 snprintf(buf, sizeof(buf), "request stat stats %d %d %d %d %d %d %d\n", cpl.stats.Str, cpl.stats.Con, cpl.stats.Dex, cpl.stats.Int, cpl.stats.Wis, cpl.stats.Pow, cpl.stats.Cha);
1241                 write(scripts[i].out_fd, buf, strlen(buf));
1242             } else if (strncmp(c, "cmbt", 4) == 0) {
1243                 char buf[1024];
1244 
1245                 snprintf(buf, sizeof(buf), "request stat cmbt %d %d %d %d %d\n", cpl.stats.wc, cpl.stats.ac, cpl.stats.dam, cpl.stats.speed, cpl.stats.weapon_sp);
1246                 write(scripts[i].out_fd, buf, strlen(buf));
1247             } else if (strncmp(c, "hp", 2) == 0) {
1248                 char buf[1024];
1249 
1250                 snprintf(buf, sizeof(buf), "request stat hp %d %d %d %d %d %d %d\n", cpl.stats.hp, cpl.stats.maxhp, cpl.stats.sp, cpl.stats.maxsp, cpl.stats.grace, cpl.stats.maxgrace, cpl.stats.food);
1251                 write(scripts[i].out_fd, buf, strlen(buf));
1252             } else if (strncmp(c, "xp", 2) == 0) {
1253                 char buf[1024];
1254                 int s;
1255 
1256                 snprintf(buf, sizeof(buf), "request stat xp %d %" G_GINT64_FORMAT, cpl.stats.level, cpl.stats.exp);
1257                 write(scripts[i].out_fd, buf, strlen(buf));
1258                 for (s = 0; s < MAX_SKILL; ++s) {
1259                     snprintf(buf, sizeof(buf), " %d %" G_GINT64_FORMAT, cpl.stats.skill_level[s], cpl.stats.skill_exp[s]);
1260                     write(scripts[i].out_fd, buf, strlen(buf));
1261                 }
1262                 write(scripts[i].out_fd, "\n", 1);
1263             } else if (strncmp(c, "resists", 7) == 0) {
1264                 char buf[1024];
1265                 int s;
1266 
1267                 snprintf(buf, sizeof(buf), "request stat resists");
1268                 write(scripts[i].out_fd, buf, strlen(buf));
1269                 for (s = 0; s < 30; ++s) {
1270                     snprintf(buf, sizeof(buf), " %d", cpl.stats.resists[s]);
1271                     write(scripts[i].out_fd, buf, strlen(buf));
1272                 }
1273                 write(scripts[i].out_fd, "\n", 1);
1274             } else if (strncmp(c, "paths", 2) == 0) {
1275                 char buf[1024];
1276 
1277                 snprintf(buf, sizeof(buf), "request stat paths %d %d %d\n", cpl.stats.attuned, cpl.stats.repelled, cpl.stats.denied);
1278                 write(scripts[i].out_fd, buf, strlen(buf));
1279             }
1280         } else if (strncmp(c, "flags", 5) == 0) {
1281             char buf[1024];
1282 
1283             snprintf(buf, sizeof(buf), "request flags %d %d %d %d\n", cpl.stats.flags, cpl.fire_on, cpl.run_on, cpl.no_echo);
1284             write(scripts[i].out_fd, buf, strlen(buf));
1285         } else if (strncmp(c, "items ", 6) == 0) {
1286             c += 5;
1287             while (*c && *c != ' ') {
1288                 ++c;
1289             }
1290             while (*c == ' ') {
1291                 ++c;
1292             }
1293             if (!*c) {
1294                 return;    /* bad request */
1295             }
1296             /*
1297              *   items inv    Return a list of items in the inventory, one per line
1298              *   items actv   Return a list of inventory items that are active, one per line
1299              *   items on     Return a list of items under the player, one per line
1300              *   items cont   Return a list of items in the open container, one per line
1301              */
1302             if (strncmp(c, "inv", 3) == 0) {
1303                 char *buf;
1304                 item *it;
1305 
1306                 for (it = cpl.ob->inv; it; it = it->next) {
1307                     script_send_item(i, "request items inv ", it);
1308                 }
1309                 buf = "request items inv end\n";
1310                 write(scripts[i].out_fd, buf, strlen(buf));
1311             }
1312             if (strncmp(c, "actv", 4) == 0) {
1313                 char *buf;
1314                 item *it;
1315 
1316                 for (it = cpl.ob->inv; it; it = it->next) {
1317                     if (it->applied) {
1318                         script_send_item(i, "request items actv ", it);
1319                     }
1320                 }
1321                 buf = "request items actv end\n";
1322                 write(scripts[i].out_fd, buf, strlen(buf));
1323             }
1324             if (strncmp(c, "on", 2) == 0) {
1325                 char *buf;
1326                 item *it;
1327 
1328                 for (it = cpl.below->inv; it; it = it->next) {
1329                     script_send_item(i, "request items on ", it);
1330                 }
1331                 buf = "request items on end\n";
1332                 write(scripts[i].out_fd, buf, strlen(buf));
1333             }
1334             if (strncmp(c, "cont", 4) == 0) {
1335                 char *buf;
1336                 item *it;
1337 
1338                 if (cpl.container) {
1339                     for (it = cpl.container->inv; it; it = it->next) {
1340                         script_send_item(i, "request items cont ", it);
1341                     }
1342                 }
1343                 buf = "request items cont end\n";
1344                 write(scripts[i].out_fd, buf, strlen(buf));
1345             }
1346         } else if (strncmp(c, "map ", 4) == 0) {
1347             int x, y;
1348 
1349             c += 3;
1350             while (*c && *c != ' ') {
1351                 ++c;
1352             }
1353             while (*c == ' ') {
1354                 ++c;
1355             }
1356             if (!*c) {
1357                 return;    /* bad request */
1358             }
1359             /*
1360              *   map pos      Return the players x,y within the current map
1361              *   map near     Return the 3x3 grid of the map centered on the player
1362              *   map all      Return all the known map information
1363              *   map <x> <y>  Return the information about square x,y in the current map
1364              */
1365             if (strncmp(c, "pos", 3) == 0) {
1366                 char buf[1024];
1367 
1368                 snprintf(buf, sizeof(buf), "request map pos %d %d\n",
1369                          script_pos.x, script_pos.y);
1370                 write(scripts[i].out_fd, buf, strlen(buf));
1371             } else if (strncmp(c, "near", 4) == 0) {
1372                 for (y = 0; y < 3; ++y)
1373                     for (x = 0; x < 3; ++x)
1374                         send_map(i,
1375                                  x+pl_pos.x+use_config[CONFIG_MAPWIDTH]/2-1,
1376                                  y+pl_pos.y+use_config[CONFIG_MAPHEIGHT]/2-1
1377                                 );
1378             } else if (strncmp(c, "all", 3) == 0) {
1379                 char *endmsg = "request map end\n";
1380                 int sizex, sizey;
1381 
1382                 mapdata_size(&sizex, &sizey);
1383 
1384                 for (y = 0; y < sizey; y++) {
1385                     for (x = 0; x < sizex; x++) {
1386                         send_map(i, x, y);
1387                     }
1388                 }
1389 
1390                 write(scripts[i].out_fd, endmsg, strlen(endmsg));
1391             } else {
1392                 while (*c && !isdigit(*c)) {
1393                     ++c;
1394                 }
1395                 if (!*c) {
1396                     return;    /* No x specified */
1397                 }
1398                 x = atoi(c);
1399                 while (*c && *c != ' ') {
1400                     ++c;
1401                 }
1402                 while (*c && !isdigit(*c)) {
1403                     ++c;
1404                 }
1405                 if (!*c) {
1406                     return;    /* No y specified */
1407                 }
1408                 y = atoi(c);
1409                 send_map(i, x, y);
1410             }
1411         } else if (strncmp(c, "skills", 6) == 0) {
1412             char buf[1024];
1413             int s;
1414 
1415             for (s = 0; s < CS_NUM_SKILLS; s++) {
1416                 if (skill_names[s]) {
1417                     snprintf(buf, sizeof(buf), "request skills %d %s\n", CS_STAT_SKILLINFO + s, skill_names[s]);
1418                     write(scripts[i].out_fd, buf, strlen(buf));
1419                 }
1420             }
1421             snprintf(buf, sizeof(buf), "request skills end\n");
1422             write(scripts[i].out_fd, buf, strlen(buf));
1423         } else if (strncmp(c, "spells", 6) == 0) {
1424             char buf[1024];
1425             Spell *spell;
1426 
1427             for (spell = cpl.spelldata; spell; spell = spell->next) {
1428                 snprintf(buf, sizeof(buf), "request spells %d %d %d %d %d %d %d %d %s\n",
1429                         spell->tag, spell->level, spell->sp, spell->grace,
1430                         spell->skill_number, spell->path, spell->time,
1431                         spell->dam, spell->name);
1432                 write(scripts[i].out_fd, buf, strlen(buf));
1433             }
1434             snprintf(buf, sizeof(buf), "request spells end\n");
1435             write(scripts[i].out_fd, buf, strlen(buf));
1436         } else {
1437             char buf[1024];
1438 
1439             snprintf(buf, sizeof(buf), "Script %d %s malfunction; unimplemented request:", i+1, scripts[i].name);
1440             draw_ext_info(NDI_RED, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_SCRIPT, buf);
1441             draw_ext_info(NDI_RED, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_SCRIPT, cmd);
1442         }
1443     } else if (strncmp(cmd, "issue", 5) == 0) {
1444         int repeat;
1445         int must_send;
1446 
1447         c = cmd+5;
1448         while (*c && *c == ' ') {
1449             ++c;
1450         }
1451         if (*c && (isdigit(*c) || *c == '-')) { /* repeat specified; use send_command() */
1452             repeat = atoi(c);
1453             while (*c && *c != ' ') {
1454                 ++c;
1455             }
1456             while (*c && !isdigit(*c) && *c != '-') {
1457                 ++c;
1458             }
1459             if (!*c) {
1460                 return;    /* No must_send specified */
1461             }
1462             must_send = atoi(c);
1463             while (*c && *c != ' ') {
1464                 ++c;
1465             }
1466             if (!*c) {
1467                 return;    /* No command specified */
1468             }
1469             while (*c == ' ') {
1470                 ++c;
1471             }
1472             if (repeat != -1) {
1473                 int r;
1474 
1475                 r = send_command(c, repeat, must_send);
1476                 if (r != 1) {
1477                     char buf[1024];
1478 
1479                     snprintf(buf, sizeof(buf), "Script %d %s malfunction; command not sent", i+1, scripts[i].name);
1480                     draw_ext_info(
1481                         NDI_RED, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_SCRIPT, buf);
1482                     draw_ext_info(
1483                         NDI_RED, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_SCRIPT, cmd);
1484                 }
1485             }
1486         } else {
1487             c = cmd+5;
1488             while (*c && *c != ' ') {
1489                 ++c;
1490             }
1491             while (*c == ' ') {
1492                 ++c;
1493             }
1494 
1495             /*
1496              * Check special cases with tags:
1497              *     mark <tag>
1498              *     lock <new state> <tag>
1499              *     take <tag> [<count>]
1500              *     drop <tag> [<count>]
1501              *     apply <tag>
1502              */
1503             if (strncmp(c, "mark", 4) == 0) {
1504                 int tag;
1505                 SockList sl;
1506                 guint8 buf[MAX_BUF];
1507 
1508                 c += 4;
1509 
1510                 while (*c && !isdigit(*c)) {
1511                     ++c;
1512                 }
1513                 if (!*c) {
1514                     return;    /* No tag specified */
1515                 }
1516                 tag = atoi(c);
1517 
1518                 SockList_Init(&sl, buf);
1519                 SockList_AddString(&sl, "mark ");
1520                 SockList_AddInt(&sl, tag);
1521                 SockList_Send(&sl, csocket.fd);
1522             } else if (strncmp(c, "lock", 4) == 0) {
1523                 int tag, locked;
1524                 SockList sl;
1525                 guint8 buf[MAX_BUF];
1526 
1527                 c += 4;
1528 
1529                 while (*c && !isdigit(*c)) {
1530                     ++c;
1531                 }
1532                 if (!*c) {
1533                     return;    /* No state specified */
1534                 }
1535                 locked = atoi(c);
1536                 while (*c && *c != ' ') {
1537                     ++c;
1538                 }
1539                 while (*c && !isdigit(*c)) {
1540                     ++c;
1541                 }
1542                 if (!*c) {
1543                     return;    /* No tag specified */
1544                 }
1545                 tag = atoi(c);
1546 
1547                 SockList_Init(&sl, buf);
1548                 SockList_AddString(&sl, "lock ");
1549                 SockList_AddChar(&sl, locked);
1550                 SockList_AddInt(&sl, tag);
1551                 SockList_Send(&sl, csocket.fd);
1552             } else if ( (strncmp(c, "take", 4) == 0) || (strncmp(c, "drop", 4) == 0) ) {
1553                 int tag, count, dest;
1554 
1555                 dest = (strncmp(c, "drop", 4) == 0) ? 0 : cpl.ob->tag; /* dest is player tag for take, 0 for drop */
1556                 c += 4;
1557 
1558                 while (*c && !isdigit(*c)) {
1559                     ++c;
1560                 }
1561                 if (!*c) {
1562                     return;    /* No tag */
1563                 }
1564                 tag = atoi(c);
1565                 while (*c && *c != ' ') {
1566                     ++c;
1567                 }
1568                 while (*c && !isdigit(*c)) {
1569                     ++c;
1570                 }
1571                 if (!*c) {
1572                     count=0; /* default: count of zero */
1573                 }
1574                 count = atoi(c);
1575 
1576                 client_send_move(dest,tag,count);
1577             } else if (strncmp(c, "apply", 5) == 0) {
1578                 int tag;
1579 
1580                 c += 5;
1581 
1582                 while (*c && !isdigit(*c)) {
1583                     ++c;
1584                 }
1585                 if (!*c) {
1586                     return;    /* No tag specified */
1587                 }
1588                 tag = atoi(c);
1589 
1590                 client_send_apply(tag);
1591             } else {
1592                 cs_print_string(csocket.fd, "%s", c);
1593             }
1594         }
1595     } else if (strncmp(cmd, "localcmd", 8) == 0) {
1596         char *param;
1597 
1598         c = cmd+8;
1599         while (*c == ' ') {
1600             c++;
1601         }
1602         param = c;
1603         while ((*param != '\0') && (*param != ' ')) {
1604             param++;
1605         }
1606         if (*param == ' ') {
1607             *param = '\0';
1608             param++;
1609         } else {
1610             param = NULL;
1611         }
1612 
1613         if (!handle_local_command(c, param)) {
1614             char buf[1024];
1615 
1616             snprintf(buf, sizeof(buf), "Script %s malfunction; localcmd not understood", scripts[i].name);
1617             draw_ext_info(NDI_RED, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_SCRIPT, buf);
1618             snprintf(buf, sizeof(buf), "Script <<localcmd %s %s>>", c, (param == NULL) ? "" : param);
1619             draw_ext_info(NDI_RED, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_SCRIPT, buf);
1620         }
1621     } else if (strncmp(cmd, "draw", 4) == 0) {
1622         int color;
1623 
1624         c = cmd+4;
1625         while (*c && !isdigit(*c)) {
1626             ++c;
1627         }
1628         if (!*c) {
1629             return;    /* No color specified */
1630         }
1631         color = atoi(c);
1632         while (*c && *c != ' ') {
1633             ++c;
1634         }
1635         if (!*c) {
1636             return;    /* No message specified */
1637         }
1638         while (*c == ' ') {
1639             ++c;
1640         }
1641         draw_ext_info(color, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_SCRIPT, c);
1642     } else if (strncmp(cmd, "monitor", 7) == 0) {
1643         scripts[i].monitor = 1;
1644     } else if (strncmp(cmd, "unmonitor", 9) == 0) {
1645         scripts[i].monitor = 0;
1646     } else {
1647         char buf[1024];
1648 
1649         snprintf(buf, sizeof(buf), "Script %d %s malfunction; invalid command:", i+1, scripts[i].name);
1650         draw_ext_info(NDI_RED, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_SCRIPT, buf);
1651         draw_ext_info(NDI_RED, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_SCRIPT, cmd);
1652     }
1653 }
1654 
1655 /*
1656  * script_send_item()
1657  *
1658  * Send one line to the script with item information.
1659  *
1660  * A header string is passed in.  The format is:
1661  *
1662  * <header>  tag num weight flags type name
1663  *
1664  * flags are a bitmask:
1665  * read, unidentified, magic, cursed, damned, unpaid, locked, applied, open, was_open, inv_updated
1666  * 1024      512        256     128     64      32       16      8       4      2         1
1667  */
1668  // TODO: Add blessed here
script_send_item(int i,const char * head,const item * it)1669 static void script_send_item(int i, const char *head, const item *it)
1670 {
1671     char buf[4096];
1672     int flags;
1673 
1674     flags = it->read;
1675     flags = (flags<<1)|(it->flagsval&F_UNIDENTIFIED?1:0);
1676     flags = (flags<<1)|it->magical;
1677     flags = (flags<<1)|it->cursed;
1678     flags = (flags<<1)|it->damned;
1679     flags = (flags<<1)|it->unpaid;
1680     flags = (flags<<1)|it->locked;
1681     flags = (flags<<1)|it->applied;
1682     flags = (flags<<1)|it->open;
1683     flags = (flags<<1)|it->was_open;
1684     flags = (flags<<1)|it->inv_updated;
1685     snprintf(buf, sizeof(buf), "%s%d %d %d %d %d %s\n", head, it->tag, it->nrof, (int)(it->weight*1000+0.5), flags, it->type, it->d_name);
1686     write(scripts[i].out_fd, buf, strlen(buf));
1687 }
1688