1 /**
2  * DinkC script engine
3 
4  * Copyright (C) 1997, 1998, 1999, 2002, 2003  Seth A. Robinson
5  * Copyright (C) 2005, 2006  Dan Walma
6  * Copyright (C) 2005, 2007, 2008, 2009, 2011  Sylvain Beucler
7 
8  * This file is part of GNU FreeDink
9 
10  * GNU FreeDink is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU General Public License as
12  * published by the Free Software Foundation; either version 3 of the
13  * License, or (at your option) any later version.
14 
15  * GNU FreeDink is distributed in the hope that it will be useful, but
16  * WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  * General Public License for more details.
19 
20  * You should have received a copy of the GNU General Public License
21  * along with this program.  If not, see
22  * <http://www.gnu.org/licenses/>.
23  */
24 
25 #ifdef HAVE_CONFIG_H
26 #include <config.h>
27 #endif
28 
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <strings.h> /* compare */
33 #include <xalloc.h>
34 
35 #include "gettext.h"
36 #define _(String) gettext (String)
37 
38 #include "dinkc.h"
39 #include "dinkc_bindings.h"
40 #include "game_engine.h"
41 #include "input.h"
42 #include "paths.h"
43 #include "str_util.h"
44 #include "log.h"
45 
46 int returnint = 0;
47 int bKeepReturnInt = 0;
48 char returnstring[200];
49 /* Used to tell decipher_string about the currently selected savegame
50    in a choice menu; also abuse to tell which key is selected in
51    joystick remapping */
52 unsigned short decipher_savegame = 0;
53 
54 #define MAX_CALLBACKS 100
55 struct call_back
56 {
57   int owner;
58   /*bool*/int active;
59   int type;
60   char name[20];
61   int offset;
62   long min, max;
63   int lifespan;
64   unsigned long timer;
65 };
66 static struct call_back callback[MAX_CALLBACKS];
67 /* TODO: Used 1->100 in the game, should it be MAX_CALLBACKS+1 ? */
68 
69 /* DinkC script buffer */
70 static char *rbuf[MAX_SCRIPTS]; //pointers to buffers we may need
71 
72 /* Number of reserved ASCII indexes in .d BPE compression format */
73 #define NB_PAIRS_MAX 128
74 
75 
76 struct refinfo *rinfo[MAX_SCRIPTS];
77 
78 
79 /**
80  * Decompress a .d DinkC script; also clean newlines. Check
81  * contrib/d2c.c for more explanation about the decompression process.
82  */
decompress(FILE * in,int script)83 static void decompress(FILE *in, int script)
84 {
85   int step = 512;
86   int nb_read = 0;
87   rbuf[script] = xmalloc(step);
88   rbuf[script][0] = '\0';
89 
90   unsigned char stack[NB_PAIRS_MAX+1], pairs[NB_PAIRS_MAX][2];
91   short c, top = -1;
92   int nb_pairs = 0;
93 
94   /* Check for optional pair count and pair table */
95   if ((c = fgetc(in)) > 127)
96     {
97       /* Read pairs table */
98       nb_pairs = c - 128;
99       int i, j;
100       for (i = 0; i < nb_pairs; i++)
101 	{
102 	  for (j = 0; j < 2; j++)
103 	    {
104 	      int c = fgetc(in);
105 	      if (c == EOF)
106 		{
107 		  log_error("decompress: invalid header: truncated pair table");
108 		  free(rbuf[script]);
109 		  rbuf[script] = NULL;
110 		  return;
111 		}
112 	      if (c > i+128)
113 		{
114 		  log_error("decompress: invalid header: reference to a pair that is not registered yet");
115 		  free(rbuf[script]);
116 		  rbuf[script] = NULL;
117 		  return;
118 		}
119 	      pairs[i][j] = c;
120 	    }
121 	}
122     }
123   else
124     {
125       /* Non-compressed file, put back the character we read */
126       ungetc(c, in);
127     }
128 
129   for (;;)
130     {
131       /* Pop byte from stack or read byte from file */
132       if (top >= 0)
133 	c = stack[top--];
134       else if ((c = fgetc(in)) == EOF)
135 	break;
136 
137       /* Push pair on stack or output byte to file */
138       if (c > 127)
139 	{
140 	  if ((c-128) >= nb_pairs)
141 	    {
142 	      log_error("decompress: invalid body: references non-existent pair");
143 	      break;
144 	    }
145 	  stack[++top] = pairs[c-128][1];
146 	  stack[++top] = pairs[c-128][0];
147 	}
148       else
149 	{
150 	  rbuf[script][nb_read] = c;
151 	  nb_read++;
152 	  if ((nb_read % step) == 0)
153 	    rbuf[script] = xrealloc(rbuf[script], nb_read + step);
154 	}
155     }
156   rinfo[script]->end = nb_read;
157   rbuf[script][nb_read] = '\0'; /* safety */
158   rbuf[script] = xrealloc(rbuf[script], nb_read+1);
159 }
160 
decompress_nocomp(FILE * in,int script)161 static void decompress_nocomp(FILE *in, int script)
162 {
163   int step = 512;
164   int nb_read = 0;
165   rbuf[script] = xmalloc(step);
166   rbuf[script][0] = '\0';
167 
168   int c;
169   while ((c = getc(in)) != EOF)
170     {
171       rbuf[script][nb_read] = c;
172       nb_read++;
173       if ((nb_read % step) == 0)
174 	rbuf[script] = xrealloc(rbuf[script], nb_read + step);
175     }
176   rinfo[script]->end = nb_read;
177   rbuf[script][nb_read] = '\0'; /* safety */
178   rbuf[script] = xrealloc(rbuf[script], nb_read+1);
179 }
180 
181 
182 /**
183  * Only load game metadata (timetime). Used when displaying the list
184  * of saved games (see decipher_string).
185  */
load_game_small(int num,char line[196],int * mytime)186 static /*bool*/int load_game_small(int num, char line[196], int *mytime)
187 {
188   FILE *f = paths_savegame_fopen(num, "rb");
189   if (f == NULL)
190     {
191       log_info("Couldn't quickload save game %d", num);
192       return /*false*/0;
193     }
194   else
195     {
196       //int version = read_lsb_int(f);
197       fseek(f, 4, SEEK_CUR);
198 
199       fread(line, 196, 1, f);
200       line[195] = '\0';
201       *mytime = read_lsb_int(f);
202       fclose(f);
203 
204       return /*true*/1;
205     }
206 }
207 
208 
209 /**
210  * Load script from 'filename', save it in the first available script
211  * slot, attach to game sprite #'sprite' if 'set_sprite' is 1.
212  **/
load_script(char filename[15],int sprite,int set_sprite)213 int load_script(char filename[15], int sprite, /*bool*/int set_sprite)
214 {
215   char temp[100];
216   int script = 0;
217   FILE *in = NULL;
218   /*bool*/int comp = /*false*/0;
219 
220   log_info("LOADING %s", filename);
221 
222   sprintf(temp, "story/%s.d", filename);
223   in = paths_dmodfile_fopen(temp, "rb");
224   if (in == NULL)
225     {
226       sprintf(temp, "story/%s.c", filename);
227       in = paths_dmodfile_fopen(temp, "rb");
228       if (in == NULL)
229 	{
230 	  sprintf(temp, "story/%s.d", filename);
231 	  in = paths_fallbackfile_fopen(temp, "rb");
232 	  if (in == NULL)
233 	    {
234 	      sprintf(temp, "story/%s.c", filename);
235 	      in = paths_fallbackfile_fopen(temp, "rb");
236 	      if (in == NULL)
237 		{
238 		  log_warn("Script %s not found. (checked for .C and .D) (requested by %d?)", temp, sprite);
239 		  return 0;
240 		}
241 	    }
242 	}
243     }
244 
245   strtoupper(temp);
246   log_debug("Temp thingie is %c", temp[strlen(temp)-1]);
247   if (temp[strlen(temp)-1] == 'D')
248     comp = 1;
249   else
250     comp = 0;
251 
252   {
253     int k = 1;
254     for (; k < MAX_SCRIPTS; k++)
255       if (rbuf[k] == NULL)
256 	break;
257     script = k;
258   }
259 
260   if (script == MAX_SCRIPTS)
261     {
262       log_error("Couldn't find unused buffer for script.");
263       fclose(in);
264       return 0;
265     }
266 
267   log_info("Loading script %s.. (slot %d)", temp, script);
268   rinfo[script] = XZALLOC(struct refinfo);
269   if (rinfo[script] == NULL)
270     {
271       log_error("Couldn't allocate rscript %d.", script);
272       return 0;
273     }
274   memset(rinfo[script], 0, sizeof(struct refinfo));
275   /* For clarity: */
276   rinfo[script]->current  = 0;
277   rinfo[script]->cur_line = 1;
278   rinfo[script]->cur_col  = 0;
279   rinfo[script]->debug_line = 1;
280 
281 
282   if (comp)
283     {
284       log_debug("Decompressing...");
285       decompress(in, script);
286     }
287   else
288     {
289       log_debug("Reading from disk...");
290       decompress_nocomp(in, script);
291     }
292   fclose(in);
293 
294   if (rbuf[script] == NULL)
295     {
296       log_error("Couldn't allocate rbuff %d.", script);
297       free(rinfo[script]);
298       rinfo[script] = NULL;
299       return 0;
300     }
301 
302   rinfo[script]->name = strdup(filename);
303   rinfo[script]->sprite = sprite;
304 
305   if (set_sprite && sprite != 0 && sprite != 1000)
306     spr[sprite].script = script;
307 
308   return script;
309 }
310 
311 
dinkc_execute_one_liner(char * line)312 int dinkc_execute_one_liner(char* line)
313 {
314   /* Find available script slot */
315   int k = 1;
316   for (k = 1; k < MAX_SCRIPTS; k++)
317     if (rbuf[k] == NULL)
318       break;
319 
320   if (k < MAX_SCRIPTS)
321     {
322       rinfo[k] = XZALLOC(struct refinfo);
323       rinfo[k]->name = xmalloc(1);
324       rinfo[k]->name[0] = '\0';
325       rinfo[k]->sprite = 1000; /* survice screen change */
326       rinfo[k]->level = 1; /* skip 'void main(void) {' parsing */
327       rbuf[k] = (char*) malloc(255);
328       strcpy(rbuf[k], line);
329       process_line(k, rbuf[k], 0);
330       return returnint;
331     }
332   else
333     return -1;
334 }
335 
336 
337 /**
338  * Remove leading spaces by shifting 'str' to the left, as much as
339  * there is leading spaces.
340  */
strip_beginning_spaces(char * str)341 void strip_beginning_spaces(char *str)
342 {
343   char *pc = str;
344   int diff = 0;
345 /*   int i; */
346 
347   /* Find first non-space character (pos) */
348   while (*pc == ' ')
349     pc++;
350   diff = pc - str;
351 
352   /* Shift string to the left from pos */
353   /* Don't use str(str, pc) to avoid memory overlap */
354   while (*pc != '\0')
355     {
356       *(pc - diff) = *pc;
357       pc++;
358     }
359   *(pc - diff) = '\0';
360 }
361 
362 
363 
364 /**
365  * Locate a procedure (such as "void hit()")
366  */
locate(int script,char * lookup_proc)367 /*bool*/int locate(int script, char* lookup_proc)
368 {
369   if (rinfo[script] == NULL)
370     return 0;
371 
372   int save_current  = rinfo[script]->current;
373   int save_cur_line = rinfo[script]->cur_line;
374   int save_cur_col  = rinfo[script]->cur_col;
375   int save_debug_line = rinfo[script]->debug_line;
376   rinfo[script]->current  = 0;
377   rinfo[script]->cur_line = 1;
378   rinfo[script]->cur_col  = 0;
379   rinfo[script]->debug_line = 1;
380 
381   char* line = NULL;
382   char* word = NULL;
383 
384   while((line = read_next_line(script)) != NULL)
385     {
386       strip_beginning_spaces(line);
387 
388       int is_proc = 0;
389       word = get_word(line, 1);
390       if (compare(word, "VOID"))
391 	is_proc = 1;
392       free(word);
393       if (is_proc)
394 	{
395 	  char* cur_proc = NULL;
396 	  word = get_word(line, 2);
397 	  cur_proc = separate_string(word, 1, '(');
398 	  free(word);
399 
400 	  int is_right_proc = 0;
401 	  if (compare(cur_proc, lookup_proc))
402 	    is_right_proc = 1;
403 	  free(cur_proc);
404 
405 	  if (is_right_proc)
406 	    {
407 	      //clean up vars so it is ready to run
408 	      if (rinfo[script]->sprite != 1000)
409 		{
410 		  spr[rinfo[script]->sprite].move_active = 0;
411 		  if (dversion >= 108)
412 		    spr[rinfo[script]->sprite].move_nohard = 0;
413 		}
414 	      rinfo[script]->skipnext = /*false*/0;
415 	      rinfo[script]->onlevel = 0;
416 	      rinfo[script]->level = 0;
417 
418 	      free(line);
419 	      return 1;
420 	      //this is desired proc
421 	    }
422 	}
423       free(line);
424     }
425 
426   // Not found, restoring position
427   rinfo[script]->current  = save_current;
428   rinfo[script]->cur_line = save_cur_line;
429   rinfo[script]->cur_col  = save_cur_col;
430   rinfo[script]->debug_line = save_debug_line;
431   return 0;
432 }
433 
434 /**
435  * Look for the 'label' label (e.g. 'loop:'), that is used by a "goto"
436  * instruction. This sets the script->current field appropriately.
437  **/
locate_goto(char * expr,int script)438 /*bool*/int locate_goto(char* expr, int script)
439 {
440   replace_norealloc(";", "", expr);
441   char* label = xmalloc(strlen(expr) + 1 + 1);
442   sprintf(label, "%s:", expr);
443 
444   char* line = NULL;
445   rinfo[script]->current = 0;
446   rinfo[script]->cur_line = 1;
447   while ((line = read_next_line(script)) != NULL)
448     {
449       strip_beginning_spaces(line);
450 
451       int is_right_label = 0;
452       char* word = get_word(line, 1);
453       replace_norealloc("\n", "", word);
454       if (compare(word, label))
455 	is_right_label = 1;
456       free(word);
457 
458       if (is_right_label)
459 	{
460 	  log_debug("Found goto : Line is %s, word is %s.", line, label);
461 
462 	  rinfo[script]->skipnext = /*false*/0;
463 	  rinfo[script]->onlevel = 0;
464 	  rinfo[script]->level = 0;
465 
466 	  free(label);
467 	  free(line);
468 	  return 1;
469 	  //this is desired label
470 	}
471       free(line);
472     }
473 
474   log_warn("%s: cannot goto %s", rinfo[script]->name, label);
475   free(label);
476   return 0;
477 }
478 
479 /**
480  * v1.07-style scope. This function is buggy: the first memory slot
481  * has precedence (independently of local/global scope).
482  *
483  * Return -1 if not found, slot index >1 if found. Slot 0 isn't
484  * currently used by the engine.
485  */
search_var_with_this_scope_107(char * variable,int var_scope)486 int search_var_with_this_scope_107(char* variable, int var_scope)
487 {
488   int i;
489   for (i = 1; i < MAX_VARS; i ++)
490     if (play.var[i].active == 1
491 	&& ((play.var[i].scope == DINKC_GLOBAL_SCOPE) || (play.var[i].scope == var_scope))
492 	&& (compare(play.var[i].name, variable)))
493       return i;
494   return -1; /* not found */
495 }
496 
497 /**
498  * v1.08-style scope: local variables are searched before global
499  * variables.
500  *
501  * Return -1 if not found, slot index >1 if found. Slot 0 isn't
502  * currently used by the engine.
503  */
search_var_with_this_scope_108(char * variable,int var_scope)504 int search_var_with_this_scope_108(char* variable, int var_scope)
505 {
506   int search_scope[2];
507   search_scope[0] = var_scope; /* first local scope */
508   search_scope[1] = DINKC_GLOBAL_SCOPE; /* then global scope */
509 
510   int i;
511   for (i = 0; i < 2; i++)
512     {
513       //We'll start going through every var, starting at one
514       int v;
515       for (v = 1; v < MAX_VARS; v++)
516 	{
517 	  //Okay... make sure the var is active,
518 	  //The scope should match the script,
519 	  //Then make sure the name is the same.
520 	  if (play.var[v].active
521 	      && play.var[v].scope == search_scope[i]
522 	      && compare (play.var[v].name, variable))
523 	    return v;
524 	}
525     }
526   return -1;
527 }
528 
529 /**
530  *
531  */
search_var_with_this_scope(char * variable,int scope)532 int search_var_with_this_scope(char* variable, int scope)
533 {
534   if (dversion >= 108)
535     return search_var_with_this_scope_108(variable, scope);
536   return search_var_with_this_scope_107(variable, scope);
537 }
538 
539 /**
540  * Expand 'variable' in the scope of 'script' and return the integer
541  * value. Only used in function 'get_parms'.
542  */
decipher(char * variable,int script)543 long decipher(char* variable, int script)
544 {
545   // Special vars: &current_sprite and &current_script
546   if (compare(variable, "&current_sprite"))
547     return rinfo[script]->sprite;
548   if (compare(variable, "&current_script"))
549     return script;
550 
551   //v1.08 special variables.
552   if (dversion >= 108)
553     {
554       if (compare(variable, "&return"))
555 	return returnint;
556       if (compare(variable, "&arg1"))
557 	return rinfo[script]->arg1;
558       if (compare(variable, "&arg2"))
559 	return rinfo[script]->arg2;
560       if (compare(variable, "&arg3"))
561 	return rinfo[script]->arg3;
562       if (compare(variable, "&arg4"))
563 	return rinfo[script]->arg4;
564       if (compare(variable, "&arg5"))
565 	return rinfo[script]->arg5;
566       if (compare(variable, "&arg6"))
567 	return rinfo[script]->arg6;
568       if (compare(variable, "&arg7"))
569 	return rinfo[script]->arg7;
570       if (compare(variable, "&arg8"))
571 	return rinfo[script]->arg8;
572       if (compare(variable, "&arg9"))
573 	return rinfo[script]->arg9;
574     }
575 
576   // Check in local and global variables
577   int i = search_var_with_this_scope(variable, script);
578   if (i != -1)
579     return play.var[i].var;
580   else
581     return 0; // compatibility
582 }
583 
584 
585 /**
586  * Replace all variables in a string; try longest variables
587  * first. Known bug: may replace shorter variables (e.g. &gold instead
588  * of &golden).
589  */
var_replace_107(char ** line_p,int scope)590 void var_replace_107(char** line_p, int scope)
591 {
592   char crap[20];
593   int i;
594   for (i = 1; i < MAX_VARS; i ++)
595     if ((play.var[i].active == 1)
596 	&& ((play.var[i].scope == DINKC_GLOBAL_SCOPE) || (play.var[i].scope == scope)))
597       {
598 	sprintf(crap, "%d", play.var[i].var);
599 	replace(play.var[i].name, crap, line_p);
600       }
601 }
602 
603 /**
604  * Replace all variables in a string; try longest variables first.
605  *
606  * Possible improvements:
607  *
608  * - Copy play.var[] and sort it by variable length (and avoid the
609  *   recursion)
610  *
611  * - find vars in the string and replace them as-needed (requires
612  *   understanding what exactly is an end-of-variable delimiter, if
613  *   such a thing exists)
614  */
var_replace_108(int i,int script,char ** line_p,char * prevar)615 void var_replace_108(int i, int script, char** line_p, char *prevar)
616 {
617   while (i < MAX_VARS)
618     {
619       //First, make sure the variable is active.
620       //Then, make sure it is in scope,
621       //Then, see if the variable name is in the line
622       //Then, prevar is null, or if prevar isn't null, see if current variable starts with prevar
623       if (play.var[i].active
624 	  && i == search_var_with_this_scope_108(play.var[i].name, script)
625 	  && strstr (*line_p, play.var[i].name)
626 	  && (prevar == NULL || (prevar != NULL && strstr (play.var[i].name, prevar))))
627 	{
628 	  //Look for shorter variables
629 	  var_replace_108(i + 1, script, line_p, play.var[i].name);
630 	  //we didn't find any, so we replace!
631 	  char crap[20];
632 	  sprintf(crap, "%d", play.var[i].var);
633 	  replace(play.var[i].name, crap, line_p);
634 	}
635       i++;
636     }
637 }
638 
639 /**
640  * Replace all variables (&something) in 'line', with scope 'scope'
641  */
var_replace(char ** line_p,int scope)642 void var_replace(char** line_p, int scope)
643 {
644   if (dversion >= 108)
645     var_replace_108(1, scope, line_p, NULL);
646   else
647     var_replace_107(line_p, scope);
648 }
649 
650 
651 /**
652  * Similar to decipher, plus:
653  * - expand special choice variables &savegameinfo and &buttoninfo
654  * - it can replace several variables in the same string
655  * - with v1.07 it has a prefix bug (see var_replace_107)
656  */
decipher_string(char ** line_p,int script)657 void decipher_string(char** line_p, int script)
658 {
659   char buffer[20 + 1];
660 
661   /* Replace all valid variables in 'line' */
662   var_replace(line_p, script);
663 
664   if ((strchr(*line_p, '&') != NULL) && (script != 0))
665     {
666       sprintf(buffer, "%d", rinfo[script]->sprite); replace("&current_sprite", buffer, line_p);
667       sprintf(buffer, "%d", script);                replace("&current_script", buffer, line_p);
668 
669       if (dversion >= 108)
670 	{
671 	  //v1.08 special variables.
672 	  sprintf(buffer, "%d", returnint);           replace("&return", buffer, line_p);
673 	  sprintf(buffer, "%d", rinfo[script]->arg1); replace("&arg1", buffer, line_p);
674 	  sprintf(buffer, "%d", rinfo[script]->arg2); replace("&arg2", buffer, line_p);
675 	  sprintf(buffer, "%d", rinfo[script]->arg3); replace("&arg3", buffer, line_p);
676 	  sprintf(buffer, "%d", rinfo[script]->arg4); replace("&arg4", buffer, line_p);
677 	  sprintf(buffer, "%d", rinfo[script]->arg5); replace("&arg5", buffer, line_p);
678 	  sprintf(buffer, "%d", rinfo[script]->arg6); replace("&arg6", buffer, line_p);
679 	  sprintf(buffer, "%d", rinfo[script]->arg7); replace("&arg7", buffer, line_p);
680 	  sprintf(buffer, "%d", rinfo[script]->arg8); replace("&arg8", buffer, line_p);
681 	  sprintf(buffer, "%d", rinfo[script]->arg9); replace("&arg9", buffer, line_p);
682 	}
683 
684       if (decipher_savegame != 0)
685 	{
686 	  int button_action = input_get_button_action(decipher_savegame-1);
687 	  if      (button_action == ACTION_ATTACK)    replace("&buttoninfo", _("Attack"), line_p);
688 	  else if (button_action == ACTION_TALK)      replace("&buttoninfo", _("Talk/Examine"), line_p);
689 	  else if (button_action == ACTION_MAGIC)     replace("&buttoninfo", _("Magic"), line_p);
690 	  else if (button_action == ACTION_INVENTORY) replace("&buttoninfo", _("Item Screen"), line_p);
691 	  else if (button_action == ACTION_MENU)      replace("&buttoninfo", _("Main Menu"), line_p);
692 	  else if (button_action == ACTION_MAP)       replace("&buttoninfo", _("Map"), line_p);
693 	  else if (button_action == ACTION_BUTTON7)   replace("&buttoninfo", _("Unused"), line_p);
694 	  else if (button_action == ACTION_BUTTON8)   replace("&buttoninfo", _("Unused"), line_p);
695 	  else if (button_action == ACTION_BUTTON9)   replace("&buttoninfo", _("Unused"), line_p);
696 	  else if (button_action == ACTION_BUTTON10)  replace("&buttoninfo", _("Unused"), line_p);
697 	  else if (button_action == ACTION_DOWN)      replace("&buttoninfo", _("Down"), line_p);
698 	  else if (button_action == ACTION_LEFT)      replace("&buttoninfo", _("Left"), line_p);
699 	  else if (button_action == ACTION_RIGHT)     replace("&buttoninfo", _("Right"), line_p);
700 	  else if (button_action == ACTION_UP)        replace("&buttoninfo", _("Up"), line_p);
701 	  else replace("&buttoninfo", _("Error: not mapped"), line_p);
702 	}
703     }
704 
705   if ((decipher_savegame != 0) && compare(*line_p, "&savegameinfo"))
706     {
707       char gameinfo[196] = "";
708       int mytime = 0;
709 
710       free(*line_p);
711       if (load_game_small(decipher_savegame, gameinfo, &mytime) == 1)
712 	asprintf(line_p, _("Slot %d - %d:%02d - %s"), decipher_savegame,
713 		 mytime/60, mytime%60, gameinfo);
714       else
715 	asprintf(line_p, _("Slot %d - Empty"), decipher_savegame);
716     }
717 }
718 
719 /**
720  *
721  * name: name of the procedure() to call
722  * n1: wait at least n1 milliseconds before callback
723  * n2: wait at most n1+n2 milliseconds before callback
724  * script: number of the script currently running
725  **/
add_callback(char name[20],int n1,int n2,int script)726 int add_callback(char name[20], int n1, int n2, int script)
727 {
728   int k;
729   for (k = 1; k < MAX_CALLBACKS; k++)
730     {
731       if (callback[k].active == /*false*/0)
732 	{
733 	  memset(&callback[k], 0, sizeof(callback[k]));
734 
735 	  callback[k].active = /*true*/1;
736 	  callback[k].min = n1;
737 	  callback[k].max = n2;
738 	  callback[k].owner = script;
739 	  strcpy(callback[k].name, name);
740 
741 	  log_debug("Callback added to %d.", k);
742 	  return(k);
743 	}
744     }
745 
746   log_error("Couldn't add callback, all out of space");
747   return 0;
748 }
749 
kill_callback(int cb)750 void kill_callback(int cb)
751 {
752   if (cb >= 0 && cb <= 99)
753     callback[cb].active = /*false*/0;
754 }
755 
kill_callbacks_owned_by_script(int script)756 void kill_callbacks_owned_by_script(int script)
757 {
758   int i = 1;
759   for (; i < MAX_CALLBACKS; i++)
760     {
761       if (callback[i].owner == script)
762 	{
763 	  log_debug("Kill_all_callbacks just killed %d for script %d", i, script);
764 	  //killed callback
765 	  callback[i].active = /*false*/0;
766 	}
767     }
768 }
769 
770 
kill_script(int k)771 void kill_script(int k)
772 {
773   if (rinfo[k] != NULL)
774     {
775       int i;
776 
777       kill_callbacks_owned_by_script(k);
778 
779       // Now let's kill all local vars associated with this script
780       for (i = 1; i < MAX_VARS; i++)
781 	{
782 	  if (play.var[i].active && play.var[i].scope == k)
783 	    play.var[i].active = /*false*/0;
784 	}
785       log_debug("Killed script %s. (num %d)", rinfo[k]->name, k);
786 
787       if (rinfo[k]->name != NULL)
788 	free(rinfo[k]->name);
789       if (rinfo[k] != NULL)
790 	free(rinfo[k]);
791       rinfo[k] = NULL;
792       if (rbuf[k] != NULL)
793 	free(rbuf[k]);
794       rbuf[k] = NULL;
795     }
796 }
797 
798 
799 /**
800  * Kill all scripts except those attached to pseudo-sprite 1000, which
801  * is meant to survive across screen changes
802  * (kill_all_scripts_for_real(...) is more brutal)
803  *
804  * Used by gfx_tiles only
805  */
kill_all_scripts(void)806 void kill_all_scripts(void)
807 {
808   /* Kill scripts (except if attached to pseudo-sprite 1000) */
809   int k = 1;
810   for (; k < MAX_SCRIPTS; k++)
811     {
812       if (rinfo[k] != NULL)
813 	if (rinfo[k]->sprite != 1000)
814 	  kill_script(k);
815     }
816 
817   /* Kill pending callbacks (except if attached to pseudo-sprite 1000) */
818   for (k = 1; k < MAX_CALLBACKS; k++)
819     {
820       if (callback[k].active
821 	  && (!(rinfo[callback[k].owner] != NULL)
822 	      && (rinfo[callback[k].owner]->sprite == 1000)))
823 	{
824 	  log_debug("Killed callback %d.  (was attached to script %d)",
825 		    k, callback[k].owner);
826 	  callback[k].active = 0;
827 	}
828     }
829 }
830 
831 /**
832  * Kill all scripts including those attached to pseudo-sprite 1000
833  */
kill_all_scripts_for_real(void)834 void kill_all_scripts_for_real(void)
835 {
836   int k = 1;
837   for (k = 1; k < MAX_SCRIPTS; k++)
838     {
839       if (rinfo[k] != NULL)
840 	kill_script(k);
841     }
842 
843   for (k = 1; k <= MAX_CALLBACKS; k++)
844     {
845       callback[k].active = 0;
846     }
847 }
848 
849 
850 /**
851  * Return the next single line from rbuf[script], starting at
852  * rinfo[script]->current. Update line/column counters.
853  */
read_next_line(int script)854 char* read_next_line(int script)
855 {
856   if (rinfo[script] == NULL || rbuf == NULL)
857     {
858       log_error("Tried to read script %d, it doesn't exist.", script);
859       return NULL;
860     }
861 
862   if (rinfo[script]->current >= rinfo[script]->end)
863     {
864       //at end of buffer
865       return NULL;
866     }
867 
868   /* remember the beginning of the line to be parsed, we'll use it in
869      the debugging messages */
870   rinfo[script]->debug_line = rinfo[script]->cur_line;
871 
872   int k = rinfo[script]->current;
873   int start = k;
874   for (; k < rinfo[script]->end; k++)
875     {
876       rinfo[script]->current++;
877       rinfo[script]->cur_col++;
878 
879       if (rbuf[script][k] == '\n')
880 	{
881 	  rinfo[script]->cur_line++;
882 	  rinfo[script]->cur_col = 0;
883 	}
884       if (rbuf[script][k] == '\n' || rbuf[script][k] == '\r')
885 	break;
886     }
887 
888   if (k < rinfo[script]->end)
889     {
890       int len = rinfo[script]->current - start;
891       char* buf = xmalloc(len + 1);
892 
893       char* pc = buf;
894       int k = start;
895 	for (; k < rinfo[script]->current; k++, pc++)
896 	{
897 	  *pc = rbuf[script][k];
898 
899 	  /* Compatibility substitutions, important when parsing
900 	     title_start/title_end, namely */
901 	  if (*pc == '\t') *pc = ' ';
902 	  if (*pc == '\r') *pc = '\n';
903 	}
904       *pc = '\0'; /* for safety */
905       return buf;
906     }
907   else
908     {
909       //at end of buffer
910       return NULL;
911     }
912 }
913 
914 /**
915  * Run callbacks, order by index. Sets the activation delay if
916  * necessary. Kill obsolete callbacks along the way.
917  *
918  * Callbacks are set by wait() and set_callback_random().
919  *
920  * spawn()/external()/etc. use other mechanisms. say_stop*() also use
921  * callbacks, but implemented differently (spr[x].callback, processed
922  * in updateFrame()).
923  **/
process_callbacks(void)924 void process_callbacks(void)
925 {
926   int now = game_GetTicks();
927   int i, k;
928 
929   for (i = 1; i < MAX_SCRIPTS; i++)
930     {
931       if (rinfo[i] != NULL)
932 	{
933 	  if (rinfo[i]->sprite > 0 && rinfo[i]->sprite != 1000 && spr[rinfo[i]->sprite].active == /*false*/0)
934 	    {
935 	      //kill this script, owner is dead
936 	      log_debug("Killing script %s, owner sprite %d is dead.", rinfo[i]->name, rinfo[i]->sprite);
937 	      kill_script(i);
938 	    }
939 	}
940     }
941 
942   for (k = 1; k < MAX_CALLBACKS; k++)
943     {
944       if (callback[k].active)
945 	{
946 	  if (callback[k].owner > 0 && rinfo[callback[k].owner] == NULL)
947 	    {
948 	      //kill this process, it's owner sprite is 'effin dead.
949 	      log_debug("Killed callback %s because script %d is dead.",
950 			k, callback[k].owner);
951 	      callback[k].active = /*false*/0;
952 	    }
953 	  else
954 	    {
955 	      if (callback[k].timer == 0)
956 		{
957 		  //set timer
958 
959 		  if (callback[k].max > 0)
960 		    callback[k].timer = now + (rand() % callback[k].max) + callback[k].min;
961 		  else
962 		    callback[k].timer = now + callback[k].min;
963 		}
964 	      else
965 		{
966 		  if (callback[k].timer < now)
967 		    {
968 		      callback[k].timer = 0;
969 
970 		      if (compare(callback[k].name, ""))
971 			{
972 			  //callback defined no proc name, so lets assume they want to start the script where it
973 			  //left off
974 			  //kill this callback
975 			  callback[k].active = /*false*/0;
976 			  run_script(callback[k].owner);
977 			  log_debug("Called script %d from callback %d.",
978 				    callback[k].owner, k);
979 			}
980 		      else
981 			{
982 			  log_debug("Called proc %s from callback %d.", callback[k].name, k);
983 
984 			  //callback defined a proc name
985 			  if (locate(callback[k].owner,callback[k].name))
986 			    {
987 			      //found proc, lets run it
988 			      run_script(callback[k].owner);
989 			    }
990 			}
991 		    }
992 		}
993 	    }
994 	}
995     }
996 }
997 
998 
999 /**
1000  * Run main() for all active sprites on screen
1001  */
init_scripts()1002 void init_scripts()
1003 {
1004   int k = 1;
1005   for (; k < MAX_SCRIPTS; k++)
1006     {
1007       if (rinfo[k] != NULL && rinfo[k]->sprite != 0
1008 	  /* don't go out of bounds in spr[300], e.g. when sprite == 1000: */
1009 	  && rinfo[k]->sprite < MAX_SPRITES_AT_ONCE
1010 	  && spr[rinfo[k]->sprite].active)
1011 	{
1012 	  if (locate(k, "main"))
1013 	    {
1014 	      log_debug("Screendraw: running main of script %s..", rinfo[k]->name);
1015 	      run_script(k);
1016 	    }
1017 	}
1018     }
1019 }
1020 
1021 
1022 
kill_scripts_owned_by(int sprite)1023 void kill_scripts_owned_by(int sprite)
1024 {
1025   int i;
1026         for (i = 1; i < MAX_SCRIPTS; i++)
1027         {
1028                 if (rinfo[i] != NULL)
1029                 {
1030                         if (rinfo[i]->sprite == sprite)
1031                         {
1032                                 kill_script(i);
1033 
1034                         }
1035 
1036                 }
1037         }
1038 
1039 }
1040 
kill_returning_stuff(int script)1041 void kill_returning_stuff(int script)
1042 {
1043   //Msg("Checking callbacks..");
1044   //check callbacks
1045 
1046   int i;
1047   // callbacks from wait() and run_script_by_number()
1048   for (i = 1; i < MAX_CALLBACKS; i++)
1049     {
1050       if (callback[i].active && callback[i].owner == script)
1051 	//      if (compare(callback[i].name, ""))
1052 	{
1053 	  log_debug("killed a returning callback, ha!");
1054 	  callback[i].active = /*false*/0;
1055 	}
1056 
1057     }
1058 
1059   // callbacks from say_*()
1060   for (i = 1; i <= last_sprite_created; i++)
1061     {
1062       if (spr[i].active && spr[i].brain == 8 && spr[i].callback == script)
1063 	{
1064 	  log_debug("Killed sprites callback command");
1065 	  spr[i].callback = 0;
1066 	}
1067     }
1068 }
1069 
1070 
run_script(int script)1071 void run_script(int script)
1072 {
1073   int result;
1074   char* line = NULL;
1075 
1076   /* keep 'return' value? */
1077   if (dversion >= 108)
1078     {
1079       if (bKeepReturnInt == 1)
1080 	{
1081 	  bKeepReturnInt = 0;
1082 	}
1083       else
1084 	{
1085 	  returnint = 0;
1086 	}
1087     }
1088   else
1089     {
1090       returnint = 0;
1091     }
1092   returnstring[0] = 0;
1093 
1094 
1095   if (rinfo[script] != NULL)
1096     {
1097       log_debug("Script %s is entered at %d:%d (offset %d).",
1098 		rinfo[script]->name,
1099 		rinfo[script]->cur_line, rinfo[script]->cur_col,
1100 		rinfo[script]->current);
1101     }
1102   else
1103     {
1104       log_error("Tried to run a script that doesn't exist in memory.  Nice work.");
1105     }
1106 
1107   int doelse_once = 0;
1108   while ((line = read_next_line(script)) != NULL)
1109     {
1110       while (1)
1111 	{
1112 	  strip_beginning_spaces(line);
1113 	  if (strcmp(line, "\n") == 0)
1114 	    break;
1115 
1116 
1117 	  int doelse = 0;
1118 	  if (doelse_once == 1)
1119 	    {
1120 	      doelse = 1;
1121 	      doelse_once = 0;
1122 	    }
1123 	  result = process_line(script, line, doelse);
1124 
1125 
1126 	  if (result == DCPS_DOELSE_ONCE)
1127 	    {
1128 	      doelse_once = 1;
1129 	      /* now process the rest of the line */
1130 	    }
1131 
1132 	  if (result == DCPS_YIELD)
1133 	    {
1134 	      /* Quit script: */
1135 	      log_debug("giving script the boot");
1136 	      free(line);
1137 	      return;
1138 	    }
1139 
1140 	  if (result == DCPS_GOTO_NEXTLINE)
1141 	    break;
1142 
1143 	  /* else result == DCPS_CONTINUE */
1144 	}
1145       free(line);
1146     }
1147 
1148   if (rinfo[script] != NULL && rinfo[script]->proc_return != 0)
1149     {
1150       run_script(rinfo[script]->proc_return);
1151       kill_script(script);
1152     }
1153 }
1154 
var_exists(char name[20],int scope)1155 int var_exists(char name[20], int scope)
1156 {
1157   int i;
1158         for (i = 1; i < MAX_VARS; i++)
1159         {
1160                 if (play.var[i].active)
1161                 {
1162                         if (compare(play.var[i].name, name))
1163                         {
1164 
1165                                 if (scope == play.var[i].scope)
1166                                 {
1167                                         //Msg("Found match for %s.", name);
1168                                         return(i);
1169                                 }
1170                         }
1171 
1172 
1173 
1174                 }
1175         }
1176 
1177         return(0);
1178 }
1179 
1180 /**
1181  * Make new global functions (v1.08)
1182  */
make_function(char file[10],char func[20])1183 void make_function (char file[10], char func[20])
1184 {
1185   //See if it already exists
1186 
1187   int exists = 0;
1188   int i;
1189   for (i = 0; strlen (play.func[i].func) > 0 && i < 100; i++)
1190     {
1191       if (compare (func, play.func[i].func))
1192 	{
1193 	  exists = 1;
1194 	  break;
1195 	}
1196     }
1197   if (exists == 1)
1198     {
1199       strncpy (play.func[i].file, file, 10);
1200     }
1201   else
1202     {
1203       strncpy (play.func[0].file, file, 10);
1204       strncpy (play.func[0].func, func, 20);
1205     }
1206 }
1207 
1208 
make_int(char name[80],int value,int scope,int script)1209 void make_int(char name[80], int value, int scope, int script)
1210 {
1211         int dupe;
1212        int i;
1213         if (strlen(name) > 19)
1214         {
1215 
1216                 log_error("[DinkC] %s:%d: varname %s is too long",
1217 			  rinfo[script]->name, rinfo[script]->debug_line, name);
1218                 return;
1219         }
1220         dupe = var_exists(name, scope);
1221 
1222         if (dupe > 0)
1223 	  {
1224 	    if (scope != DINKC_GLOBAL_SCOPE)
1225 	      {
1226 		log_warn("[DinkC] %s:%d: Local var %s already used in this procedure",
1227 			 rinfo[script]->name, rinfo[script]->debug_line,
1228 			 name, rinfo[script]->name);
1229 
1230 		play.var[dupe].var = value;
1231 	      }
1232 	    else
1233 	      {
1234 		log_warn("[DinkC] %s:%d: var %s is already a global, not changing value",
1235 			  rinfo[script]->name, rinfo[script]->debug_line, name);
1236 	      }
1237 	    return;
1238         }
1239 
1240 
1241         //make new var
1242 
1243         for (i = 1; i < MAX_VARS; i++)
1244         {
1245                 if (play.var[i].active == /*false*/0)
1246                 {
1247 
1248                         play.var[i].active = /*true*/1;
1249                         play.var[i].scope = scope;
1250                         strcpy(play.var[i].name, name);
1251                         //g("var %s created, used slot %d ", name,i);
1252                         play.var[i].var = value;
1253                         return;
1254                 }
1255         }
1256 
1257         log_error("[DinkC] %s:%d: out of var space, all %d used",
1258 		  rinfo[script]->name, rinfo[script]->debug_line, MAX_VARS);
1259 }
1260 
1261 /**
1262  * (re)Define variable
1263  *
1264  * name: variable name
1265  * newname: new value (unless that's a function call, cf. 'rest')
1266  * math: operator (one of '=', '+', '-', '*', '/')
1267  * script: in-memory script identifier
1268  * rest: text of the script after the operator (left-trimmed)
1269  */
var_equals(char * name,char * newname,char math,int script,char rest[200])1270 void var_equals(char* name, char* newname, char math, int script, char rest[200])
1271 {
1272   int newval = 0;
1273   struct varman *lhs_var = NULL;
1274 
1275   /** Ensure left-hand side is an existing variable **/
1276   if (name[0] != '&')
1277     {
1278       log_error("[DinkC] %s:%d:[var_equals]: unknown var %s",
1279 		rinfo[script]->name, rinfo[script]->debug_line, name);
1280       return;
1281     }
1282   /* Find the variable slot */
1283   {
1284     int k = search_var_with_this_scope(name, script);
1285     if (k != -1)
1286       lhs_var = &(play.var[k]);
1287 
1288     if (lhs_var == NULL) /* not found */
1289       {
1290 	log_error("[DinkC] %s:%d:[var_equals]: unknown var %s",
1291 		  rinfo[script]->name, rinfo[script]->debug_line, name);
1292 	return;
1293       }
1294   }
1295 
1296   /** Analyse right-hand side **/
1297   /* check if right-hand side is a function */
1298   if (strchr(rest, '(') != NULL)
1299     {
1300       process_line(script, rest, /*false*/0);
1301       newval = returnint;
1302       goto next2;
1303     }
1304 
1305   /* check if right-hand side is a variable to copy */
1306   /* remove trailing ';' */
1307   if (strchr(newname, ';') != NULL)
1308     replace_norealloc(";", "", newname);
1309   /* look for existing variable */
1310   {
1311     int k2 = search_var_with_this_scope(newname, script);
1312     if (k2 != -1)
1313       {
1314 	newval = play.var[k2].var;
1315 	//found var
1316 	goto next2;
1317       }
1318   }
1319   /* also check special variables */
1320   if (compare(newname, "&current_sprite"))
1321     {
1322       newval = rinfo[script]->sprite;
1323       goto next2;
1324     }
1325   if (compare(newname, "&current_script"))
1326     {
1327       newval = script;
1328       goto next2;
1329     }
1330   if (dversion >= 108)
1331     {
1332       //v1.08 special variables.
1333       if (compare (newname, "&return"))
1334 	{
1335 	  newval = returnint;
1336 	  goto next2;
1337 	}
1338       if (compare (newname, "&arg1"))
1339 	{
1340 	  newval = rinfo[script]->arg1;
1341 	  goto next2;
1342 	}
1343       if (compare (newname, "&arg2"))
1344 	{
1345 	  newval = rinfo[script]->arg2;
1346 	  goto next2;
1347 	}
1348       if (compare (newname, "&arg3"))
1349 	{
1350 	  newval = rinfo[script]->arg3;
1351 	  goto next2;
1352 	}
1353       if (compare (newname, "&arg4"))
1354 	{
1355 	  newval = rinfo[script]->arg4;
1356 	  goto next2;
1357 	}
1358       if (compare (newname, "&arg5"))
1359 	{
1360 	  newval = rinfo[script]->arg5;
1361 	  goto next2;
1362 	}
1363       if (compare (newname, "&arg6"))
1364 	{
1365 	  newval = rinfo[script]->arg6;
1366 	  goto next2;
1367 	}
1368       if (compare (newname, "&arg7"))
1369 	{
1370 	  newval = rinfo[script]->arg7;
1371 	  goto next2;
1372 	}
1373       if (compare (newname, "&arg8"))
1374 	{
1375 	  newval = rinfo[script]->arg8;
1376 	  goto next2;
1377 	}
1378       if (compare (newname, "&arg9"))
1379 	{
1380 	  newval = rinfo[script]->arg9;
1381 	  goto next2;
1382 	}
1383     }
1384   /* otherwise, assume right-hand side is an integer */
1385   newval = atol(newname);
1386 
1387 
1388 next2:
1389   /* Apply the right operation */
1390   if (math == '=')
1391     lhs_var->var = newval;
1392   if (math == '+')
1393     lhs_var->var += newval;
1394   if (math == '-')
1395     lhs_var->var -= newval;
1396   if (math == '/')
1397     lhs_var->var = lhs_var->var / newval;
1398   if (math == '*')
1399     lhs_var->var = lhs_var->var * newval;
1400 }
1401 
1402 /**
1403  * Evaluate a value (variable, int, or maths), in the context of
1404  * 'script'.
1405  */
var_figure(char * h,int script)1406 int var_figure(char* h, int script)
1407 {
1408   char* word = NULL;
1409   int ret = 0;
1410   int n1 = 0, n2 = 0;
1411 
1412   int is_one_word_equation = 0;
1413   word = get_word(h, 2);
1414   if (compare(word, ""))
1415     is_one_word_equation = 1;
1416   free(word);
1417   if (is_one_word_equation)
1418     {
1419       // variable -> integer
1420       if (h[0] == '&')
1421 	decipher_string(&h, script);
1422 
1423       // integer
1424       ret = atol(h);
1425       return ret;
1426     }
1427 
1428   word = get_word(h, 1);
1429   decipher_string(&word, script);
1430   n1 = atol(word);
1431   free(word);
1432 
1433   word = get_word(h, 3);
1434   replace_norealloc(")", "", word);
1435   decipher_string(&word, script);
1436   n2 = atol(word);
1437   free(word);
1438 
1439   word = get_word(h, 2);
1440   log_debug("Compared %d to %d", n1, n2);
1441 
1442   if (compare(word, "=="))
1443     {
1444       if (n1 == n2) ret = 1; else ret = 0;
1445       free(word);
1446       return ret;
1447     }
1448 
1449   if (compare(word, ">"))
1450     {
1451       if (n1 > n2) ret = 1; else ret = 0;
1452       free(word);
1453       return ret;
1454     }
1455 
1456   if (compare(word, ">="))
1457     {
1458       if (n1 >= n2) ret = 1; else ret = 0;
1459       free(word);
1460       return ret;
1461     }
1462 
1463 
1464   if (compare(word, "<"))
1465     {
1466       if (n1 < n2) ret = 1; else ret = 0;
1467       free(word);
1468       return ret;
1469     }
1470 
1471   if (compare(word, "<="))
1472     {
1473       if (n1 <= n2) ret = 1; else ret = 0;
1474       free(word);
1475       return ret;
1476     }
1477 
1478   if (compare(word, "!="))
1479     {
1480       if (n1 != n2) ret = 1; else ret = 0;
1481       free(word);
1482       return ret;
1483     }
1484 
1485   free(word);
1486   return ret;
1487 }
1488 
1489 /**
1490  * Check if 'line' is a valid variable declaration, and define the
1491  * variable it to 0 (via make_int(...)). 'line' is modified.
1492  */
int_prepare(char * line,int script)1493 void int_prepare(char* line, int script)
1494 {
1495   char* hold = strdup(line);
1496 
1497   char* name = NULL;
1498   char *temp = NULL;
1499   replace_norealloc("=", " ", line);
1500   temp = separate_string(line, 1, ';');
1501   strcpy(line, temp); // safe as strlen(line) <= strlen(temp)
1502   free(temp);
1503   name = get_word(line, 2);
1504 
1505   if (name[0] != '&')
1506     {
1507       log_error("[DinkC] %s:%d: can't create var %s, should be &%s.",
1508 		rinfo[script]->name, rinfo[script]->debug_line,
1509 		name, name);
1510     }
1511   else
1512     {
1513       make_int(name, 0, script, script);
1514 
1515       strcpy(line, hold);
1516     }
1517   free(name);
1518   free(hold);
1519 }
1520 
1521 
dinkc_init()1522 void dinkc_init()
1523 {
1524   dinkc_bindings_init();
1525 }
1526 
dinkc_quit()1527 void dinkc_quit()
1528 {
1529   dinkc_bindings_quit();
1530 }
1531