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