1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  */
22 
23 #include "glk/jacl/jacl.h"
24 #include "glk/jacl/language.h"
25 #include "glk/jacl/types.h"
26 #include "glk/jacl/prototypes.h"
27 #include "glk/jacl/csv.h"
28 #include "common/str.h"
29 
30 namespace Glk {
31 namespace JACL {
32 
33 struct flock {
34 	short l_type;
35 	short l_whence;
36 	long l_start;
37 	long l_len;
38 	long l_pid;
39 };
40 
41 #define F_DUPFD  0
42 #define F_GETFD  1
43 #define F_SETFD  2
44 #define F_GETFL  3
45 #define F_SETFL  4
46 #define F_GETLK  5
47 #define F_SETLK  6
48 #define F_SETLKW 7
49 
50 #define F_RDLCK  0
51 #define F_WRLCK  1
52 #define F_UNLCK  2
53 
fcntl(int __fd,int __cmd,...)54 int fcntl(int __fd, int __cmd, ...) {
55 	return 0;
56 }
57 
58 #ifndef strcasestr
strcasestr(const char * s,const char * find)59 const char *strcasestr(const char *s, const char *find) {
60 	char c, sc;
61 	size_t len;
62 
63 	if ((c = *find++) != 0) {
64 		c = (char)tolower((unsigned char)c);
65 		len = strlen(find);
66 		do {
67 			do {
68 				if ((sc = *s++) == 0)
69 					return (NULL);
70 			} while ((char)tolower((unsigned char)sc) != c);
71 		} while (scumm_strnicmp(s, find, len) != 0);
72 		s--;
73 	}
74 	return s;
75 }
76 #endif
77 
78 #define MAX_TRY 10
79 
80 flock           read_lck;
81 int             read_fd;
82 flock           write_lck;
83 int             write_fd;
84 
85 char *url_encode(char *str);
86 char to_hex(char code);
87 
88 const char *location_attributes[] = {
89 	"VISITED ", "DARK ", "ON_WATER ", "UNDER_WATER ", "WITHOUT_AIR ", "OUTDOORS ",
90 	"MID_AIR ", "TIGHT_ROPE ", "POLLUTED ", "SOLVED ", "MID_WATER ", "DARKNESS ",
91 	"MAPPED ", "KNOWN ",
92 	NULL
93 };
94 
95 const char *object_attributes[] = {
96 	"CLOSED ", "LOCKED ", "DEAD ", "IGNITABLE ", "WORN ", "CONCEALING ",
97 	"LUMINOUS ", "WEARABLE ", "CLOSABLE ", "LOCKABLE ", "ANIMATE ", "LIQUID ",
98 	"CONTAINER ", "SURFACE ", "PLURAL ", "FLAMMABLE ", "BURNING ", "LOCATION ",
99 	"ON ", "DAMAGED ", "FEMALE ", "POSSESSIVE ", "OUT_OF_REACH ", "TOUCHED ",
100 	"SCORED ", "SITTING ", "NPC ", "DONE ", "GAS ", "NO_TAB ",
101 	"NOT_IMPORTANT ", NULL
102 };
103 
104 const char *object_elements[] = {
105 	"parent", "capacity", "mass", "bearing", "velocity", "next", "previous",
106 	"child", "index", "status", "state", "counter", "points", "class", "x", "y",
107 	NULL
108 };
109 
110 const char *location_elements[] = {
111 	"north", "south", "east", "west", "northeast", "northwest", "southeast",
112 	"southwest", "up", "down", "in", "out", "points", "class", "x", "y",
113 	NULL
114 };
115 
116 struct csv_parser               parser_csv;
117 char                            in_name[1024];
118 char                            out_name[1024];
119 Common::SeekableReadStream     *infile;
120 Common::WriteStream            *outfile;
121 
122 int                             stack = 0;
123 int                             proxy_stack = 0;
124 
125 int                             field_no = 0;
126 
127 struct stack_type               backup[STACK_SIZE];
128 struct proxy_type               proxy_backup[STACK_SIZE];
129 
130 struct function_type *resolved_function = NULL;
131 struct string_type *resolved_string = NULL;
132 
133 struct string_type *new_string = NULL;
134 struct string_type *current_cstring = NULL;
135 struct string_type *previous_cstring = NULL;
136 
137 struct cinteger_type *new_cinteger = NULL;
138 struct cinteger_type *current_cinteger = NULL;
139 struct cinteger_type *previous_cinteger = NULL;
140 
141 long                            bit_mask;
142 extern int                      encrypted;
143 extern int                      after_from;
144 extern int                      last_exact;
145 
146 extern char                     temp_directory[];
147 extern char                     data_directory[];
148 char                            csv_buffer[1024];
149 
150 int                             resolved_attribute;
151 
152 /* THE ITERATION VARIABLE USED FOR LOOPS */
153 int                             *loop_integer = NULL;
154 int                             *select_integer = NULL;
155 
156 int                             criterion_value = 0;
157 int                             criterion_type = 0;
158 int                             criterion_negate = FALSE;
159 int                             current_level;
160 int                             execution_level;
161 int                             *ask_integer;
162 int                             new_x;
163 int                             new_y;
164 
165 int                             interrupted = FALSE;
166 char                            string_buffer[2048];
167 char                            argument_buffer[1024];
168 #ifdef GLK
169 extern schanid_t                sound_channel[];
170 extern strid_t                  game_stream;
171 extern winid_t                  mainwin;
172 extern winid_t                  statuswin;
173 extern winid_t                  current_window;
174 
175 extern strid_t                  mainstr;
176 extern strid_t                  statusstr;
177 extern strid_t                  quotestr;
178 extern strid_t                  inputstr;
179 int                             top_of_loop = 0;
180 int                             top_of_select = 0;
181 int                             top_of_while = 0;
182 int                             top_of_iterate = 0;
183 int                             top_of_update = 0;
184 int                             top_of_do_loop = 0;
185 #else
186 extern FILE                     *file;
187 char                            option_buffer[2024];
188 int                             style_stack[100];
189 int                             style_index = 0;
190 long                            top_of_loop = 0;
191 long                            top_of_select = 0;
192 long                            top_of_while = 0;
193 long                            top_of_iterate = 0;
194 long                            top_of_update = 0;
195 long                            top_of_do_loop = 0;
196 
197 #endif
198 
199 #ifdef __NDS__
200 extern int                      bold_mode;
201 extern int                      pre_mode;
202 extern int                      reverse_mode;
203 extern int                      input_mode;
204 extern int                      subheader_mode;
205 extern int                      note_mode;
206 #endif
207 
208 extern char                     user_id[];
209 extern char                     prefix[];
210 extern char                     text_buffer[];
211 extern char                     chunk_buffer[];
212 extern const char               *word[];
213 
214 extern char                     bookmark[];
215 extern char                     file_prompt[];
216 
217 /* CONTAINED IN PARSER.C */
218 extern int                      object_list[4][MAX_OBJECTS];
219 extern int                      list_size[];
220 extern int                      max_size[];
221 
222 /* CONTAINED IN ENCAPSULATE.C */
223 extern int                      quoted[];
224 
225 extern struct object_type       *object[];
226 extern struct integer_type      *integer_table;
227 extern struct integer_type      *integer[];
228 extern struct cinteger_type     *cinteger_table;
229 extern struct attribute_type    *attribute_table;
230 extern struct string_type       *string_table;
231 extern struct string_type       *cstring_table;
232 extern struct function_type     *function_table;
233 extern struct function_type     *executing_function;
234 extern struct command_type      *completion_list;
235 extern struct word_type         *grammar_table;
236 extern struct synonym_type      *synonym_table;
237 extern struct filter_type       *filter_table;
238 
239 extern char                     function_name[];
240 extern char                     temp_buffer[];
241 extern char                     error_buffer[];
242 extern char                     proxy_buffer[];
243 
244 extern char                     default_function[];
245 extern char                     override_[];
246 
247 extern int                      noun[];
248 extern int                      wp;
249 extern int                      start_of_this_command;
250 extern int                      start_of_last_command;
251 extern int                      buffer_index;
252 extern int                      objects;
253 extern int                      integers;
254 extern int                      player;
255 extern int                      oec;
256 extern int                      *object_element_address;
257 extern int                      *object_backup_address;
258 extern int                      walkthru_running;
259 
260 // VALUES FROM LOADER
261 extern int                      value_resolved;
262 
263 extern Common::WriteStream     *transcript;
264 extern char                     margin_string[];
265 
266 char                            integer_buffer[16];
267 char                            called_name[1024];
268 char                            scope_criterion[24];
269 const char                      *output;
270 
terminate(int code)271 void terminate(int code) {
272 	// FREE ANY EXTRA RAM ALLOCATED BY THE CSV PARSER
273 	csv_free(&parser_csv);
274 
275 #ifdef GLK
276 	int index;
277 	event_t         event;
278 
279 	// FLUSH THE GLK WINDOW SO THE ERROR GETS DISPLAYED IMMEDIATELY.
280 	g_vm->glk_select_poll(&event);
281 
282 	/* CLOSE THE SOUND CHANNELS */
283 	for (index = 0; index < 8; index++) {
284 		if (sound_channel[index] != NULL) {
285 			g_vm->glk_schannel_destroy(sound_channel[index]);
286 		}
287 	}
288 
289 	/* CLOSE THE STREAM */
290 	if (game_stream != NULL) {
291 		g_vm->glk_stream_close(game_stream, NULL);
292 	}
293 
294 	g_vm->glk_exit();
295 #else
296 	if (file != NULL)           /* CLOSE THE GAME FILE */
297 		fclose(file);
298 
299 	exit(code);
300 #endif
301 }
302 
build_proxy()303 void build_proxy() {
304 	int             index;
305 
306 	proxy_buffer[0] = 0;
307 
308 	/* LOOP THROUGH ALL THE PARAMETERS OF THE PROXY COMMAND
309 	   AND BUILD THE MOVE TO BE ISSUED ON THE PLAYER'S BEHALF */
310 	for (index = 1; word[index] != NULL; index++) {
311 		strcat(proxy_buffer, text_of_word(index));
312 	}
313 
314 	for (index = 0; index < (int)strlen(proxy_buffer); index++) {
315 		if (proxy_buffer[index] == '~') {
316 			proxy_buffer[index] = '\"';
317 		}
318 	}
319 
320 	//printf("--- proxy buffer = \"%s\"\n", proxy_buffer);
321 }
322 
cb1(void * s,size_t i,void * not_used)323 void cb1(void *s, size_t i, void *not_used) {
324 	struct string_type *resolved_cstring;
325 
326 	//sprintf (temp_buffer, "Trying to set field %d to equal %s^", field_no, (const char *) s);
327 	//write_text(temp_buffer);
328 
329 	sprintf(temp_buffer, "field[%d]", field_no);
330 
331 	if ((resolved_cstring = cstring_resolve(temp_buffer)) != NULL) {
332 		//write_text("Resolved ");
333 		//write_text(temp_buffer);
334 		//write_text("^");
335 		strncpy(resolved_cstring->value, (const char *)s, i);
336 		resolved_cstring->value[i] = 0;
337 		//sprintf(temp_buffer, "Setting field %d to ~%s~^", field_no, (const char *) s);
338 		//write_text(temp_buffer);
339 		// INCREMENT THE FIELD NUMBER SO THE NEXT ONE GETS STORED IN THE RIGHT CONSTANT
340 		field_no++;
341 	} else {
342 		write_text("Can't resolve ");
343 		write_text(temp_buffer);
344 		write_text("^");
345 	}
346 
347 }
348 
cb2(int c,void * not_used)349 void cb2(int c, void *not_used) {
350 	// THE END OF THE RECORD HAS BEEN REACHED, EXPORT THE NUMBER OF FIELDS READ
351 	struct cinteger_type *resolved_cinteger;
352 
353 	if ((resolved_cinteger = cinteger_resolve("field_count")) != NULL) {
354 		resolved_cinteger->value = field_no;
355 	}
356 }
357 
execute(const char * funcname)358 int execute(const char *funcname) {
359 	int             index;
360 	int             counter;
361 	int            *container;
362 
363 	int             object_1,
364 	                object_2;
365 
366 	if (g_vm->shouldQuit())
367 		return 0;
368 
369 	/* THESE VARIABLE KEEP TRACK OF if AND endif COMMANDS TO DECIDE WHETHER
370 	 *THE CURRENT LINE OF CODE SHOULD BE EXECUTED OR NOT */
371 	int             currentLevel = 0;
372 	int             executionLevel = 0;
373 
374 	/* THESE ARE USED AS FILE POINTER OFFSETS TO RETURN TO FIXED
375 	 * POINTS IN THE GAME FILE */
376 #ifdef GLK
377 	int         before_command = 0;
378 #else
379 	long            before_command = 0;
380 #endif
381 
382 
383 	strncpy(called_name, funcname, 1023);
384 
385 	/* GET THE FUNCTION OBJECT BY THE FUNCTION NAME */
386 	resolved_function = function_resolve(called_name);
387 
388 	if (resolved_function == NULL) {
389 		//printf("--- failed to find %s\n", called_name);
390 		return (FALSE);
391 	}
392 
393 #ifdef GLK
394 	push_stack(g_vm->glk_stream_get_position(game_stream));
395 	if (g_vm->shouldQuit())
396 		return FALSE;
397 #else
398 	push_stack(ftell(file));
399 #endif
400 
401 	top_of_loop = 0;
402 	top_of_select = 0;
403 	top_of_while = 0;
404 	top_of_iterate = 0;
405 	top_of_update = 0;
406 	top_of_do_loop = 0;
407 
408 	executing_function = resolved_function;
409 	executing_function->call_count++;
410 
411 	// CREATE ALL THE PASSED ARGUMENTS AS JACL INTEGER CONSTANTS
412 	set_arguments(called_name);
413 
414 	// SET function_name TO THE CORE NAME STORED IN THE FUNCTION OBJECT
415 	// LEAVING called_name TO CONTAIN THE FULL ARGUMENT LIST
416 	strncpy(function_name, executing_function->name, 80);
417 	strncpy(cstring_resolve("function_name")->value, executing_function->name, 80);
418 
419 	//sprintf(temp_buffer, "--- starting to execute %s^", function_name);
420 	//write_text(temp_buffer);
421 
422 	// JUMP TO THE POINT IN THE PROCESSED GAME FILE WHERE THIS FUNCTION STARTS
423 #ifdef GLK
424 	g_vm->glk_stream_set_position(game_stream, executing_function->position, seekmode_Start);
425 	before_command = executing_function->position;
426 	(void)glk_get_bin_line_stream(game_stream, text_buffer, (glui32) 1024);
427 #else
428 	fseek(file, executing_function->position, SEEK_SET);
429 	before_command = executing_function->position;
430 	fgets(text_buffer, 1024, file);
431 #endif
432 
433 	if (encrypted) jacl_decrypt(text_buffer);
434 
435 	while (text_buffer[0] != 125 && !interrupted) {
436 		encapsulate();
437 		if (word[0] == NULL);
438 		else if (!strcmp(word[0], "endwhile")) {
439 			currentLevel--;
440 			if (currentLevel < executionLevel) {
441 				// THIS ENDWHILE COMMAND WAS BEING EXECUTED,
442 				// NOT JUST COUNTED.
443 				if (top_of_while == FALSE) {
444 					sprintf(error_buffer, NO_WHILE, executing_function->name);
445 					log_error(error_buffer, PLUS_STDOUT);
446 				} else {
447 #ifdef GLK
448 					g_vm->glk_stream_set_position(game_stream, top_of_while, seekmode_Start);
449 #else
450 					fseek(file, top_of_while, SEEK_SET);
451 #endif
452 					executionLevel = currentLevel;
453 				}
454 			}
455 		} else if (!strcmp(word[0], "enditerate")) {
456 			currentLevel--;
457 			if (currentLevel < executionLevel) {
458 				// THIS ENDITERATE COMMAND WAS BEING EXECUTED,
459 				// NOT JUST COUNTED.
460 				if (top_of_iterate == FALSE) {
461 					sprintf(error_buffer, NO_ITERATE, executing_function->name);
462 					log_error(error_buffer, PLUS_STDOUT);
463 				} else {
464 #ifdef GLK
465 					g_vm->glk_stream_set_position(game_stream, top_of_iterate, seekmode_Start);
466 #else
467 					fseek(file, top_of_iterate, SEEK_SET);
468 #endif
469 					executionLevel = currentLevel;
470 				}
471 			}
472 		} else if (!strcmp(word[0], "endupdate")) {
473 			currentLevel--;
474 			if (currentLevel < executionLevel) {
475 				// THIS ENDUPDATE COMMAND WAS BEING EXECUTED,
476 				// NOT JUST COUNTED.
477 				if (top_of_update == FALSE) {
478 					sprintf(error_buffer, NO_UPDATE, executing_function->name);
479 					log_error(error_buffer, PLUS_STDOUT);
480 				} else {
481 #ifdef GLK
482 					g_vm->glk_stream_set_position(game_stream, top_of_update, seekmode_Start);
483 #else
484 					fseek(file, top_of_update, SEEK_SET);
485 #endif
486 					executionLevel = currentLevel;
487 				}
488 			}
489 		} else if (!strcmp(word[0], "print") && currentLevel != executionLevel) {
490 			// SKIP THIS BLOCK OF PLAIN TEXT UNTIL IT FINDS A
491 			// LINE THAT STARTS WITH A '.' OR A '}'
492 #ifdef GLK
493 			(void)glk_get_bin_line_stream(game_stream, text_buffer, (glui32) 1024);
494 #else
495 			fgets(text_buffer, 1024, file);
496 #endif
497 
498 			if (encrypted) jacl_decrypt(text_buffer);
499 
500 			while (text_buffer[0] != '.') {
501 				if (text_buffer[0] == '}') {
502 					// HIT THE END OF THE FUNCTION, JUST BAIL OUT
503 					return (exit_function(TRUE));
504 				}
505 
506 				// GET THE NEXT LINE
507 #ifdef GLK
508 				(void)glk_get_bin_line_stream(game_stream, text_buffer, (glui32) 1024);
509 #else
510 				fgets(text_buffer, 1024, file);
511 #endif
512 
513 				if (encrypted) jacl_decrypt(text_buffer);
514 
515 			}
516 		} else if (!strcmp(word[0], "endif")) {
517 			currentLevel--;
518 			if (currentLevel < executionLevel) {
519 				/* THIS SHOULD NEVER HAPPEN */
520 				executionLevel = currentLevel;
521 			}
522 		} else if (!strcmp(word[0], "endall")) {
523 			currentLevel = 0;
524 			executionLevel = 0;
525 		} else if (!strcmp(word[0], "else")) {
526 			if (currentLevel == executionLevel) {
527 				executionLevel--;
528 			} else if (currentLevel == executionLevel + 1) {
529 				executionLevel++;
530 			}
531 		} else if (currentLevel == executionLevel) {
532 			if (!strcmp(word[0], "look")) {
533 				// THIS IS JUST HERE FOR BACKWARDS COMPATIBILITY
534 				object[HERE]->attributes &= ~1L;
535 				look_around();
536 			} else if (!strcmp(word[0], "repeat")) {
537 #ifdef GLK
538 				top_of_do_loop = g_vm->glk_stream_get_position(game_stream);
539 #else
540 				top_of_do_loop = ftell(file);
541 #endif
542 			} else if (!strcmp(word[0], "until")) {
543 				if (word[3] == NULL) {
544 					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
545 					noproprun();
546 					return (exit_function(TRUE));
547 				} else {
548 					if (top_of_do_loop == FALSE) {
549 						sprintf(error_buffer, NO_REPEAT, executing_function->name);
550 						log_error(error_buffer, PLUS_STDOUT);
551 					} else if (!condition()) {
552 #ifdef GLK
553 						g_vm->glk_stream_set_position(game_stream, top_of_do_loop, seekmode_Start);
554 #else
555 						fseek(file, top_of_do_loop, SEEK_SET);
556 #endif
557 					}
558 				}
559 			} else if (!strcmp(word[0], "untilall")) {
560 				if (word[3] == NULL) {
561 					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
562 					noproprun();
563 					return (exit_function(TRUE));
564 				} else {
565 					if (top_of_do_loop == FALSE) {
566 						sprintf(error_buffer, NO_REPEAT, executing_function->name);
567 						log_error(error_buffer, PLUS_STDOUT);
568 					} else if (!and_condition()) {
569 #ifdef GLK
570 						g_vm->glk_stream_set_position(game_stream, top_of_do_loop, seekmode_Start);
571 #else
572 						fseek(file, top_of_do_loop, SEEK_SET);
573 #endif
574 
575 					}
576 				}
577 			} else if (!strcmp(word[0], "iterate")) {
578 				int i;
579 
580 				// A NEW iterate LOOP MEANS STARTING BACK AT THE FIRST FIELD
581 				field_no = 0;
582 
583 				currentLevel++;
584 				/* THIS LOOP COMES BACK TO THE START OF THE LINE CURRENTLY
585 				   EXECUTING, NOT THE LINE AFTER */
586 
587 				top_of_iterate = before_command;
588 
589 				// infile REMAINS OPEN DURING THE ITERATION, ONLY NEEDS
590 				// OPENING THE FIRST TIME
591 				if (infile == NULL) {
592 					strcpy(temp_buffer, data_directory);
593 					strcat(temp_buffer, prefix);
594 					strcat(temp_buffer, "-");
595 					strcat(temp_buffer, text_of_word(1));
596 					strcat(temp_buffer, ".csv");
597 
598 					infile = File::openForReading(temp_buffer);
599 
600 					if (word[2] != NULL && !strcmp(word[2], "skip_header")) {
601 						assert(infile);
602 						infile->read(csv_buffer, 1024);
603 					}
604 				}
605 
606 				if (infile == NULL) {
607 					sprintf(error_buffer, "Failed to open file %s\n", temp_buffer);
608 					log_error(error_buffer, LOG_ONLY);
609 					infile = NULL;
610 				} else {
611 					if (word[1] == NULL) {
612 						/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
613 						noproprun();
614 						return (exit_function(TRUE));
615 					} else {
616 						// IF THERE IS ANOTHER RECORD TO READ FROM THE CSV FILE THEN
617 						// SET THE field[] CONSTANTS AND INCREMENT THE executionLevel
618 						infile->read(csv_buffer, 1024);
619 
620 						if (infile->pos() < infile->size()) {
621 							i = strlen(csv_buffer);
622 							//sprintf (temp_buffer, "Read ~%s~ with %d bytes.^", csv_buffer, i);
623 							//write_text(temp_buffer);
624 							if (csv_parse(&parser_csv, csv_buffer, i, cb1, cb2, (void *) NULL) != (uint)i) {
625 								sprintf(error_buffer, "Error parsing file: %s\n", csv_strerror(csv_error(&parser_csv)));
626 								log_error(error_buffer, PLUS_STDOUT);
627 								delete infile;
628 								infile = NULL;
629 							} else {
630 								// A LINE HAS BEEN SUCCESSFULLY READ, EXECUTE THE CONTENTS OF THE LOOP
631 								executionLevel++;
632 							}
633 						} else {
634 							delete infile;
635 							infile = NULL;
636 						}
637 					}
638 				}
639 			} else if (!strcmp(word[0], "update")) {
640 				int i;
641 
642 				// SET UP THE RECORD LOCKING STRUCTURE, THE ADDRESS OF WHICH
643 				// IS PASSED TO THE fcntl() SYSTEM CALL
644 				write_lck.l_type = F_WRLCK; // SETTING A WRITE LOCK
645 				write_lck.l_whence = 0;     // OFFSET l_start FROM BEGINNING OF FILE
646 				write_lck.l_start = 0LL;
647 				write_lck.l_len = 0LL;      // UNTIL THE END OF THE FILE ADDRESS SPACE
648 
649 				read_lck.l_type = F_RDLCK;  // SETTING A READ LOCK
650 				read_lck.l_whence = 0;      // OFFSET l_start FROM BEGINNING OF FILE
651 				read_lck.l_start = 0LL;
652 				read_lck.l_len = 0LL;       // UNTIL THE END OF THE FILE ADDRESS SPACE
653 
654 				// A NEW iterate LOOP MEANS STARTING BACK AT THE FIRST FIELD
655 				field_no = 0;
656 
657 				currentLevel++;
658 				// THIS LOOP COMES BACK TO THE START OF THE LINE CURRENTLY
659 				// EXECUTING, NOT THE LINE AFTER
660 
661 				top_of_update = before_command;
662 
663 				// infile REMAINS OPEN DURING THE ITERATION, ONLY NEEDS
664 				// OPENING THE FIRST TIME
665 				if (infile == NULL) {
666 					strcpy(in_name, data_directory);
667 					strcat(in_name, prefix);
668 					strcat(in_name, "-");
669 					strcat(in_name, text_of_word(1));
670 					strcat(in_name, ".csv");
671 
672 					infile = File::openForReading(in_name);
673 				}
674 
675 				if (outfile == NULL) {
676 					// OPEN A TEMPORARY OUTPUT FILE TO WRITE THE MODIFICATIONS TO
677 					strcpy(out_name, data_directory);
678 					strcat(out_name, prefix);
679 					strcat(out_name, "-");
680 					strcat(out_name, text_of_word(1));
681 					strcat(out_name, "-");
682 					strcat(out_name, user_id);
683 					strcat(out_name, ".csv");
684 
685 					outfile = File::openForWriting(out_name);
686 				}
687 
688 				if (infile == NULL) {
689 					sprintf(error_buffer, "Failed to open input CSV file ~%s\n", in_name);
690 					log_error(error_buffer, LOG_ONLY);
691 					if (outfile != NULL) {
692 						delete outfile;
693 						outfile = NULL;
694 					}
695 					return (exit_function(TRUE));
696 				} else {
697 					if (outfile == NULL) {
698 						sprintf(error_buffer, "Failed to open output CSV file ~%s~\n", out_name);
699 						log_error(error_buffer, LOG_ONLY);
700 						if (infile != NULL) {
701 							delete infile;
702 							infile = NULL;
703 						}
704 						return (exit_function(TRUE));
705 					} else {
706 #ifdef FILE_CTL
707 						int tryCtr = 0;
708 						write_fd = fileno(outfile);
709 						// ATTEMPT LOCKING OUTPUT FILE MAX_TRY TIMES BEFORE GIVING UP.
710 						while (fcntl(write_fd, F_SETLK, &write_lck) < 0) {
711 							if (errno == EAGAIN || errno == EACCES) {
712 								// THERE MIGHT BE OTHER ERROR CASES IN WHICH
713 								// USERS MIGHT TRY AGAIN
714 								if (++tryCtr < MAX_TRY) {
715 									jacl_sleep(1000);
716 									continue;
717 								}
718 								sprintf(error_buffer, "File busy unable to get lock on output file.\n");
719 								log_error(error_buffer, PLUS_STDOUT);
720 								return (exit_function(TRUE));
721 							}
722 						}
723 
724 						tryCtr = 0;
725 
726 						read_fd = fileno(infile);
727 						// ATTEMPT LOCKING OUTPUT FILE MAX_TRY TIMES BEFORE GIVING UP.
728 						while (fcntl(read_fd, F_SETLK, &read_lck) < 0) {
729 							if (errno == EAGAIN || errno == EACCES) {
730 								// THERE MIGHT BE OTHER ERROR CASES IN WHICH
731 								// USERS MIGHT TRY AGAIN
732 								if (++tryCtr < MAX_TRY) {
733 									jacl_sleep(1000);
734 									continue;
735 								}
736 								sprintf(error_buffer, "File busy unable to get lock on input file.\n");
737 								log_error(error_buffer, PLUS_STDOUT);
738 								return (exit_function(TRUE));
739 							}
740 						}
741 #endif
742 						if (word[1] == NULL) {
743 							/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
744 							noproprun();
745 							return (exit_function(TRUE));
746 						} else {
747 							// IF THERE IS ANOTHER RECORD TO READ FROM THE CSV FILE THEN
748 							// SET THE field[] CONSTANTS AND INCREMENT THE executionLevel
749 							infile->read(csv_buffer, 1024);
750 							if (infile->pos() < infile->size()) {
751 								i = strlen(csv_buffer);
752 								if (csv_parse(&parser_csv, csv_buffer, i, cb1, cb2, (int *) &field_no) != (uint)i) {
753 									sprintf(error_buffer, "Error parsing file: %s\n", csv_strerror(csv_error(&parser_csv)));
754 									log_error(error_buffer, PLUS_STDOUT);
755 									read_lck.l_type = F_UNLCK;  // SETTING A READ LOCK
756 									fcntl(read_fd, F_SETLK, &read_lck);
757 									delete infile;
758 									infile = NULL;
759 								} else {
760 									// A LINE HAS BEEN SUCCESSFULLY READ, EXECUTE THE CONTENTS OF THE LOOP
761 									executionLevel++;
762 								}
763 							} else {
764 								write_lck.l_type = F_UNLCK; // REMOVE THE WRITE LOCK
765 								fcntl(write_fd, F_SETLK, &write_lck);
766 								delete outfile;
767 
768 								read_lck.l_type = F_UNLCK;  // REMOVE THE READ LOCK
769 								fcntl(read_fd, F_SETLK, &read_lck);
770 								delete infile;
771 
772 								rename(out_name, in_name);
773 
774 								outfile = NULL;
775 								infile = NULL;
776 							}
777 						}
778 					}
779 				}
780 			} else if (!strcmp(word[0], "while")) {
781 				currentLevel++;
782 				/* THIS LOOP COMES BACK TO THE START OF THE LINE CURRENTLY
783 				   EXECUTING, NOT THE LINE AFTER */
784 				top_of_while = before_command;
785 				if (word[3] == NULL) {
786 					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
787 					noproprun();
788 					return (exit_function(TRUE));
789 				} else if (condition()) {
790 					executionLevel++;
791 				}
792 			} else if (!strcmp(word[0], "whileall")) {
793 				currentLevel++;
794 				/* THIS LOOP COMES BACK TO THE START OF THE LINE CURRENTLY
795 				   EXECUTING, NOT THE LINE AFTER */
796 				top_of_while = before_command;
797 				if (word[3] == NULL) {
798 					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
799 					noproprun();
800 					return (exit_function(TRUE));
801 				} else if (and_condition()) {
802 					executionLevel++;
803 				}
804 			} else if (!strcmp(word[0], "loop")) {
805 				/* THE LOOP COMMAND LOOPS ONCE FOR EACH DEFINED
806 				 * OBJECT (FOREACH) */
807 #ifdef GLK
808 				top_of_loop = g_vm->glk_stream_get_position(game_stream);
809 #else
810 				top_of_loop = ftell(file);
811 #endif
812 				if (word[1] == NULL) {
813 					// IF NONE IS SUPPLIED DEFAULT TO noun3
814 					loop_integer = &noun[2];
815 				} else {
816 					// STORE THE CONTAINER TO PUT THE CURRENT OBJECT IN
817 					loop_integer = container_resolve(word[1]);
818 
819 					// IF THE SUPPLIED CONTAINER CAN'T BE RESOLVED
820 					// DEFAULT TO noun3
821 					if (loop_integer == NULL)
822 						loop_integer = &noun[2];
823 				}
824 
825 				// SET THE VALUE OF THE LOOP INDEX TO POINT TO THE FIRST OBJECT
826 				*loop_integer = 1;
827 
828 			} else if (!strcmp(word[0], "endloop")) {
829 				if (top_of_loop == FALSE) {
830 					sprintf(error_buffer, NO_LOOP, executing_function->name);
831 					log_error(error_buffer, PLUS_STDOUT);
832 				} else {
833 					*loop_integer += 1;
834 					if (*loop_integer > objects) {
835 						top_of_loop = FALSE;
836 						*loop_integer = 0;
837 					} else {
838 #ifdef GLK
839 						g_vm->glk_stream_set_position(game_stream, top_of_loop, seekmode_Start);
840 #else
841 						fseek(file, top_of_loop, SEEK_SET);
842 #endif
843 					}
844 				}
845 			} else if (!strcmp(word[0], "select")) {
846 				/* THE SELECT COMMAND LOOPS ONCE FOR EACH DEFINED
847 				 * OBJECT THAT MATCHES THE SUPPLIED CRITERION */
848 #ifdef GLK
849 				top_of_select = g_vm->glk_stream_get_position(game_stream);
850 #else
851 				top_of_select = ftell(file);
852 #endif
853 				if (word[1] == NULL) {
854 					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
855 					noproprun();
856 					return (exit_function(TRUE));
857 				} else if (word[2] == NULL) {
858 					// IF NONE IS SUPPLIED DEFAULT TO noun3
859 					select_integer = &noun[2];
860 				} else {
861 					// STORE THE CONTAINER TO PUT THE CURRENT OBJECT IN
862 					select_integer = container_resolve(word[2]);
863 
864 					// IF THE SUPPLIED CONTAINER CAN'T BE RESOLVED
865 					// DEFAULT TO noun3
866 					if (select_integer == NULL) {
867 						select_integer = &noun[2];
868 					}
869 				}
870 
871 				// SET THE VALUE OF THE SELECT INDEX TO ONE BEFORE THE
872 				// FIRST OBJECT. THE NEXT FUNCTION AUTOMATICALLY INCREMENTS
873 				// THE INDEX BY ONE AT THE START OF THE WHILE LOOP.
874 				*select_integer = 0;
875 
876 				if (word[1][0] == '!') {
877 					criterion_negate = TRUE;
878 					strcpy(argument_buffer, &word[1][1]);
879 				} else {
880 					criterion_negate = FALSE;
881 					strcpy(argument_buffer, word[1]);
882 				}
883 
884 				// DETERMINE THE CRITERION FOR SELETION
885 				if (!strcmp(argument_buffer, "*held")
886 				        || !strcmp(argument_buffer, "*here")
887 				        || !strcmp(argument_buffer, "*anywhere")
888 				        || !strcmp(argument_buffer, "*present")) {
889 					criterion_type = CRI_SCOPE;
890 					strncpy(scope_criterion, argument_buffer, 20);
891 				} else if ((criterion_value = attribute_resolve(argument_buffer))) {
892 					criterion_type = CRI_ATTRIBUTE;
893 				} else if ((criterion_value = user_attribute_resolve(argument_buffer))) {
894 					criterion_type = CRI_USER_ATTRIBUTE;
895 				} else {
896 					// USE VALUE OF AS A CATCH ALL IF IT IS NOT AN ATTRIBUTE OR SCOPE
897 					criterion_value = value_of(argument_buffer);
898 
899 					if (value_resolved) {
900 						criterion_type = CRI_PARENT;
901 					} else {
902 						// CAN'T RESOLVE CRITERION
903 						criterion_type = CRI_NONE;
904 					}
905 				}
906 
907 				if (criterion_type != CRI_NONE) {
908 					if (select_next() == FALSE) {
909 						*select_integer = 0;
910 						top_of_select = 0;
911 					}
912 				} else {
913 					*select_integer = 0;
914 				}
915 
916 				if (*select_integer == 0) {
917 					// THERE ARE NO MATCHING OBJECTS SO JUMP TO THE endselect
918 #ifdef GLK
919 					(void)glk_get_bin_line_stream(game_stream, text_buffer, (glui32) 1024);
920 #else
921 					fgets(text_buffer, 1024, file);
922 #endif
923 
924 					if (encrypted) jacl_decrypt(text_buffer);
925 
926 					while (text_buffer[0] != '}') {
927 						encapsulate();
928 						if (word[0] != NULL && !strcmp(word[0], "endselect")) {
929 							break;
930 						}
931 #ifdef GLK
932 						(void)glk_get_bin_line_stream(game_stream, text_buffer, (glui32) 1024);
933 #else
934 						fgets(text_buffer, 1024, file);
935 #endif
936 					}
937 				}
938 			} else if (!strcmp(word[0], "endselect")) {
939 				if (top_of_select == FALSE) {
940 					sprintf(error_buffer, NO_LOOP, executing_function->name);
941 					log_error(error_buffer, PLUS_STDOUT);
942 				} else {
943 					if (select_next(/* select_integer, criterion_type, criterion_value, scope_criterion */)) {
944 #ifdef GLK
945 						g_vm->glk_stream_set_position(game_stream, top_of_select, seekmode_Start);
946 #else
947 						fseek(file, top_of_select, SEEK_SET);
948 #endif
949 					} else {
950 						*select_integer = 0;
951 						top_of_select = 0;
952 					}
953 				}
954 			} else if (!strcmp(word[0], "break")) {
955 				currentLevel++;
956 				executionLevel--;
957 #ifdef GLK
958 			} else if (!strcmp(word[0], "cursor")) {
959 				if (word[2] == NULL) {
960 					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
961 					noproprun(0);
962 					return (exit_function(TRUE));
963 				} else {
964 					if (current_window == statuswin) {
965 						g_vm->glk_window_move_cursor(statuswin, value_of(word[1], TRUE), value_of(word[2], TRUE));
966 					} else {
967 						log_error(BAD_CURSOR, PLUS_STDOUT);
968 					}
969 				}
970 			} else if (!strcmp(word[0], "stop")) {
971 				int channel;
972 
973 				if (SOUND_SUPPORTED->value) {
974 					/* SET THE CHANNEL TO STOP, IF SUPPLIED */
975 					if (word[1] == NULL) {
976 						channel = 0;
977 					} else {
978 						channel = value_of(word[1], TRUE);
979 
980 						/* SANITY CHECK THE CHANNEL SELECTED */
981 						if (channel < 0 || channel > 7) {
982 							channel = 0;
983 						}
984 					}
985 					g_vm->glk_schannel_stop(sound_channel[channel]);
986 				}
987 			} else if (!strcmp(word[0], "volume")) {
988 				int channel, volume;
989 
990 				if (SOUND_SUPPORTED->value) {
991 					/* SET THE CHANNEL TO STOP, IF SUPPLIED */
992 					if (word[2] == NULL) {
993 						channel = 0;
994 					} else {
995 						channel = value_of(word[2], TRUE);
996 
997 						/* SANITY CHECK THE CHANNEL SELECTED */
998 						if (channel < 0 || channel > 7) {
999 							channel = 0;
1000 						}
1001 					}
1002 
1003 					if (word[1] == NULL) {
1004 						/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
1005 						noproprun();
1006 						return (exit_function(TRUE));
1007 					} else {
1008 						volume = value_of(word[1], TRUE);
1009 
1010 						/* SANITY CHECK THE CHANNEL SELECTED */
1011 						if (volume < 0) {
1012 							volume = 0;
1013 						}
1014 
1015 						if (volume > 100) {
1016 							volume = 100;
1017 						}
1018 
1019 						/* STORE A COPY OF THE CURRENT VOLUME FOR ACCESS
1020 						 * FROM JACL CODE */
1021 						sprintf(temp_buffer, "volume[%d]", channel);
1022 						cinteger_resolve(temp_buffer)->value = volume;
1023 
1024 						/* NOW SCALE THE 0-100 VOLUME TO THE 0-65536 EXPECTED
1025 						 * BY Glk */
1026 						volume = volume * 655;
1027 
1028 						/* SET THE VOLUME */
1029 						g_vm->glk_schannel_set_volume(sound_channel[channel], (glui32) volume);
1030 					}
1031 				}
1032 			} else if (!strcmp(word[0], "timer")) {
1033 				if (TIMER_SUPPORTED->value && TIMER_ENABLED->value) {
1034 					if (word[1] == NULL) {
1035 						/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
1036 						noproprun();
1037 						return (exit_function(TRUE));
1038 					} else {
1039 						index = value_of(word[1], TRUE);
1040 						/* DON'T ALLOW NEGATIVE VALUES, BUT NO UPPER LIMIT */
1041 						if (index < 0) index = 0;
1042 
1043 						/* SET THE GLK TIMER */
1044 						g_vm->glk_request_timer_events((glui32) index);
1045 
1046 						/* EXPOSE THE CURRENT VALUE THROUGH A JACL CONSTANT
1047 						   SO THAT GAME CODE CAN READ THE IT */
1048 						cinteger_resolve("timer")->value = index;
1049 					}
1050 				}
1051 			} else if (!strcmp(word[0], "sound")) {
1052 				int channel;
1053 				glui32 repeats;
1054 
1055 				if (SOUND_SUPPORTED->value && SOUND_ENABLED->value) {
1056 					/* SET THE CHANNEL TO USE, IF SUPPLIED */
1057 					if (word[2] == NULL) {
1058 						channel = 0;
1059 					} else {
1060 						channel = value_of(word[2], TRUE);
1061 
1062 						/* SANITY CHECK THE CHANNEL SELECTED */
1063 						if (channel < 0 || channel > 7) {
1064 							channel = 0;
1065 						}
1066 					}
1067 
1068 					/* SET THE NUMBER OF REPEATS, IF SUPPLIED */
1069 					if (word[3] == NULL) {
1070 						repeats = 1;
1071 					} else {
1072 						repeats = value_of(word[3], TRUE);
1073 					}
1074 
1075 					if (word[1] == NULL) {
1076 						/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
1077 						noproprun();
1078 						return (exit_function(TRUE));
1079 					} else {
1080 						if (g_vm->glk_schannel_play_ext(sound_channel[channel], (glui32) value_of(word[1], TRUE), repeats, channel + 1) == 0) {
1081 							/* THE CHANNEL NUMBER IS PASSED SO THAT THE SOUND
1082 							 * NOTIFICATION EVENT CAN USE THE INFORMATION
1083 							 * IT HAS 1 ADDED TO IT SO THAT IT IS A NON-ZERO
1084 							 * NUMBER AND THE EVENT IS ACTIVATED */
1085 							sprintf(error_buffer, "Unable to play sound: %ld", value_of(word[1], FALSE));
1086 							log_error(error_buffer, PLUS_STDERR);
1087 						}
1088 					}
1089 				}
1090 			} else if (!strcmp(word[0], "image")) {
1091 				if (GRAPHICS_SUPPORTED->value && GRAPHICS_ENABLED->value) {
1092 					if (word[1] == NULL) {
1093 						/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
1094 						noproprun();
1095 						return (exit_function(TRUE));
1096 					} else {
1097 						if (!g_vm->loadingSavegame() && g_vm->glk_image_draw(mainwin, (glui32) value_of(word[1], TRUE), imagealign_InlineDown, 0) == 0) {
1098 							sprintf(error_buffer, "Unable to draw image: %ld", value_of(word[1], FALSE));
1099 							log_error(error_buffer, PLUS_STDERR);
1100 						}
1101 					}
1102 				}
1103 			} else if (!strcmp(word[0], "askstring") || !strcmp(word[0], "getstring")) {
1104 				if (word[1] == NULL) {
1105 					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
1106 					noproprun(0);
1107 					return (exit_function(TRUE));
1108 				} else {
1109 					/* GET A POINTER TO THE STRING BEING MODIFIED */
1110 					if ((resolved_string = string_resolve(word[1])) == NULL) {
1111 						unkstrrun(word[1]);
1112 						return (exit_function(TRUE));
1113 					}
1114 
1115 					// PROMPT THE USER TO INPUT A STRING AND STORE IT IN THE
1116 					// RESOLVED VARIABLE
1117 					get_string(resolved_string->value);
1118 				}
1119 
1120 			} else if (!strcmp(word[0], "asknumber") || !strcmp(word[0], "getnumber")) {
1121 				int low, high;
1122 
1123 				int insist = FALSE;
1124 
1125 				/* THE ONLY DIFFERENCE WITH THE getnumber COMMAND IS THAT
1126 				 * IT INSISTS THE PLAYER GIVES A LEGAL RESPONSE */
1127 				if (!strcmp(word[0], "getnumber")) {
1128 					insist = TRUE;
1129 				}
1130 
1131 				if (word[3] != NULL) {
1132 					ask_integer = container_resolve(word[1]);
1133 					if (ask_integer == NULL) {
1134 						unkvarrun(word[1]);
1135 						return (exit_function(TRUE));
1136 					}
1137 
1138 					low = value_of(word[2], TRUE);
1139 					high = value_of(word[3], TRUE);
1140 
1141 					if (high == -1 || low == -1) {
1142 						return (exit_function(TRUE));
1143 					}
1144 
1145 					*ask_integer = get_number(insist, low, high);
1146 				} else {
1147 					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
1148 					noproprun();
1149 					return (exit_function(TRUE));
1150 				}
1151 			} else if (!strcmp(word[0], "getyesorno")) {
1152 				if (word[1] != NULL) {
1153 					ask_integer = container_resolve(word[1]);
1154 					if (ask_integer == NULL) {
1155 						unkvarrun(word[1]);
1156 						return (exit_function(TRUE));
1157 					}
1158 
1159 					*ask_integer = get_yes_or_no();
1160 				} else {
1161 					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
1162 					noproprun();
1163 					return (exit_function(TRUE));
1164 				}
1165 			} else if (!strcmp(word[0], "clear")) {
1166 				if (!walkthru_running) {
1167 					g_vm->glk_window_clear(current_window);
1168 				}
1169 			} else if (!strcmp(word[0], "terminate")) {
1170 				terminate(0);
1171 				return 0;
1172 			} else if (!strcmp(word[0], "more")) {
1173 				if (word[1] == NULL) {
1174 					more("[MORE]");
1175 				} else {
1176 					more(word[1]);
1177 				}
1178 			} else if (!strcmp(word[0], "style")) {
1179 				/* THIS COMMAND IS USED TO OUTPUT ANSI CODES OR SET GLK
1180 				 * STREAM STYLES */
1181 				if (word[1] == NULL) {
1182 					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
1183 					noproprun();
1184 					return (exit_function(TRUE));
1185 				} else {
1186 					if (!strcmp(word[1], "bold")
1187 					        || !strcmp(word[1], "emphasised")) {
1188 						g_vm->glk_set_style(style_Emphasized);
1189 					} else if (!strcmp(word[1], "note")) {
1190 						g_vm->glk_set_style(style_Note);
1191 					} else if (!strcmp(word[1], "input")) {
1192 						g_vm->glk_set_style(style_Input);
1193 					} else if (!strcmp(word[1], "header")) {
1194 						g_vm->glk_set_style(style_Header);
1195 					} else if (!strcmp(word[1], "subheader")) {
1196 						g_vm->glk_set_style(style_Subheader);
1197 					} else if (!strcmp(word[1], "reverse")
1198 					           || !strcmp(word[1], "inverse")) {
1199 						if (current_window == mainwin) {
1200 							g_vm->glk_set_style(style_User2);
1201 						} else {
1202 							g_vm->glk_set_style(style_User1);
1203 						}
1204 					} else if (!strcmp(word[1], "pre")
1205 					           || !strcmp(word[1], "preformatted")) {
1206 						g_vm->glk_set_style(style_Preformatted);
1207 					} else if (!strcmp(word[1], "normal")) {
1208 						g_vm->glk_set_style(style_Normal);
1209 					}
1210 				}
1211 			} else if (!strcmp(word[0], "flush")) {
1212 			} else if (!strcmp(word[0], "hyperlink")) {
1213 				/* OUTPUT LINK TEXT AS PLAIN TEXT UNDER Glk */
1214 				if (word[2] == NULL) {
1215 					noproprun();
1216 					pop_stack();
1217 					return (TRUE);
1218 				} else {
1219 					write_text(text_of_word(1));
1220 				}
1221 #else
1222 #ifdef __NDS__
1223 			} else if (!strcmp(word[0], "flush")) {
1224 				jflush();
1225 			} else if (!strcmp(word[0], "cursor")) {
1226 				if (word[2] == NULL) {
1227 					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
1228 					noproprun(0);
1229 					return (exit_function(TRUE));
1230 				} else {
1231 					printf("\x1b[%d;%dH", (int) value_of(word[1], TRUE), (int) value_of(word[2], TRUE));
1232 				}
1233 			} else if (!strcmp(word[0], "stop")) {
1234 			} else if (!strcmp(word[0], "volume")) {
1235 			} else if (!strcmp(word[0], "timer")) {
1236 			} else if (!strcmp(word[0], "sound")) {
1237 			} else if (!strcmp(word[0], "image")) {
1238 			} else if (!strcmp(word[0], "askstring") || !strcmp(word[0], "getstring")) {
1239 				if (word[1] == NULL) {
1240 					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
1241 					noproprun(0);
1242 					return (exit_function(TRUE));
1243 				} else {
1244 					/* GET A POINTER TO THE STRING BEING MODIFIED */
1245 					if ((resolved_string = string_resolve(word[1])) == NULL) {
1246 						unkstrrun(word[1]);
1247 						return (exit_function(TRUE));
1248 					}
1249 
1250 					// PROMPT THE USER TO INPUT A STRING AND STORE IT IN THE
1251 					// RESOLVED VARIABLE
1252 					get_string(resolved_string->value);
1253 				}
1254 
1255 			} else if (!strcmp(word[0], "asknumber") || !strcmp(word[0], "getnumber")) {
1256 				int low, high;
1257 
1258 				int insist = FALSE;
1259 
1260 				/* THE ONLY DIFFERENCE WITH THE getnumber COMMAND IS THAT
1261 				 * IT INSISTS THE PLAYER GIVES A LEGAL RESPONSE */
1262 				if (!strcmp(word[0], "getnumber")) {
1263 					insist = TRUE;
1264 				}
1265 
1266 				if (word[3] != NULL) {
1267 					ask_integer = container_resolve(word[1]);
1268 					if (ask_integer == NULL) {
1269 						unkvarrun(word[1]);
1270 						return (exit_function(TRUE));
1271 					}
1272 
1273 					low = value_of(word[2], TRUE);
1274 					high = value_of(word[3], TRUE);
1275 
1276 					if (high == -1 || low == -1) {
1277 						return (exit_function(TRUE));
1278 					}
1279 
1280 					*ask_integer = get_number(insist, low, high);
1281 				} else {
1282 					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
1283 					noproprun();
1284 					return (exit_function(TRUE));
1285 				}
1286 			} else if (!strcmp(word[0], "getyesorno")) {
1287 				if (word[1] != NULL) {
1288 					ask_integer = container_resolve(word[1]);
1289 					if (ask_integer == NULL) {
1290 						unkvarrun(word[1]);
1291 						return (exit_function(TRUE));
1292 					}
1293 
1294 					*ask_integer = get_yes_or_no();
1295 				} else {
1296 					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
1297 					noproprun();
1298 					return (exit_function(TRUE));
1299 				}
1300 			} else if (!strcmp(word[0], "clear")) {
1301 				clrscrn();
1302 			} else if (!strcmp(word[0], "terminate")) {
1303 				terminate(0);
1304 				return;
1305 			} else if (!strcmp(word[0], "more")) {
1306 				if (word[1] == NULL) {
1307 					more("[MORE]");
1308 				} else {
1309 					more(word[1]);
1310 				}
1311 			} else if (!strcmp(word[0], "style")) {
1312 				/* THIS COMMAND IS USED TO OUTPUT ANSI CODES OR SET GLK
1313 				 * STREAM STYLES */
1314 				if (word[1] == NULL) {
1315 					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
1316 					noproprun();
1317 					return (exit_function(TRUE));
1318 				} else {
1319 					if (!strcmp(word[1], "bold")
1320 					        || !strcmp(word[1], "emphasised")) {
1321 						printf("\x1b[37;1m");   // SET TO BRIGHT WHITE
1322 						bold_mode = TRUE;
1323 					} else if (!strcmp(word[1], "note")) {
1324 						printf("\x1b[34;1m");   // SET TO BRIGHT BLUE
1325 						note_mode = TRUE;
1326 					} else if (!strcmp(word[1], "input")) {
1327 						printf("\x1b[32;0m");   // SET TO DIM GREEN
1328 						input_mode = TRUE;
1329 					} else if (!strcmp(word[1], "header")) {
1330 						printf("\x1b[37;0m");   // SET TO DIM WHITE
1331 					} else if (!strcmp(word[1], "subheader")) {
1332 						printf("\x1b[33;1m");   // SET TO BRIGHT YELLOW
1333 						subheader_mode = TRUE;
1334 					} else if (!strcmp(word[1], "reverse")
1335 					           || !strcmp(word[1], "inverse")) {
1336 						printf("\x1b[7m");  // SET TO DIM WHITE
1337 						reverse_mode = TRUE;
1338 					} else if (!strcmp(word[1], "pre")
1339 					           || !strcmp(word[1], "preformatted")) {
1340 						printf("\x1b[37;0m");   // SET TO DIM WHITE
1341 						pre_mode = TRUE;
1342 					} else if (!strcmp(word[1], "normal")) {
1343 						printf("\x1b[37;0m");   // SET TO DIM WHITE
1344 						bold_mode = FALSE;
1345 						pre_mode = FALSE;
1346 						reverse_mode = FALSE;
1347 						input_mode = FALSE;
1348 						subheader_mode = FALSE;
1349 						note_mode = FALSE;
1350 					}
1351 				}
1352 			} else if (!strcmp(word[0], "hyperlink")) {
1353 				/* OUTPUT LINK TEXT AS PLAIN TEXT UNDER Glk */
1354 				if (word[2] == NULL) {
1355 					noproprun();
1356 					pop_stack();
1357 					return (TRUE);
1358 				} else {
1359 					write_text(text_of_word(1));
1360 				}
1361 #else
1362 				/* HERE STARTS THE CGIJACL-ONLY FUNCTIONS */
1363 			} else if (!strcmp(word[0], "option")) {
1364 				/* USED TO ADD AN OPTION TO AN HTML LIST */
1365 				if (word[1] == NULL) {
1366 					noproprun();
1367 					pop_stack();
1368 					return (TRUE);
1369 				} else {
1370 					index = value_of(word[1]);
1371 					if (word[2] != NULL) {
1372 						sprintf(option_buffer, "<option value=\"%d\">",
1373 						        index);
1374 					} else {
1375 						object_names(index, temp_buffer);
1376 						sprintf(option_buffer, "<option value=\"%s\">", temp_buffer);
1377 					}
1378 
1379 					write_text(option_buffer);
1380 					list_output(index, TRUE);
1381 					write_text(temp_buffer);
1382 
1383 				}
1384 			} else if (!strcmp(word[0], "getenv")) {
1385 				struct string_type *resolved_setstring = NULL;
1386 
1387 				if (word[2] == NULL) {
1388 					noproprun();
1389 					pop_stack();
1390 					return (TRUE);
1391 				} else {
1392 					// GET A POINTER TO THE STRING BEING MODIFIED
1393 					if ((resolved_setstring = string_resolve(word[1])) == NULL) {
1394 						unkstrrun(word[1]);
1395 						return (exit_function(TRUE));
1396 					}
1397 
1398 					// COPY THE VARIABLE OF THE CGI VARIABLE INTO THE SPECIFIED STRING VARIABLE
1399 					if (getenv(text_of_word(2)) != NULL) {
1400 						strncpy(resolved_setstring->value, getenv(text_of_word(2)), 255);
1401 					} else {
1402 						strncpy(resolved_setstring->value, "", 255);
1403 					}
1404 				}
1405 			} else if (!strcmp(word[0], "button")) {
1406 				/* USED TO CREATE AN HTML BUTTON */
1407 				if (word[1] == NULL) {
1408 					noproprun();
1409 					pop_stack();
1410 					return (TRUE);
1411 				}
1412 				if (word[2] != NULL) {
1413 					sprintf(option_buffer, "<input class=~button~ type=~image~ src=~%s~ name=~verb~ value=~", text_of_word(2));
1414 					strcat(option_buffer, text_of_word(1));
1415 					strcat(option_buffer, "~>");
1416 					write_text(option_buffer);
1417 				} else {
1418 					sprintf(option_buffer, "<input class=~button~ type=~submit~ style=~width: 90px; margin: 5px;~ name=~verb~ value=~%s~>", text_of_word(1));
1419 					write_text(option_buffer);
1420 				}
1421 			} else if (!strcmp(word[0], "hidden")) {
1422 				sprintf(temp_buffer, "<INPUT TYPE=\"hidden\" NAME=\"user_id\" VALUE=\"%s\">", user_id);
1423 				write_text(temp_buffer);
1424 			} else if (!strcmp(word[0], "control")) {
1425 				/* USED TO CREATE A HYPERLINK THAT IS AN IMAGE */
1426 				if (word[2] == NULL) {
1427 					noproprun();
1428 					pop_stack();
1429 					return (TRUE);
1430 				} else {
1431 					sprintf(option_buffer, "<a href=\"?command=%s&amp;user_id=%s\"><img border=0 SRC=\"", text_of_word(2), user_id);
1432 					strcat(option_buffer, text_of_word(1));
1433 					strcat(option_buffer, "\"></a>");
1434 					write_text(option_buffer);
1435 				}
1436 			} else if (!strcmp(word[0], "hyperlink") || !strcmp(word[0], "hyperlinkNE")) {
1437 				string_buffer[0] = 0;
1438 
1439 				/* USED TO CREATE A HYPERLINK WITH SESSION INFORMATION INCLUDED */
1440 				if (word[2] == NULL) {
1441 					noproprun();
1442 					pop_stack();
1443 					return (TRUE);
1444 				} else {
1445 					char *encoded;
1446 
1447 					if (!strcmp(word[0], "hyperlink")) {
1448 						encoded = url_encode(text_of_word(2));
1449 					} else {
1450 						encoded = text_of_word(2);
1451 					}
1452 
1453 					if (word[3] == NULL) {
1454 						sprintf(string_buffer, "<a href=\"?command=%s&amp;user_id=%s\">", encoded, user_id);
1455 						strcat(string_buffer, text_of_word(1));
1456 						strcat(string_buffer, "</a>");
1457 					} else {
1458 						sprintf(string_buffer, "<a class=\"%s\" href=\"?command=", text_of_word(3));
1459 						strcat(string_buffer, encoded);
1460 						sprintf(option_buffer, "&amp;user_id=%s\">%s</a>", user_id, text_of_word(1));
1461 						strcat(string_buffer, option_buffer);
1462 					}
1463 
1464 					if (!strcmp(word[0], "hyperlink")) {
1465 						free(encoded);
1466 					}
1467 
1468 					write_text(string_buffer);
1469 				}
1470 			} else if (!strcmp(word[0], "prompt")) {
1471 				/* USED TO OUTPUT A HTML INPUT CONTROL THAT CONTAINS SESSION INFORMATION */
1472 				if (word[1] != NULL) {
1473 					sprintf(temp_buffer, "<input id=\"JACLCommandPrompt\" type=text name=~command~ onKeyPress=~%s~>\n", word[1]);
1474 					write_text(temp_buffer);
1475 				} else {
1476 					sprintf(temp_buffer, "<input id=\"JACLCommandPrompt\" type=text name=~command~>\n");
1477 					write_text(temp_buffer);
1478 				}
1479 				sprintf(temp_buffer, "<input type=hidden name=\"user_id\" value=\"%s\">", user_id);
1480 				write_text(temp_buffer);
1481 			} else if (!strcmp(word[0], "style")) {
1482 				/* THIS COMMAND IS USED TO OUTPUT ANSI CODES OR SET GLK
1483 				 * STREAM STYLES */
1484 				if (word[1] == NULL) {
1485 					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
1486 					noproprun();
1487 					return (exit_function(TRUE));
1488 				} else {
1489 					if (!strcmp(word[1], "bold")
1490 					        || !strcmp(word[1], "emphasised")) {
1491 						write_text("<b>");
1492 						style_stack[style_index++] = BOLD;
1493 					} else if (!strcmp(word[1], "note")) {
1494 						write_text("<i>");
1495 						style_stack[style_index++] = NOTE;
1496 					} else if (!strcmp(word[1], "input")) {
1497 						write_text("<i>");
1498 						style_stack[style_index++] = INPUT;
1499 					} else if (!strcmp(word[1], "header")) {
1500 						write_text("<h1>");
1501 						style_stack[style_index++] = HEADER;
1502 					} else if (!strcmp(word[1], "subheader")) {
1503 						write_text("<h2>");
1504 						style_stack[style_index++] = SUBHEADER;
1505 					} else if (!strcmp(word[1], "reverse")
1506 					           || !strcmp(word[1], "inverse")) {
1507 						write_text("<b>");
1508 						style_stack[style_index++] = REVERSE;
1509 					} else if (!strcmp(word[1], "pre")
1510 					           || !strcmp(word[1], "preformatted")) {
1511 						write_text("<pre>");
1512 						style_stack[style_index++] = PRE;
1513 					} else if (!strcmp(word[1], "normal")) {
1514 						style_index--;
1515 						for (; style_index > -1; style_index--) {
1516 							switch (style_stack[style_index]) {
1517 							case BOLD:
1518 								write_text("</b>");
1519 								break;
1520 							case NOTE:
1521 								write_text("</i>");
1522 								break;
1523 							case INPUT:
1524 								write_text("</i>");
1525 								break;
1526 							case HEADER:
1527 								write_text("</h1>");
1528 								break;
1529 							case SUBHEADER:
1530 								write_text("</h2>");
1531 								break;
1532 							case REVERSE:
1533 								write_text("</b>");
1534 								break;
1535 							case PRE:
1536 								write_text("</pre>");
1537 								break;
1538 							}
1539 						}
1540 						style_index = 0;
1541 					}
1542 				}
1543 				/* THESE FINAL COMMANDS HAVE NO EFFECT UNDER CGIJACL
1544 				   AND THERE IS NO HARM IN IGNORING THEM */
1545 			} else if (!strcmp(word[0], "flush")) {
1546 			} else if (!strcmp(word[0], "image")) {
1547 				if (word[1] == NULL) {
1548 					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
1549 					noproprun(0);
1550 					return (exit_function(TRUE));
1551 				} else {
1552 					if (word[2] == NULL) {
1553 						sprintf(option_buffer, "<img src=~%s~>", text_of_word(1));
1554 					} else {
1555 						sprintf(option_buffer, "<img class=~%s~ src=~%s~>", text_of_word(2), text_of_word(1));
1556 					}
1557 
1558 					write_text(option_buffer);
1559 				}
1560 			} else if (!strcmp(word[0], "sound")) {
1561 				if (word[2] == NULL) {
1562 					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
1563 					noproprun(0);
1564 					return (exit_function(TRUE));
1565 				} else {
1566 					write_text("<audio autoplay=~autoplay~>");
1567 					if (word[3] == NULL) {
1568 						sprintf(option_buffer, "<source src=~%s~ type=~%s~>", text_of_word(1), text_of_word(2));
1569 						write_text(option_buffer);
1570 					}
1571 					write_text("</audio>");
1572 				}
1573 			} else if (!strcmp(word[0], "cursor")) {
1574 			} else if (!strcmp(word[0], "timer")) {
1575 			} else if (!strcmp(word[0], "volume")) {
1576 			} else if (!strcmp(word[0], "askstring") || !strcmp(word[0], "getstring")) {
1577 			} else if (!strcmp(word[0], "asknumber") || !strcmp(word[0], "getnumber")) {
1578 			} else if (!strcmp(word[0], "getyesorno")) {
1579 			} else if (!strcmp(word[0], "clear")) {
1580 			} else if (!strcmp(word[0], "more")) {
1581 			} else if (!strcmp(word[0], "terminate")) {
1582 #endif
1583 #endif
1584 			} else if (!strcmp(word[0], "proxy")) {
1585 				/* THE PROXY COMMAND ISSUES A MOVE ON THE PLAYER'S BEHALF
1586 				 * ALL STATE MUST BE SAVED SO THE CURRENT MOVE CAN CONTINUE
1587 				 * ONCE THE PROXIED MOVE IS COMPLETE */
1588 #ifdef GLK
1589 				push_stack(g_vm->glk_stream_get_position(game_stream));
1590 #else
1591 				push_stack(ftell(file));
1592 #endif
1593 				push_proxy();
1594 
1595 				build_proxy();
1596 
1597 				// TEXT BUFFER IS THE NORMAL ARRAY FOR HOLDING THE PLAYERS
1598 				// MOVE FOR PROCESSING
1599 				strncpy(text_buffer, proxy_buffer, 1024);
1600 
1601 				command_encapsulate();
1602 
1603 				jacl_truncate();
1604 
1605 				preparse();
1606 
1607 				pop_proxy();
1608 
1609 				pop_stack();
1610 			} else if (!strcmp(word[0], "override")) {
1611 				/* TELLS THE INTERPRETER TO LOOK FOR AN _override FUNCTION
1612 				 * TO EXECUTE IN PLACE OF ANY CODE THAT FOLLOWS THIS LINE.
1613 				 * THIS COMMAND IS USED EXCLUSIVELY IN GLOBAL FUNCTIONS
1614 				 * ASSOCIATED WITH GRAMMAR LINES */
1615 				if (execute(override_) == TRUE) {
1616 					return (exit_function(TRUE));
1617 				} else {
1618 					if (execute(default_function) == TRUE) {
1619 						return (exit_function(TRUE));
1620 					}
1621 				}
1622 			} else if (!strcmp(word[0], "execute") || !strcmp(word[0], "call")) {
1623 				/* CALLS ANOTHER JACL FUNCTION */
1624 				if (word[1] == NULL) {
1625 					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
1626 					noproprun();
1627 					return (exit_function(TRUE));
1628 				} else {
1629 					/* RESOLVE ALL THE TEXT AND STORE IT IN A TEMPORARY BUFFER*/
1630 					string_buffer[0] = 0;
1631 
1632 					for (counter = 1; word[counter] != NULL && counter < MAX_WORDS; counter++) {
1633 						strcat(string_buffer, arg_text_of_word(counter));
1634 					}
1635 
1636 					if (function_resolve(string_buffer) == NULL && !strcmp(word[0], "execute")) {
1637 						char *argstart;
1638 
1639 						/* REMOVE ANY PARAMETERS FROM FUNCTION NAME
1640 						   BEFORE DISPLAYING ERROR MESSAGE */
1641 						argstart = strchr(string_buffer, '<');
1642 						if (argstart != NULL)
1643 							*argstart = 0;
1644 
1645 						sprintf(error_buffer, UNDEFINED_FUNCTION, executing_function->name, string_buffer);
1646 						log_error(error_buffer, PLUS_STDOUT);
1647 					} else {
1648 						execute(string_buffer);
1649 					}
1650 				}
1651 			} else if (!strcmp(word[0], "points")) {
1652 				/* INCREASE THE PLAYER'S SCORE AND POTENTIALLY INFORM THEM OF THE INCREASE */
1653 				if (word[1] != NULL) {
1654 					SCORE->value += value_of(word[1], TRUE);
1655 					if (NOTIFY->value) {
1656 #ifdef GLK
1657 						g_vm->glk_set_style(style_Note);
1658 #else
1659 #ifdef __NDS__
1660 						printf("\x1b[34;1m");   // SET TO BRIGHT BLUE
1661 						note_mode = TRUE;
1662 #else
1663 						write_text("<b><i>");
1664 #endif
1665 #endif
1666 						write_text(cstring_resolve("SCORE_UP")->value);
1667 						sprintf(temp_buffer, "%ld", value_of(word[1], TRUE));
1668 						write_text(temp_buffer);
1669 						if (value_of(word[1], TRUE) == 1) {
1670 							write_text(cstring_resolve("POINT")->value);
1671 						} else {
1672 							write_text(cstring_resolve("POINTS")->value);
1673 						}
1674 #ifdef GLK
1675 						g_vm->glk_set_style(style_Normal);
1676 #else
1677 #ifdef __NDS__
1678 						printf("\x1b[37;0m");   // SET TO DIM WHITE
1679 						note_mode = FALSE;
1680 #else
1681 						write_text("</i></b>");
1682 #endif
1683 #endif
1684 					}
1685 				} else {
1686 					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
1687 					noproprun();
1688 					return (exit_function(TRUE));
1689 				}
1690 			} else if (!strcmp(word[0], "print")) {
1691 				int non_space = FALSE;
1692 
1693 				// DISPLAYS A BLOCK OF PLAIN TEXT UNTIL IT FINDS A
1694 				// LINE THAT STARTS WITH A '.' OR A '}'
1695 #ifdef GLK
1696 				(void)glk_get_bin_line_stream(game_stream, text_buffer, (glui32) 1024);
1697 #else
1698 				fgets(text_buffer, 1024, file);
1699 #endif
1700 
1701 				if (encrypted) jacl_decrypt(text_buffer);
1702 
1703 				while (text_buffer[0] != '.' && text_buffer[0] != '}') {
1704 					index = 0;
1705 					non_space = FALSE;
1706 
1707 					/* REMOVE ANY NEWLINE CHARACTERS */
1708 					while (text_buffer[index] != 0) {
1709 						if (text_buffer[index] == '|' && non_space == FALSE) {
1710 							/* THE BAR CHARACTER IS CHANGED TO A SPACE TO
1711 							 * ALLOW INDENTING OF NEW PARAGRAPHS ETC */
1712 							text_buffer[index] = ' ';
1713 						} else if (text_buffer[index] == '\r') {
1714 							text_buffer[index] = 0;
1715 							break;
1716 						} else if (text_buffer[index] == '\n') {
1717 							text_buffer[index] = 0;
1718 							break;
1719 						} else if (text_buffer[index] != ' ' && text_buffer[index] != '\t') {
1720 							non_space = TRUE;
1721 						}
1722 
1723 						index++;
1724 					}
1725 
1726 					if (text_buffer[0] != 0) {
1727 						// CHECK IF THERE IS THE NEED TO ADD AN
1728 						// IMPLICIT SPACE
1729 						index = strlen(text_buffer);
1730 
1731 						if (text_buffer[index - 1] == '\\') {
1732 							// A BACKSLASH IS USED TO INDICATE AN IMPLICIT
1733 							// SPACE SHOULD NOT BE PRINTED
1734 							text_buffer[index - 1] = 0;
1735 						} else if (text_buffer[index - 1] != '^') {
1736 							// ADD AN IMPLICIT SPACE IF THE PREVIOUS LINE
1737 							// DIDN'T END WITH A CARRIAGE RETURN
1738 							strcat(text_buffer, " ");
1739 						}
1740 
1741 						// OUTPUT THE LINE READ AS PLAIN TEXT
1742 						write_text(text_buffer);
1743 					}
1744 
1745 					// GET THE NEXT LINE
1746 #ifdef GLK
1747 					(void)glk_get_bin_line_stream(game_stream, text_buffer, (glui32) 1024);
1748 #else
1749 					fgets(text_buffer, 1024, file);
1750 #endif
1751 
1752 					if (encrypted) jacl_decrypt(text_buffer);
1753 				}
1754 			} else if (!strcmp(word[0], "mesg")) {
1755 				for (counter = 1; word[counter] != NULL && counter < MAX_WORDS; counter++) {
1756 					warning("%s", text_of_word(counter));
1757 				}
1758 			} else if (!strcmp(word[0], "error")) {
1759 				write_text("ERROR: In function ~");
1760 				write_text(executing_function->name);
1761 				write_text("~, ");
1762 				for (counter = 1; word[counter] != NULL && counter < MAX_WORDS; counter++) {
1763 					write_text(text_of_word(counter));
1764 				}
1765 			} else if (!strcmp(word[0], "debug") && DEBUG->value) {
1766 				write_text("DEBUG: ");
1767 				for (counter = 1; word[counter] != NULL && counter < MAX_WORDS; counter++) {
1768 					write_text(text_of_word(counter));
1769 				}
1770 			} else if (!strcmp(word[0], "write")) {
1771 				for (counter = 1; word[counter] != NULL && counter < MAX_WORDS; counter++) {
1772 					output = text_of_word(counter);
1773 					if (*output != 0) {
1774 						// IF THE OUTPUT ISN'T AN EMPTY STRING, DISPLAY IT
1775 						write_text(output);
1776 					}
1777 				}
1778 			} else if (!strcmp(word[0], "length")) {
1779 				if (word[2] == NULL) {
1780 					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
1781 					noproprun(0);
1782 					return (exit_function(TRUE));
1783 				} else {
1784 					if ((container = container_resolve(word[1])) == NULL) {
1785 						unkvarrun(word[1]);
1786 						return (exit_function(TRUE));
1787 					}
1788 
1789 					*container = strlen(text_of(word[2]));
1790 				}
1791 			} else if (!strcmp(word[0], "savegame")) {
1792 				if (word[1] == NULL) {
1793 					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
1794 					noproprun();
1795 					return (exit_function(TRUE));
1796 				} else {
1797 					if ((container = container_resolve(word[1])) == NULL) {
1798 						unkvarrun(word[1]);
1799 						return (exit_function(TRUE));
1800 					} else {
1801 						*container = save_interaction();
1802 					}
1803 				}
1804 			} else if (!strcmp(word[0], "restoregame")) {
1805 				if (word[1] == NULL) {
1806 					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
1807 					noproprun();
1808 					return (exit_function(TRUE));
1809 				} else {
1810 					if ((container = container_resolve(word[1])) == NULL) {
1811 						unkvarrun(word[1]);
1812 						return (exit_function(TRUE));
1813 					} else {
1814 						*container = restore_interaction();
1815 					}
1816 				}
1817 			} else if (!strcmp(word[0], "restartgame")) {
1818 				restart_game();
1819 				execute("+intro");
1820 				eachturn();
1821 #ifdef GLK
1822 			} else if (!strcmp(word[0], "undomove")) {
1823 				undoing();
1824 			} else if (!strcmp(word[0], "updatestatus")) {
1825 				status_line();
1826 #else
1827 			} else if (!strcmp(word[0], "undomove")) {
1828 			} else if (!strcmp(word[0], "updatestatus")) {
1829 #endif
1830 			} else if (!strcmp(word[0], "split")) {
1831 
1832 				// 0     1       2      3         4
1833 				// split counter source delimiter destination
1834 
1835 				int *split_container;
1836 				char split_buffer[256] = "";
1837 				char container_buffer[256] = "";
1838 				char delimiter[256] = "";
1839 				char *match = NULL;
1840 				struct string_type *resolved_splitstring = NULL;
1841 
1842 				strcpy(split_buffer, text_of_word(2));
1843 				strcpy(delimiter, text_of_word(3));
1844 
1845 				char *source = split_buffer;
1846 
1847 				if (word[4] == NULL) {
1848 					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
1849 					noproprun(0);
1850 					return (exit_function(TRUE));
1851 				} else {
1852 					split_container = container_resolve(var_text_of_word(1));
1853 
1854 					if (split_container == NULL) {
1855 						unkvarrun(var_text_of_word(1));
1856 						return (exit_function(TRUE));
1857 					} else {
1858 						*split_container = 0;
1859 						match = source;     // THERE IS ALWAYS ONE MATCH, EVEN IF
1860 						// NO DELIMETERS ARE FOUND
1861 
1862 						while ((match = strstr(source, delimiter))) {
1863 							*match = 0;
1864 							strcpy(container_buffer, var_text_of_word(4));
1865 							strcat(container_buffer, "[");
1866 							sprintf(integer_buffer, "%d", *split_container);
1867 							strcat(container_buffer, integer_buffer);
1868 							strcat(container_buffer, "]");
1869 
1870 							if ((resolved_splitstring = string_resolve(container_buffer)) == NULL) {
1871 								unkstrrun(var_text_of_word(4));
1872 								return (exit_function(TRUE));
1873 							} else {
1874 								strcpy(resolved_splitstring->value, source);
1875 								source = match + strlen(delimiter);
1876 								(*split_container)++;
1877 							}
1878 						}
1879 						strcpy(container_buffer, var_text_of_word(4));
1880 						strcat(container_buffer, "[");
1881 						sprintf(integer_buffer, "%d", *split_container);
1882 						strcat(container_buffer, integer_buffer);
1883 						strcat(container_buffer, "]");
1884 
1885 						if ((resolved_splitstring = string_resolve(container_buffer)) == NULL) {
1886 							unkstrrun(word[1]);
1887 							return (exit_function(TRUE));
1888 						} else {
1889 							strcpy(resolved_splitstring->value, source);
1890 							(*split_container)++;
1891 						}
1892 					}
1893 				}
1894 			} else if (!strcmp(word[0], "setstring") ||
1895 			           !strcmp(word[0], "addstring")) {
1896 				char setstring_buffer[2048] = "";
1897 				struct string_type *resolved_setstring = NULL;
1898 
1899 				if (word[2] == NULL) {
1900 					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
1901 					noproprun(0);
1902 					return (exit_function(TRUE));
1903 				} else {
1904 					/* GET A POINTER TO THE STRING BEING MODIFIED */
1905 					if ((resolved_setstring = string_resolve(var_text_of_word(1))) == NULL) {
1906 						unkstrrun(word[1]);
1907 						return (exit_function(TRUE));
1908 					}
1909 
1910 					/* RESOLVE ALL THE TEXT AND STORE IT IN A TEMPORARY BUFFER*/
1911 					for (counter = 2; word[counter] != NULL && counter < MAX_WORDS; counter++) {
1912 						strcat(setstring_buffer, text_of_word(counter));
1913 					}
1914 
1915 					/* setstring_buffer IS NOW FILLED, COPY THE UP TO 256 BYTES OF
1916 					 * IT INTO THE STRING */
1917 					if (!strcmp(word[0], "setstring")) {
1918 						strncpy(resolved_setstring->value, setstring_buffer, 255);
1919 					} else {
1920 						/* CALCULATE HOW MUCH SPACE IS LEFT IN THE STRING */
1921 						counter = 255 - strlen(resolved_setstring->value);
1922 						/* THIS IS A addstring COMMAND, SO USE STRNCAT INSTEAD */
1923 						strncat(resolved_setstring->value, setstring_buffer, counter);
1924 					}
1925 				}
1926 			} else if (!strcmp(word[0], "padstring")) {
1927 				char setstring_buffer[2048] = "";
1928 				struct string_type *resolved_setstring = NULL;
1929 				string_buffer[0] = 0;
1930 
1931 				if (word[3] == NULL) {
1932 					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
1933 					noproprun(0);
1934 					return (exit_function(TRUE));
1935 				} else {
1936 					/* GET A POINTER TO THE STRING BEING MODIFIED */
1937 					if ((resolved_setstring = string_resolve(word[1])) == NULL) {
1938 						unkstrrun(word[1]);
1939 						return (exit_function(TRUE));
1940 					}
1941 
1942 					index = value_of(word[3], TRUE);
1943 
1944 					for (counter = 0; counter < index; counter++) {
1945 						strcat(setstring_buffer, text_of_word(2));
1946 					}
1947 
1948 					/* setstring_buffer IS NOW FILLED, COPY THE UP TO 256 BYTES OF
1949 					 * IT INTO THE STRING */
1950 					strncpy(resolved_setstring->value, setstring_buffer, 255);
1951 				}
1952 			} else if (!strcmp(word[0], "return")) {
1953 				/* RETURN FROM THIS FUNCTION, POSSIBLY RETURNING AN INTEGER VALUE */
1954 				if (word[1] == NULL) {
1955 					return (exit_function(TRUE));
1956 				} else {
1957 					index = value_of(word[1], TRUE);
1958 					return (exit_function(index));
1959 				}
1960 			} else if (!strcmp(word[0], "position")) {
1961 				/* MOVE AN OBJECT TO ITS NEW X,Y COORDINATES BASED ON ITS CURRENT VALUES
1962 				 * FOR x, y, bearing, velocity */
1963 				if (word[1] == NULL) {
1964 					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
1965 					noproprun();
1966 					return (exit_function(TRUE));
1967 				} else {
1968 					object_1 = value_of(word[1], TRUE);
1969 
1970 					if (object_1 < 1 || object_1 > objects) {
1971 						badptrrun(word[1], object_1);
1972 						return (exit_function(TRUE));
1973 					} else {
1974 						new_position((double) object[object_1]->X,
1975 						             (double) object[object_1]->Y,
1976 						             (double) object[object_1]->BEARING,
1977 						             (double) object[object_1]->VELOCITY);
1978 
1979 						object[object_1]->X = new_x;
1980 						object[object_1]->Y = new_y;
1981 					}
1982 				}
1983 			} else if (!strcmp(word[0], "bearing")) {
1984 				/* CALCULATE THE BEARING BETWEEN TWO OBJECTS */
1985 				if (word[3] == NULL) {
1986 					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
1987 					noproprun();
1988 					return (exit_function(TRUE));
1989 				} else {
1990 					if ((container = container_resolve(word[1])) == NULL) {
1991 						unkvarrun(word[1]);
1992 						return (exit_function(TRUE));
1993 					}
1994 
1995 					object_1 = value_of(word[2], TRUE);
1996 
1997 					if (object_1 < 1 || object_1 > objects) {
1998 						badptrrun(word[2], object_1);
1999 						return (exit_function(TRUE));
2000 					} else {
2001 						object_2 = value_of(word[3], TRUE);
2002 
2003 						if (object_2 < 1 || object_2 > objects) {
2004 							badptrrun(word[3], object_2);
2005 							return (exit_function(TRUE));
2006 						} else {
2007 							if (container != NULL
2008 							        && object_1 != FALSE
2009 							        && object_2 != FALSE) {
2010 								*container = bearing((double) object[object_1]->X,
2011 								                     (double) object[object_1]->Y,
2012 								                     (double) object[object_2]->X,
2013 								                     (double) object[object_2]->Y);
2014 							}
2015 						}
2016 					}
2017 				}
2018 			} else if (!strcmp(word[0], "distance")) {
2019 				/* CALCULATE THE DISTANCE BETWEEN TWO OBJECTS */
2020 				if (word[3] == NULL) {
2021 					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
2022 					noproprun();
2023 					return (exit_function(TRUE));
2024 				} else {
2025 					container = container_resolve(word[1]);
2026 
2027 					object_1 = value_of(word[2], TRUE);
2028 
2029 					if (object_1 < 1 || object_1 > objects) {
2030 						badptrrun(word[2], object_1);
2031 						return (exit_function(TRUE));
2032 					} else {
2033 						object_2 = value_of(word[3], TRUE);
2034 
2035 						if (object_2 < 1 || object_2 > objects) {
2036 							badptrrun(word[3], object_2);
2037 							return (exit_function(TRUE));
2038 						} else {
2039 							if (container != NULL
2040 							        && object_1 != FALSE
2041 							        && object_2 != FALSE) {
2042 								*container = distance((double)
2043 								                      object[object_1]->X,
2044 								                      (double)
2045 								                      object[object_1]->Y,
2046 								                      (double)
2047 								                      object[object_2]->X,
2048 								                      (double)
2049 								                      object[object_2]->Y);
2050 							}
2051 						}
2052 					}
2053 				}
2054 			} else if (!strcmp(word[0], "dir_to") ||
2055 			           !strcmp(word[0], "npc_to")) {
2056 				/* CALCULATE THE FIRST DIRECTION TO TRAVEL IN GET TO
2057 				 * A SPECIFIED LOCATION */
2058 				if (word[3] == NULL) {
2059 					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
2060 					noproprun();
2061 					return (exit_function(TRUE));
2062 				} else {
2063 					container = container_resolve(word[1]);
2064 
2065 					object_1 = value_of(word[2], TRUE);
2066 
2067 					if (object_1 < 1 || object_1 > objects) {
2068 						badptrrun(word[2], object_1);
2069 						return (exit_function(TRUE));
2070 					} else {
2071 						object_2 = value_of(word[3], TRUE);
2072 
2073 						if (object_2 < 1 || object_2 > objects) {
2074 							badptrrun(word[3], object_2);
2075 							return (exit_function(TRUE));
2076 						} else {
2077 							if (container != NULL
2078 							        && object_1 != FALSE
2079 							        && object_2 != FALSE) {
2080 								if (!strcmp(word[0], "dir_to")) {
2081 									*container = find_route(object_1, object_2, TRUE);
2082 								} else {
2083 									*container = find_route(object_1, object_2, FALSE);
2084 								}
2085 							}
2086 						}
2087 					}
2088 				}
2089 			} else if (!strcmp(word[0], "set")) {
2090 				/* SET THE VALUE OF AN ELEMENT TO A SUPPLIED INTEGER */
2091 				if (word[3] == NULL) {
2092 					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
2093 					noproprun();
2094 					return (exit_function(TRUE));
2095 				} else {
2096 					container = container_resolve(var_text_of_word(1));
2097 
2098 					if (container == NULL) {
2099 						unkvarrun(word[1]);
2100 						return (exit_function(TRUE));
2101 					} else {
2102 						int mark = 2; // SET mark TO POINT TO THE FIRST OPERATOR
2103 						while (word[mark + 1] != NULL) {
2104 							counter = value_of(word[mark + 1], TRUE);
2105 
2106 							if (word[mark][0] == '+')
2107 								*container += counter;
2108 							else if (word[mark][0] == '-')
2109 								*container -= counter;
2110 							else if (word[mark][0] == '*')
2111 								*container = *container * counter;
2112 							else if (word[mark][0] == '%')
2113 								*container = *container % counter;
2114 							else if (word[mark][0] == '/') {
2115 								if (counter == 0) {
2116 									sprintf(error_buffer, DIVIDE_BY_ZERO,
2117 									        executing_function->name);
2118 									log_error(error_buffer, PLUS_STDOUT);
2119 								} else
2120 									*container = *container / counter;
2121 							} else if (!strcmp(word[mark], "locationof")) {
2122 								*container = grand_of(counter, FALSE);
2123 							} else if (!strcmp(word[mark], "grandof")) {
2124 								*container = grand_of(counter, TRUE);
2125 							} else if (word[mark][0] == '=') {
2126 								*container = counter;
2127 							} else {
2128 								sprintf(error_buffer, ILLEGAL_OPERATOR,
2129 								        executing_function->name,
2130 								        word[2]);
2131 								log_error(error_buffer, PLUS_STDOUT);
2132 							}
2133 
2134 							mark += 2;
2135 						}
2136 					}
2137 				}
2138 			} else if (!strcmp(word[0], "ensure")) {
2139 				/* USED TO GIVE OR TAKE AN ATTRIBUTE TO OR FROM AND OBJECT */
2140 				if (word[3] == NULL) {
2141 					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
2142 					noproprun();
2143 					return (exit_function(TRUE));
2144 				} else {
2145 					if ((bit_mask = attribute_resolve(arg_text_of(word[3])))) {
2146 						index = value_of(word[1], TRUE);
2147 						if (index < 1 || index > objects) {
2148 							badptrrun(word[1], index);
2149 							return (exit_function(TRUE));
2150 						} else {
2151 							if (!strcmp(word[2], "has")) {
2152 								object[index]->attributes =
2153 								    object[index]->attributes | bit_mask;
2154 							} else if (!strcmp(word[2], "hasnt")) {
2155 								bit_mask = ~bit_mask;
2156 								object[index]->attributes =
2157 								    object[index]->attributes & bit_mask;
2158 							}
2159 						}
2160 					} else if ((bit_mask = user_attribute_resolve(arg_text_of(word[3])))) {
2161 						index = value_of(word[1], TRUE);
2162 						if (index < 1 || index > objects) {
2163 							badptrrun(word[1], index);
2164 							return (exit_function(TRUE));
2165 						} else {
2166 							if (!strcmp(word[2], "has")) {
2167 								object[index]->user_attributes =
2168 								    object[index]->user_attributes | bit_mask;
2169 							} else if (!strcmp(word[2], "hasnt")) {
2170 								bit_mask = ~bit_mask;
2171 								object[index]->user_attributes =
2172 								    object[index]->user_attributes & bit_mask;
2173 							}
2174 						}
2175 					} else {
2176 						unkattrun(3);
2177 						return (exit_function(TRUE));
2178 					}
2179 				}
2180 			} else if (!strcmp(word[0], "append")) {
2181 				int first = TRUE;
2182 
2183 				if (word[2] == NULL) {
2184 					// NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND
2185 					noproprun();
2186 					return (exit_function(TRUE));
2187 				} else {
2188 					strcpy(temp_buffer, data_directory);
2189 					strcat(temp_buffer, prefix);
2190 					strcat(temp_buffer, "-");
2191 					strcat(temp_buffer, text_of_word(1));
2192 					strcat(temp_buffer, ".csv");
2193 
2194 					outfile = File::openForWriting(temp_buffer);
2195 
2196 					if (outfile == NULL) {
2197 						sprintf(error_buffer, "Failed to open file %s\n", temp_buffer);
2198 						log_error(error_buffer, PLUS_STDOUT);
2199 					} else {
2200 						for (counter = 2; word[counter] != NULL && counter < MAX_WORDS; counter++) {
2201 							output = text_of_word(counter);
2202 							if (*output != 0) {
2203 								if (first == FALSE) {
2204 									outfile->writeByte(',');
2205 								}
2206 								csv_fwrite(outfile, output, (size_t) strlen(output));
2207 								first = FALSE;
2208 							}
2209 						}
2210 
2211 						// TERMINATE THE LINE
2212 						outfile->writeByte('\n');
2213 
2214 						// FLUSH AND CLOSE THE FILE
2215 						outfile->flush();
2216 					}
2217 
2218 					delete outfile;
2219 					outfile = NULL;
2220 				}
2221 			} else if (!strcmp(word[0], "insert")) {
2222 				int first = TRUE;
2223 
2224 				if (word[1] == NULL) {
2225 					// NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND
2226 					noproprun();
2227 					return (exit_function(TRUE));
2228 				} else {
2229 					if (outfile == NULL) {
2230 						log_error("Insert statement not inside an 'update' loop.", PLUS_STDOUT);
2231 					} else {
2232 						for (counter = 1; word[counter] != NULL && counter < MAX_WORDS; counter++) {
2233 							output = text_of_word(counter);
2234 							if (*output != 0) {
2235 								if (first == FALSE) {
2236 									outfile->writeByte(',');
2237 								}
2238 								csv_fwrite(outfile, output, (size_t) strlen(output));
2239 								first = FALSE;
2240 							}
2241 						}
2242 
2243 						// TERMINATE THE LINE
2244 						outfile->writeByte('\n');
2245 					}
2246 				}
2247 			} else if (!strcmp(word[0], "inspect")) {
2248 				if (word[1] == NULL) {
2249 					// NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND
2250 					noproprun();
2251 					return (exit_function(TRUE));
2252 				} else {
2253 					inspect(value_of(word[1], TRUE));
2254 				}
2255 			} else if (!strcmp(word[0], "move")) {
2256 				/* THIS COMMAND IS USED TO MOVE AN OBJECT TO HAVE ANOTHER PARENT
2257 				 * INCLUDING MODIFYING ALL QUANTITY VALUES BASED ON THE OBJECTS MASS */
2258 				if (word[3] == NULL) {
2259 					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
2260 					noproprun();
2261 					return (exit_function(TRUE));
2262 				}
2263 
2264 				index = value_of(word[1], TRUE);
2265 				if (index < 1 || index > objects) {
2266 					badptrrun(word[1], index);
2267 					return (exit_function(TRUE));
2268 				} else {
2269 					object_2 = object[index]->PARENT;
2270 					if (object_2 && !(object[object_2]->attributes & LOCATION)) {
2271 						object[object_2]->QUANTITY += object[index]->MASS;
2272 					}
2273 					object_1 = value_of(word[3], TRUE);
2274 					if (object_1 < 1 || object_1 > objects) {
2275 						badptrrun(word[1], object_1);
2276 						return (exit_function(TRUE));
2277 					} else {
2278 						object[index]->PARENT = object_1;
2279 						if (!(object[object_1]->attributes & LOCATION))
2280 							object[object_1]->QUANTITY -= object[index]->MASS;
2281 					}
2282 				}
2283 			} else if (!strcmp(word[0], "ifstringall")) {
2284 				/* CHECK IF A STRING EQUALS OR CONTAINS ANOTHER STRING */
2285 				currentLevel++;
2286 				if (word[3] == NULL) {
2287 					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
2288 					noproprun(0);
2289 					return (exit_function(TRUE));
2290 				} else if (and_strcondition()) {
2291 					executionLevel++;
2292 				}
2293 			} else if (!strcmp(word[0], "ifstring")) {
2294 				/* CHECK IF A STRING EQUALS OR CONTAINS ANOTHER STRING */
2295 				currentLevel++;
2296 				if (word[3] == NULL) {
2297 					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
2298 					noproprun(0);
2299 					return (exit_function(TRUE));
2300 				} else if (strcondition()) {
2301 					executionLevel++;
2302 				}
2303 			} else if (!strcmp(word[0], "ifexecute")) {
2304 				currentLevel++;
2305 				if (word[1] == NULL) {
2306 					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
2307 					noproprun(0);
2308 					return (exit_function(TRUE));
2309 				} else {
2310 					/* RESOLVE ALL THE TEXT AND STORE IT IN A TEMPORARY BUFFER*/
2311 					string_buffer[0] = 0;
2312 
2313 					for (counter = 1; word[counter] != NULL && counter < MAX_WORDS; counter++) {
2314 						strcat(string_buffer, arg_text_of_word(counter));
2315 					}
2316 
2317 					if (execute(string_buffer)) {
2318 						executionLevel++;
2319 					}
2320 				}
2321 			} else if (!strcmp(word[0], "if")) {
2322 				currentLevel++;
2323 				if (word[3] == NULL) {
2324 					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
2325 					noproprun(0);
2326 					return (exit_function(TRUE));
2327 				} else if (condition()) {
2328 					executionLevel++;
2329 				}
2330 			} else if (!strcmp(word[0], "ifall")) {
2331 				currentLevel++;
2332 				if (word[3] == NULL) {
2333 					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
2334 					noproprun(0);
2335 					return (exit_function(TRUE));
2336 				} else if (and_condition()) {
2337 					executionLevel++;
2338 				}
2339 			} else {
2340 				sprintf(error_buffer, UNKNOWN_COMMAND,
2341 				        executing_function->name, word[0]);
2342 				log_error(error_buffer, PLUS_STDOUT);
2343 			}
2344 		} else if (!strcmp(word[wp], "if")
2345 		           || !strcmp(word[wp], "ifall")
2346 		           || !strcmp(word[wp], "ifstring")
2347 		           || !strcmp(word[wp], "ifstringall")
2348 		           || !strcmp(word[wp], "ifexecute")
2349 		           || !strcmp(word[wp], "iterate")
2350 		           || !strcmp(word[wp], "update")
2351 		           || !strcmp(word[wp], "while")
2352 		           || !strcmp(word[wp], "whileall")) {
2353 			currentLevel++;
2354 		}
2355 
2356 #ifdef GLK
2357 		if (g_vm->shouldQuit())
2358 			return 0;
2359 
2360 		before_command = g_vm->glk_stream_get_position(game_stream);
2361 		(void)glk_get_bin_line_stream(game_stream, text_buffer, (glui32) 1024);
2362 #else
2363 		before_command = ftell(file);
2364 		fgets(text_buffer, 1024, file);
2365 #endif
2366 		if (encrypted) jacl_decrypt(text_buffer);
2367 	};
2368 
2369 	return (exit_function(TRUE));
2370 }
2371 
exit_function(int return_code)2372 int exit_function(int return_code) {
2373 	if (infile != NULL) {
2374 		read_lck.l_type = F_UNLCK;  // SETTING A READ LOCK
2375 		fcntl(read_fd, F_SETLK, &read_lck);
2376 		delete infile;
2377 		infile = NULL;
2378 	}
2379 
2380 	if (outfile != NULL) {
2381 		write_lck.l_type = F_UNLCK; // SETTING A WRITE LOCK
2382 		fcntl(write_fd, F_SETLK, &write_lck);
2383 		delete outfile;
2384 		outfile = NULL;
2385 	}
2386 
2387 	/* POP THE STACK REGARDLESS OF THE RETURN CODE */
2388 	pop_stack();
2389 
2390 	return (return_code);
2391 }
2392 
object_names(int object_index,char * names_buffer)2393 char *object_names(int object_index, char *names_buffer) {
2394 	/* THIS FUNCTION CREATES A LIST OF ALL AN OBJECT'S NAMES.
2395 	   THE escape ARGUMENT INDICATES WHETHER A + SIGN SHOULD BE
2396 	   USED IN PLACE OF A SPACE BETWEEN EACH OF THE NAMES */
2397 	struct name_type *current_name = object[object_index]->first_name;
2398 	names_buffer[0] = 0;
2399 
2400 	while (current_name != NULL) {
2401 		strcat(names_buffer, " ");
2402 		strcat(names_buffer, current_name->name);
2403 		current_name = current_name->next_name;
2404 	}
2405 
2406 	return names_buffer;
2407 }
2408 
distance(double x1,double y1,double x2,double y2)2409 int distance(double x1, double y1, double x2, double y2) {
2410 	/* THIS FUNCTION CALCULATES THE DISTANCE BETWEEN TWO POINTS IN A
2411 	   TWO-DIMENSIONAL PLANE */
2412 	double          delta_x,
2413 	                delta_y;
2414 	double          distance,
2415 	                total;
2416 
2417 	/*
2418 	 * Object two in which quadrant compared to object one? 0 x = opp, y =
2419 	 * ajd + 0 degrees 1 x = adj, y = opp + 90 degrees 2 x = opp, y = ajd
2420 	 * + 180 degrees 3 x = adj, y = opp + 270 degrees
2421 	 */
2422 
2423 	/*
2424 	 * DETERMINE WHICH QUADRANT OBJECT TWO IS IN
2425 	 */
2426 
2427 	if (x2 > x1) {
2428 		/*
2429 		 * OBJECT TWO IS IN 1 OR 2
2430 		 */
2431 		delta_x = x2 - x1;
2432 		if (y2 > y1) {
2433 			delta_y = y2 - y1;
2434 		} else {
2435 			delta_y = y1 - y2;
2436 		}
2437 	} else {
2438 		/*
2439 		 * OBJECT TWO IS IN 3 OR 4
2440 		 */
2441 		delta_x = x1 - x2;
2442 		if (y2 > y1) {
2443 			delta_y = y2 - y1;
2444 		} else {
2445 			delta_y = y1 - y2;
2446 		}
2447 	}
2448 
2449 	delta_y = delta_y * delta_y;
2450 	delta_x = delta_x * delta_x;
2451 
2452 	total = delta_y + delta_x;
2453 
2454 	distance = sqrt(total);
2455 
2456 	return ((int) distance);
2457 }
2458 
new_position(double x1,double y1,double bearing,double velocity)2459 void new_position(double x1, double y1, double bearing, double velocity) {
2460 	double          delta_x,
2461 	                delta_y;
2462 	double          radians;
2463 
2464 	/*
2465 	 * Object two in which quadrant compared to object one? 0 x = opp, y =
2466 	 * ajd + 0 degrees 1 x = adj, y = opp + 90 degrees 2 x = opp, y = ajd
2467 	 * + 180 degrees 3 x = adj, y = opp + 270 degrees
2468 	 */
2469 
2470 	/*
2471 	 * sin finds opp, cos finds adj
2472 	 */
2473 
2474 	if (bearing < 91) {
2475 		radians = bearing * 2.0 * M_PI / 360.;
2476 		delta_x = velocity * sin(radians);
2477 		delta_y = velocity * cos(radians);
2478 		new_x = x1 + delta_x;
2479 		new_y = y1 + delta_y;
2480 	} else if (bearing < 181) {
2481 		bearing -= 90;
2482 		radians = bearing * 2.0 * M_PI / 360.;
2483 		delta_y = velocity * sin(radians);
2484 		delta_x = velocity * cos(radians);
2485 		new_x = x1 + delta_x;
2486 		new_y = y1 - delta_y;
2487 	} else if (bearing < 271) {
2488 		bearing -= 180;
2489 		radians = bearing * 2.0 * M_PI / 360.;
2490 		delta_x = velocity * sin(radians);
2491 		delta_y = velocity * cos(radians);
2492 		new_x = x1 - delta_x;
2493 		new_y = y1 - delta_y;
2494 	} else {
2495 		bearing -= 270;
2496 		radians = bearing * 2.0 * M_PI / 360.;
2497 		delta_y = velocity * sin(radians);
2498 		delta_x = velocity * cos(radians);
2499 		new_x = x1 - delta_x;
2500 		new_y = y1 + delta_y;
2501 	}
2502 }
2503 
bearing(double x1,double y1,double x2,double y2)2504 int bearing(double x1, double y1, double x2, double y2) {
2505 	int             quadrant;
2506 	double          delta_x,
2507 	                delta_y;
2508 	double          oppoadj;
2509 	double          bearing;
2510 
2511 	/*
2512 	 * Object two in which quadrant compared to object one? 0 x = opp, y =
2513 	 * ajd + 0 degrees 1 x = adj, y = opp + 90 degrees 2 x = opp, y = ajd
2514 	 * + 180 degrees 3 x = adj, y = opp + 270 degrees
2515 	 */
2516 
2517 	if (x2 > x1) {
2518 		delta_x = x2 - x1;
2519 		if (y2 > y1) {
2520 			quadrant = 0;
2521 			delta_y = y2 - y1;
2522 			oppoadj = delta_x / delta_y;
2523 		} else {
2524 			quadrant = 1;
2525 			delta_y = y1 - y2;
2526 			oppoadj = delta_y / delta_x;
2527 		}
2528 	} else {
2529 		delta_x = x1 - x2;
2530 		if (y2 > y1) {
2531 			quadrant = 3;
2532 			delta_y = y2 - y1;
2533 			oppoadj = delta_y / delta_x;
2534 		} else {
2535 			quadrant = 2;
2536 			delta_y = y1 - y2;
2537 			oppoadj = delta_x / delta_y;
2538 		}
2539 	}
2540 
2541 	bearing = atan(oppoadj);
2542 	bearing = bearing / (2.0 * M_PI) * 360.;
2543 	bearing = bearing + (90 * quadrant);
2544 
2545 	return ((int) bearing);
2546 }
2547 
set_arguments(const char * function_call)2548 void set_arguments(const char *function_call) {
2549 	/* THIS FUNCTION CREATES AN ARRAY OF JACL INTEGER CONSTANTS TO
2550 	   REPRESENT THE ARGUMENTS PASSED TO A JACL FUNCTION */
2551 	int             index,
2552 	                counter,
2553 	                length;
2554 	int             position = 0; /* STORE THE INDEX OF THE WORD */
2555 	/* SETTING new_word TO FALSE SKIPS THE FIRST */
2556 	/* WORD WHICH IS THE FUNCTION NAME */
2557 	int             new_word = FALSE;
2558 
2559 	char            *arg_ptr[MAX_WORDS];
2560 	int             arg_value[MAX_WORDS];
2561 
2562 	struct integer_type *resolved_integer;
2563 	struct cinteger_type *resolved_cinteger;
2564 
2565 	/* SPLIT UP THE FUNCTION CALL STRING AND EXTRACT THE ARGUMENTS */
2566 	length = strlen(function_call);
2567 
2568 	for (index = 0; index < length; index++) {
2569 		if (function_call[index] == '<') {
2570 			argument_buffer[index] = 0;
2571 			new_word = TRUE;
2572 		} else {
2573 			// COPY THE CHARACTER FROM THE CALLED NAME INTO THE CURRENT
2574 			// ARGUMENT BUFFER
2575 			argument_buffer[index] = function_call[index];
2576 			if (new_word) {
2577 				// THIS IS THE FIRST CHARACTER OF A NEW ARGUMENT SO STORE
2578 				// THE ADDRESS OF THIS CHARACTER IN THE ARGUMENT BUFFER
2579 				arg_ptr[position] = &argument_buffer[index];
2580 				new_word = FALSE;
2581 				if (position < MAX_WORDS)
2582 					position++;
2583 			}
2584 		}
2585 	}
2586 
2587 	argument_buffer[index] = 0;
2588 
2589 	/* CLEAR THE NEXT ARGUMENT POINTER */
2590 	arg_ptr[position] = NULL;
2591 
2592 	/* STORE THE INTEGER VALUE OF EACH ARGUMENT PASSED*/
2593 	index = 0;
2594 	while (arg_ptr[index] != NULL) {
2595 		//arg_value[index] = value_of(arg_ptr[index], TRUE);
2596 
2597 		if ((resolved_integer = integer_resolve(arg_ptr[index])) != NULL) {
2598 			arg_value[index] = resolved_integer->value;
2599 		} else if ((resolved_cinteger = cinteger_resolve(arg_ptr[index])) != NULL) {
2600 			arg_value[index] = resolved_cinteger->value;
2601 		} else if (object_element_resolve(arg_ptr[index])) {
2602 			arg_value[index] = oec;
2603 		} else if ((counter = object_resolve(arg_ptr[index])) != -1) {
2604 			if (counter < 1 || counter > objects) {
2605 				badptrrun(arg_ptr[index], counter);
2606 				pop_stack();
2607 				return;
2608 			} else {
2609 				arg_value[index] = counter;
2610 			}
2611 		} else if (validate(arg_ptr[index])) {
2612 			arg_value[index] = atoi(arg_ptr[index]);
2613 		} else {
2614 			arg_value[index] = -1;
2615 		}
2616 
2617 		index++;
2618 	}
2619 
2620 	/* THE CURRENT ARGUMENTS HAVE ALREADY BEEN PUSHED ONTO THE STACK
2621 	 * AND STORED IF PASSED AS AN ARGUMENT TO THIS FUNCTION SO IT IS
2622 	 * OKAY TO CLEAR THEM AND SET THE NEW VALUES */
2623 	clear_cinteger("arg");
2624 	clear_cstring("string_arg");
2625 
2626 	/* CREATE A CONSTANT FOR EACH ARGUMENT AFTER THE CORE FUNCTION NAME */
2627 	index = 0;
2628 	while (arg_ptr[index] != NULL) {
2629 		if (index == 0) noun[3] = arg_value[index];
2630 		add_cinteger("arg", arg_value[index]);
2631 		//printf("--- %s = %s\n", arg_ptr[index], arg_text_of(arg_ptr[index]));
2632 		add_cstring("string_arg", arg_text_of(arg_ptr[index]));
2633 		index++;
2634 	}
2635 }
2636 
pop_stack()2637 void pop_stack() {
2638 	int index, counter;
2639 
2640 	stack--;
2641 
2642 	clear_cinteger("arg");
2643 	clear_cstring("string_arg");
2644 
2645 	/* RECREATE THE arg ARRAY FOR THIS STACK FRAME */
2646 	for (index = 0; index < backup[stack].argcount; index++) {
2647 		if (index == 0) noun[3] = backup[stack].arguments[0];
2648 		add_cinteger("arg", backup[stack].arguments[index]);
2649 	}
2650 
2651 	/* RECREATE THE string_arg ARRAY FOR THIS STACK FRAME */
2652 	for (index = 0; index < backup[stack].argcount; index++) {
2653 		add_cstring("string_arg", backup[stack].str_arguments[index]);
2654 	}
2655 
2656 	/* RESTORE THE CONTENTS OF text_buffer */
2657 	for (counter = 0; counter < 1024; counter++)
2658 		text_buffer[counter] = backup[stack].text_buffer[counter];
2659 
2660 	/* RESTORE THE CONTENTS OF called_name */
2661 	//for (counter = 0; counter < 256; counter++)
2662 	//called_name[counter] = backup[stack].called_name[counter];
2663 	strncpy(called_name, backup[stack].called_name, 1024);
2664 
2665 	/* RESTORE THE CONTENTS OF scope_criterion */
2666 	//for (counter = 0; counter < 21; counter++)
2667 	//  scope_criterion[counter] = backup[stack].scope_criterion[counter];
2668 	strncpy(scope_criterion, backup[stack].scope_criterion, 20);
2669 
2670 	/* RESTORE THE STORED FUNCTION NAMES THAT ARE USED WHEN AN
2671 	 * 'override' COMMAND IS ENCOUNTERED IN THE CURRENT FUNCTION */
2672 	strncpy(override_, backup[stack]._override, 80);
2673 	strncpy(default_function, backup[stack].default_function, 80);
2674 
2675 	/* RESTORE ALL THE WORD POINTERS */
2676 	for (counter = 0; counter < MAX_WORDS; counter++) {
2677 		word[counter] = backup[stack].word[counter];
2678 		quoted[counter] = backup[stack].quoted[counter];
2679 	}
2680 
2681 	executing_function = backup[stack].function;
2682 
2683 	if (executing_function != NULL) {
2684 		strncpy(function_name, executing_function->name, 80);
2685 		strncpy(cstring_resolve("function_name")->value, executing_function->name, 80);
2686 	}
2687 
2688 	wp = backup[stack].wp;
2689 	top_of_loop = backup[stack].top_of_loop;
2690 	outfile = backup[stack].outfile;
2691 	infile = backup[stack].infile;
2692 	top_of_select = backup[stack].top_of_select;
2693 	top_of_while = backup[stack].top_of_while;
2694 	top_of_iterate = backup[stack].top_of_iterate;
2695 	top_of_update = backup[stack].top_of_update;
2696 	top_of_do_loop = backup[stack].top_of_do_loop;
2697 	criterion_value = backup[stack].criterion_value;
2698 	criterion_type = backup[stack].criterion_type;
2699 	criterion_negate = backup[stack].criterion_negate;
2700 	current_level = backup[stack].current_level;
2701 	execution_level = backup[stack].execution_level;
2702 	loop_integer = backup[stack].loop_integer;
2703 	select_integer = backup[stack].select_integer;
2704 
2705 #ifdef GLK
2706 	g_vm->glk_stream_set_position(game_stream, backup[stack].address, seekmode_Start);
2707 #else
2708 	fseek(file, backup[stack].address, SEEK_SET);
2709 #endif
2710 
2711 }
2712 
push_stack(int32 file_pointer)2713 void push_stack(int32 file_pointer) {
2714 	/* COPY ALL THE CURRENT SYSTEM DATA ONTO THE STACK */
2715 	int index;
2716 	int counter = 0;
2717 
2718 	if (stack == STACK_SIZE) {
2719 		log_error("Stack overflow.", PLUS_STDERR);
2720 		terminate(45);
2721 		return;
2722 	} else {
2723 		backup[stack].infile = infile;
2724 		infile = NULL;
2725 		backup[stack].outfile = outfile;
2726 		outfile = NULL;
2727 		backup[stack].function = executing_function;
2728 		backup[stack].address = file_pointer;
2729 		backup[stack].wp = wp;
2730 		backup[stack].top_of_loop = top_of_loop;
2731 		backup[stack].top_of_select = top_of_select;
2732 		backup[stack].top_of_while = top_of_while;
2733 		backup[stack].top_of_iterate = top_of_iterate;
2734 		backup[stack].top_of_update = top_of_update;
2735 		backup[stack].top_of_do_loop = top_of_do_loop;
2736 		backup[stack].criterion_value = criterion_value;
2737 		backup[stack].criterion_type = criterion_type;
2738 		backup[stack].criterion_negate = criterion_negate;
2739 		backup[stack].current_level = current_level;
2740 		backup[stack].execution_level = execution_level;
2741 		backup[stack].loop_integer = loop_integer;
2742 		backup[stack].select_integer = select_integer;
2743 
2744 		/* MAKE A COPY OF THE CURRENT CONTENTS OF text_buffer */
2745 		for (counter = 0; counter < 1024; counter++)
2746 			backup[stack].text_buffer[counter] = text_buffer[counter];
2747 
2748 		/* MAKE A COPY OF THE CURRENT CONTENTS OF called_name */
2749 		strncpy(backup[stack].called_name, called_name, 1024);
2750 
2751 		// MAKE A COPY OF THE CURRENT CONTENTS OF scope_criterion
2752 		strncpy(backup[stack].scope_criterion, scope_criterion, 20);
2753 
2754 		/* COPY THE STORED FUNCTION NAMES THAT ARE USED WHEN AN
2755 		 * 'override' COMMAND IS ENCOUNTERED IN THE CURRENT FUNCTION */
2756 		strncpy(backup[stack]._override, override_, 80);
2757 		strncpy(backup[stack].default_function, default_function, 80);
2758 
2759 		/* PUSH ALL THE WORD POINTERS ONTO THE STACK */
2760 		for (counter = 0; counter < MAX_WORDS; counter++) {
2761 			backup[stack].word[counter] = word[counter];
2762 			backup[stack].quoted[counter] = quoted[counter];
2763 		}
2764 
2765 		// PUSH ALL THE ARGUMENTS AS INTEGERS ONTO THE STACK
2766 		index = 0;
2767 		current_cinteger = cinteger_table;
2768 
2769 		if (current_cinteger != NULL) {
2770 			do {
2771 				if (!strcmp(current_cinteger->name, "arg")) {
2772 					backup[stack].arguments[index++] = current_cinteger->value;
2773 				}
2774 				current_cinteger = current_cinteger->next_cinteger;
2775 			} while (current_cinteger != NULL);
2776 		}
2777 
2778 		// STORE THE NUMBER OF ARGUMENTS PASSED TO THIS FUNCTION
2779 		// THIS IS THE SAME NUMBER FOR STRINGS AND INTEGERS
2780 		backup[stack].argcount = index;
2781 
2782 		// PUSH ALL THE ARGUMENTS AS STRINGS STRING ONTO THE STACK
2783 		index = 0;
2784 		current_cstring = cstring_table;
2785 
2786 		if (current_cstring != NULL) {
2787 			do {
2788 				if (!strcmp(current_cstring->name, "string_arg")) {
2789 					strncpy(backup[stack].str_arguments[index++], current_cstring->value, 255);
2790 				}
2791 
2792 				current_cstring = current_cstring->next_string;
2793 			} while (current_cstring != NULL);
2794 		}
2795 	}
2796 
2797 	// PUSH ON TO THE NEXT STACK FRAME
2798 	stack++;
2799 }
2800 
pop_proxy()2801 void pop_proxy() {
2802 	int index, counter;
2803 
2804 	proxy_stack--;
2805 
2806 	clear_cinteger("$integer");
2807 	clear_cstring("$string");
2808 	clear_cstring("$word");
2809 
2810 	/* RECREATE THE integer ARRAY FOR THIS STACK FRAME */
2811 	for (index = 0; index < proxy_backup[proxy_stack].integercount; index++) {
2812 		add_cinteger("$integer", proxy_backup[proxy_stack].integer[index]);
2813 	}
2814 
2815 	/* RECREATE THE text ARRAY FOR THIS STACK FRAME */
2816 	for (index = 0; index < proxy_backup[proxy_stack].textcount; index++) {
2817 		add_cstring("$string", proxy_backup[proxy_stack].text[index]);
2818 	}
2819 
2820 	/* RECREATE THE $word ARRAY FOR THIS STACK FRAME */
2821 	for (index = 0; index < proxy_backup[proxy_stack].commandcount; index++) {
2822 		add_cstring("$word", proxy_backup[proxy_stack].command[index]);
2823 	}
2824 
2825 	/* RESTORE ALL THE NOUN POINTERS */
2826 	for (counter = 0; counter < 4; counter++)
2827 		noun[counter] = proxy_backup[proxy_stack].object_pointers[counter];
2828 
2829 	/* PUSH ALL THE RESOLVED OBJECTS ONTO THE STACK */
2830 	for (index = 0; index < 4; index++) {
2831 		list_size[index] = proxy_backup[proxy_stack].list_size[index];
2832 		max_size[index] = proxy_backup[proxy_stack].max_size[index];
2833 		for (counter = 0; counter < max_size[index]; counter++) {
2834 			object_list[index][counter] = proxy_backup[proxy_stack].object_list[index][counter];
2835 		}
2836 	}
2837 
2838 	start_of_this_command = proxy_backup[proxy_stack].start_of_this_command;
2839 	start_of_last_command = proxy_backup[proxy_stack].start_of_last_command;
2840 	after_from = proxy_backup[proxy_stack].after_from;
2841 	last_exact = proxy_backup[proxy_stack].last_exact;
2842 }
2843 
push_proxy()2844 void push_proxy() {
2845 	/* COPY ALL THE CURRENT SYSTEM DATA ONTO THE STACK */
2846 	int index;
2847 	int counter = 0;
2848 	int command = 0;
2849 	int text = 0;
2850 
2851 	current_cinteger = cinteger_table;
2852 	current_cstring = cstring_table;
2853 
2854 	if (proxy_stack == STACK_SIZE) {
2855 		log_error("Stack overflow.", PLUS_STDERR);
2856 		terminate(45);
2857 		return;
2858 	} else {
2859 		proxy_backup[proxy_stack].start_of_this_command = start_of_this_command;
2860 		proxy_backup[proxy_stack].start_of_last_command = start_of_last_command;
2861 
2862 		/* PUSH ALL THE OBJECT POINTERS ONTO THE STACK */
2863 		for (counter = 0; counter < 4; counter++)
2864 			proxy_backup[proxy_stack].object_pointers[counter] = noun[counter];
2865 
2866 		/* PUSH ALL THE RESOLVED OBJECTS ONTO THE STACK */
2867 		for (index = 0; index < 4; index++) {
2868 			for (counter = 0; counter < max_size[index]; counter++) {
2869 				proxy_backup[proxy_stack].object_list[index][counter]
2870 				    =   object_list[index][counter];
2871 			}
2872 			proxy_backup[proxy_stack].list_size[index] = list_size[index];
2873 			proxy_backup[proxy_stack].max_size[index] = max_size[index];
2874 		}
2875 
2876 		/* PUSH ALL THE CURRENT COMMAND INTEGERS ONTO THE STACK */
2877 		counter = 0;
2878 
2879 		if (current_cinteger != NULL) {
2880 			do {
2881 				if (!strcmp(current_cinteger->name, "$integer")) {
2882 					proxy_backup[proxy_stack].integer[counter++] = current_cinteger->value;
2883 				}
2884 				current_cinteger = current_cinteger->next_cinteger;
2885 			} while (current_cinteger != NULL);
2886 		}
2887 
2888 		proxy_backup[proxy_stack].integercount = counter;
2889 
2890 		// PUSH ALL THE TEXT STRING SUPPLIED BY THE CURRENT COMMAND ONTO THE STACK
2891 		text = 0;
2892 		command = 0;
2893 
2894 		if (current_cstring != NULL) {
2895 			do {
2896 				if (!strcmp(current_cstring->name, "$string")) {
2897 					strncpy(proxy_backup[proxy_stack].text[text++], current_cstring->value, 255);
2898 					proxy_backup[proxy_stack].text[counter++][255] = 0;
2899 				} else if (!strcmp(current_cstring->name, "$word")) {
2900 					strncpy(proxy_backup[proxy_stack].command[command++], current_cstring->value, 255);
2901 				}
2902 
2903 				current_cstring = current_cstring->next_string;
2904 			} while (current_cstring != NULL);
2905 		}
2906 
2907 		proxy_backup[proxy_stack].textcount = counter;
2908 		proxy_backup[proxy_stack].commandcount = command;
2909 		proxy_backup[proxy_stack].after_from = after_from;
2910 		proxy_backup[proxy_stack].last_exact = last_exact;
2911 	}
2912 
2913 	// PUSH ON TO THE NEXT STACK FRAME
2914 	proxy_stack++;
2915 }
2916 
condition()2917 int condition() {
2918 	/* COMPARE GROUPS OF TWO ELEMENTS. RETURN TRUE IF ANY ONE GROUP OF
2919 	 * ELEMENTS COMPARE 'TRUE' */
2920 	int             first;
2921 
2922 	first = 1;
2923 
2924 	while (word[first + 2] != NULL && ((first + 2) < MAX_WORDS)) {
2925 		if (logic_test(first))
2926 			return (TRUE);
2927 		else
2928 			first = first + 3;
2929 	}
2930 	return (FALSE);
2931 }
2932 
and_condition()2933 int and_condition() {
2934 	/* COMPARE GROUPS OF TWO ELEMENTS. RETURN FALSE IF ANY ONE GROUP OF
2935 	 * ELEMENTS COMPARE 'FALSE' */
2936 	int             first;
2937 
2938 	first = 1;
2939 
2940 	while (word[first + 2] != NULL && ((first + 2) < MAX_WORDS)) {
2941 		if (logic_test(first) == FALSE)
2942 			return (FALSE);
2943 		else
2944 			first = first + 3;
2945 	}
2946 	return (TRUE);
2947 }
2948 
logic_test(int first)2949 int logic_test(int first) {
2950 	long            index,
2951 	                compare;
2952 
2953 	resolved_attribute = FALSE;
2954 
2955 	index = value_of(word[first], TRUE);
2956 	compare = value_of(word[first + 2], TRUE);
2957 
2958 	if (!strcmp(word[first + 1], "=") || !strcmp(word[first + 1], "==")) {
2959 		if (index == compare)
2960 			return (TRUE);
2961 		else
2962 			return (FALSE);
2963 	} else if (!strcmp(word[first + 1], ">")) {
2964 		if (index > compare)
2965 			return (TRUE);
2966 		else
2967 			return (FALSE);
2968 	} else if (!strcmp(word[first + 1], "<")) {
2969 		if (index < compare)
2970 			return (TRUE);
2971 		else
2972 			return (FALSE);
2973 	} else if (!strcmp(word[first + 1], "is")) {
2974 		if (index < 1 || index > objects) {
2975 			unkobjrun(first);
2976 			return (FALSE);
2977 		} else
2978 			return (scope(index, word[first + 2]));
2979 	} else if (!strcmp(word[first + 1], "isnt")) {
2980 		if (index < 1 || index > objects) {
2981 			unkobjrun(first);
2982 			return (FALSE);
2983 		} else
2984 			return (!scope(index, word[first + 2]));
2985 	} else if (!strcmp(word[first + 1], "has"))
2986 		if (index < 1 || index > objects) {
2987 			unkobjrun(first);
2988 			return (FALSE);
2989 		} else {
2990 			if (resolved_attribute == SYSTEM_ATTRIBUTE) {
2991 				return (object[index]->attributes & compare);
2992 			} else {
2993 				return (object[index]->user_attributes & compare);
2994 			}
2995 		}
2996 	else if (!strcmp(word[first + 1], "hasnt"))
2997 		if (index < 1 || index > objects) {
2998 			unkobjrun(first);
2999 			return (FALSE);
3000 		} else {
3001 			if (resolved_attribute == SYSTEM_ATTRIBUTE) {
3002 				return (!(object[index]->attributes & compare));
3003 			} else {
3004 				return (!(object[index]->user_attributes & compare));
3005 			}
3006 		}
3007 	else if (!strcmp(word[first + 1], "!=")
3008 	         || !strcmp(word[first + 1], "<>")) {
3009 		if (index != compare)
3010 			return (TRUE);
3011 		else
3012 			return (FALSE);
3013 	} else if (!strcmp(word[first + 1], ">=")
3014 	           || !strcmp(word[first + 1], "=>")) {
3015 		if (index >= compare)
3016 			return (TRUE);
3017 		else
3018 			return (FALSE);
3019 	} else if (!strcmp(word[first + 1], "<=")
3020 	           || !strcmp(word[first + 1], "=<")) {
3021 		if (index <= compare)
3022 			return (TRUE);
3023 		else
3024 			return (FALSE);
3025 	} else if (!strcmp(word[first + 1], "grandof")) {
3026 		/* GRANDOF SAYS THAT AN OBJECT IS THE EVENTUAL PARENT OF ANOTHER OBJECT, NOT
3027 		 * NECESSARILY IMMEDIATE */
3028 		if (index < 1 || index > objects) {
3029 			unkobjrun(first);
3030 			return (FALSE);
3031 		} else {
3032 			if (compare < 1 || compare > objects) {
3033 				unkobjrun(first + 2);
3034 				return (FALSE);
3035 			} else {
3036 				if (parent_of(index, compare, UNRESTRICT))
3037 					return (TRUE);
3038 				else
3039 					return (FALSE);
3040 			}
3041 		}
3042 	} else if (!strcmp(word[first + 1], "!grandof")) {
3043 		if (index < 1 || index > objects) {
3044 			unkobjrun(first);
3045 			return (FALSE);
3046 		} else {
3047 			if (compare < 1 || compare > objects) {
3048 				unkobjrun(first + 2);
3049 				return (FALSE);
3050 			} else {
3051 				if (parent_of(index, compare, UNRESTRICT))
3052 					return (FALSE);
3053 				else
3054 					return (TRUE);
3055 			}
3056 		}
3057 	} else {
3058 		sprintf(error_buffer,
3059 		        "ERROR: In function \"%s\", illegal operator \"%s\".^",
3060 		        executing_function->name, word[2]);
3061 		write_text(error_buffer);
3062 		return (FALSE);
3063 	}
3064 }
3065 
strcondition()3066 int strcondition() {
3067 	int             first;
3068 
3069 	first = 1;
3070 
3071 	while (word[first + 2] != NULL && ((first + 2) < MAX_WORDS)) {
3072 		if (str_test(first))
3073 			return (TRUE);
3074 		else
3075 			first = first + 3;
3076 	}
3077 	return (FALSE);
3078 }
3079 
and_strcondition()3080 int and_strcondition() {
3081 	int             first;
3082 
3083 	first = 1;
3084 
3085 	while (word[first + 2] != NULL && ((first + 2) < MAX_WORDS)) {
3086 		if (str_test(first) == FALSE)
3087 			return (FALSE);
3088 		else
3089 			first = first + 3;
3090 	}
3091 	return (TRUE);
3092 }
3093 
str_test(int first)3094 int str_test(int first) {
3095 	const char  *index;
3096 	const char  *compare;
3097 
3098 	// GET THE TWO STRING VALUES TO COMPARE
3099 
3100 	index = arg_text_of_word(first);
3101 	compare = arg_text_of_word(first + 2);
3102 
3103 	if (!strcmp(word[first + 1], "==") || !strcmp(word[first + 1], "=")) {
3104 		if (!scumm_stricmp(index, compare)) {
3105 			return (TRUE);
3106 		} else {
3107 			return (FALSE);
3108 		}
3109 	} else if (!strcmp(word[first + 1], "!contains")) {
3110 		if (strcasestr(index, compare))
3111 			return (FALSE);
3112 		else
3113 			return (TRUE);
3114 	} else if (!strcmp(word[first + 1], "contains")) {
3115 		if (strcasestr(index, compare))
3116 			return (TRUE);
3117 		else
3118 			return (FALSE);
3119 	} else if (!strcmp(word[first + 1], "<>") || !strcmp(word[first + 1], "!=")) {
3120 		if (scumm_stricmp(index, compare))
3121 			return (TRUE);
3122 		else
3123 			return (FALSE);
3124 	} else if (!strcmp(word[first + 1], "==C") || !strcmp(word[first + 1], "=C")) {
3125 		if (!strcmp(index, compare)) {
3126 			return (TRUE);
3127 		} else {
3128 			return (FALSE);
3129 		}
3130 	} else if (!strcmp(word[first + 1], "!containsC")) {
3131 		if (strstr(index, compare))
3132 			return (FALSE);
3133 		else
3134 			return (TRUE);
3135 	} else if (!strcmp(word[first + 1], "containsC")) {
3136 		if (strstr(index, compare))
3137 			return (TRUE);
3138 		else
3139 			return (FALSE);
3140 	} else if (!strcmp(word[first + 1], "<>C") || !strcmp(word[first + 1], "!=C")) {
3141 		if (strcmp(index, compare))
3142 			return (TRUE);
3143 		else
3144 			return (FALSE);
3145 	} else {
3146 		sprintf(error_buffer,
3147 		        "ERROR: In function \"%s\", illegal operator \"%s\".^",
3148 		        executing_function->name, word[2]);
3149 		write_text(error_buffer);
3150 		return (FALSE);
3151 	}
3152 }
3153 
add_cinteger(const char * name,int value)3154 void add_cinteger(const char *name, int value) {
3155 	/* THIS FUNCTION ADDS A NEW JACL CONSTANT TO THE LIST */
3156 
3157 	if ((new_cinteger = (struct cinteger_type *)
3158 	                    malloc(sizeof(struct cinteger_type))) == NULL)
3159 		outofmem();
3160 	else {
3161 		if (cinteger_table == NULL) {
3162 			cinteger_table = new_cinteger;
3163 		} else {
3164 			/* FIND LAST CONSTANT IN LIST */
3165 			current_cinteger = cinteger_table;
3166 			while (current_cinteger->next_cinteger != NULL) {
3167 				current_cinteger = current_cinteger->next_cinteger;
3168 			}
3169 			current_cinteger->next_cinteger = new_cinteger;
3170 		}
3171 		strncpy(new_cinteger->name, name, 40);
3172 		new_cinteger->name[40] = 0;
3173 		new_cinteger->value = value;
3174 		new_cinteger->next_cinteger = NULL;
3175 	}
3176 }
3177 
clear_cinteger(const char * name)3178 void clear_cinteger(const char *name) {
3179 	/* FREE CONSTANTS THAT HAVE SUPPLIED NAME*/
3180 
3181 	//printf("--- clear integer %s\n", name);
3182 	if (cinteger_table != NULL) {
3183 		current_cinteger = cinteger_table;
3184 		previous_cinteger = cinteger_table;
3185 		while (current_cinteger != NULL) {
3186 			//sprintf(temp_buffer, "--- checking integer %s^", current_cinteger->name);
3187 			//write_text(temp_buffer);
3188 			if (!strcmp(current_cinteger->name, name)) {
3189 				//sprintf(temp_buffer, "--- found integer %s^", name);
3190 				//write_text(temp_buffer);
3191 				/* FREE THIS CONSTANT */
3192 				if (previous_cinteger == current_cinteger) {
3193 					// THE INTEGER BEING CLEARED IS THE FIRST INTEGER IN THE LIST
3194 					cinteger_table = current_cinteger->next_cinteger;
3195 					previous_cinteger = current_cinteger->next_cinteger;
3196 					free(current_cinteger);
3197 					current_cinteger = previous_cinteger;
3198 				} else {
3199 					previous_cinteger->next_cinteger = current_cinteger->next_cinteger;
3200 					free(current_cinteger);
3201 					current_cinteger = previous_cinteger->next_cinteger;
3202 				}
3203 			} else {
3204 				previous_cinteger = current_cinteger;
3205 				current_cinteger = current_cinteger->next_cinteger;
3206 			}
3207 		}
3208 	}
3209 	//printf("--- leaving clear integer\n");
3210 }
3211 
add_cstring(const char * name,const char * value)3212 void add_cstring(const char *name, const char *value) {
3213 	/* ADD A STRING CONSTANT WITH THE SUPPLIED NAME AND VALUE */
3214 
3215 	if ((new_string = (struct string_type *)
3216 	                  malloc(sizeof(struct string_type))) == NULL)
3217 		outofmem();
3218 	else {
3219 		if (cstring_table == NULL) {
3220 			cstring_table = new_string;
3221 		} else {
3222 			/* FIND LAST STRING IN LIST */
3223 			current_cstring = cstring_table;
3224 			while (current_cstring->next_string != NULL) {
3225 				current_cstring = current_cstring->next_string;
3226 			}
3227 			current_cstring->next_string = new_string;
3228 		}
3229 		strncpy(new_string->name, name, 40);
3230 		new_string->name[40] = 0;
3231 		strncpy(new_string->value, value, 255);
3232 		new_string->value[255] = 0;
3233 		new_string->next_string = NULL;
3234 	}
3235 }
3236 
clear_cstring(const char * name)3237 void clear_cstring(const char *name) {
3238 	/* FREE CONSTANTS THAT HAVE SUPPLIED NAME*/
3239 	if (cstring_table != NULL) {
3240 		current_cstring = cstring_table;
3241 		previous_cstring = cstring_table;
3242 		while (current_cstring != NULL) {
3243 			if (!strcmp(current_cstring->name, name)) {
3244 				/* FREE THIS STRING */
3245 				if (previous_cstring == current_cstring) {
3246 					cstring_table = current_cstring->next_string;
3247 					previous_cstring = current_cstring->next_string;
3248 					free(current_cstring);
3249 					current_cstring = previous_cstring;
3250 				} else {
3251 					previous_cstring->next_string = current_cstring->next_string;
3252 					free(current_cstring);
3253 					current_cstring = previous_cstring->next_string;
3254 				}
3255 			} else {
3256 				previous_cstring = current_cstring;
3257 				current_cstring = current_cstring->next_string;
3258 			}
3259 		}
3260 	}
3261 }
3262 
inspect(int object_num)3263 void inspect(int object_num)  {
3264 	// THIS FUNCTION DISPLAYS THE STATE OF A JACL OBJECT FOR DEBUGGING
3265 
3266 	int index, attribute_value;
3267 
3268 	struct attribute_type *pointer = attribute_table;
3269 
3270 	if (object_num < 1 || object_num > objects) {
3271 		badptrrun(word[1], object_num);
3272 		return;
3273 	}
3274 
3275 	write_text("label: ");
3276 	write_text(object[object_num]->label);
3277 
3278 	if (object[object_num]->attributes & LOCATION) {
3279 		// OUTPUT ALL THE ATTRIBUTES WITH LOCATION ATTRIBUTE TEXT
3280 		write_text("^has location attributes: ");
3281 		index = 0;
3282 		attribute_value = 1;
3283 		while (location_attributes[index] != NULL) {
3284 			if (object[object_num]->attributes & attribute_value) {
3285 				write_text(location_attributes[index]);
3286 			}
3287 			index++;
3288 			attribute_value *= 2;
3289 		}
3290 	} else {
3291 		// OUTPUT ALL THE ATTRIBUTES WITH OBJECT ATTRIBUTE TEXT
3292 		write_text("^has object attributes: ");
3293 		index = 0;
3294 		attribute_value = 1;
3295 		while (object_attributes[index] != NULL) {
3296 			if (object[object_num]->attributes & attribute_value) {
3297 				write_text(object_attributes[index]);
3298 			}
3299 			index++;
3300 			attribute_value *= 2;
3301 		}
3302 
3303 		write_text("^has user attributes: ");
3304 		attribute_value = 1;
3305 	}
3306 
3307 	if (pointer != NULL) {
3308 		// THERE ARE USER ATTRIBUTES, SO CHECK IF THIS OBJECT OR LOCATION
3309 		// HAS ANY OF THEM
3310 		do {
3311 			if (object[object_num]->user_attributes & pointer->value) {
3312 				write_text(pointer->name);
3313 				write_text(" ");
3314 			}
3315 
3316 			pointer = pointer->next_attribute;
3317 		} while (pointer != NULL);
3318 	}
3319 
3320 	write_text("^");
3321 
3322 	index = 0;
3323 	if (object[object_num]->attributes & LOCATION) {
3324 		while (location_elements[index] != NULL) {
3325 			if (index < 12) {
3326 				if (object[object_num]->integer[index] < 1 || object[object_num]->integer[index] > objects) {
3327 					sprintf(temp_buffer, "%s: nowhere (%d)^", location_elements[index], object[object_num]->integer[index]);
3328 				} else {
3329 					sprintf(temp_buffer, "%s: %s (%d)^", location_elements[index], object[object[object_num]->integer[index]]->label, object[object_num]->integer[index]);
3330 				}
3331 			} else {
3332 				sprintf(temp_buffer, "%s: %d^", location_elements[index], object[object_num]->integer[index]);
3333 			}
3334 			write_text(temp_buffer);
3335 			index++;
3336 		}
3337 	} else {
3338 		while (object_elements[index] != NULL) {
3339 			if (index == 0) {
3340 				sprintf(temp_buffer, "%s: %s (%d)^", object_elements[index], object[object[object_num]->integer[index]]->label, object[object_num]->integer[index]);
3341 			} else {
3342 				sprintf(temp_buffer, "%s: %d^", object_elements[index], object[object_num]->integer[index]);
3343 			}
3344 			write_text(temp_buffer);
3345 			index++;
3346 		}
3347 	}
3348 }
3349 
grand_of(int child,int objs_only)3350 int grand_of(int child, int objs_only) {
3351 	/* THIS FUNCTION WILL CLIMB THE OBJECT TREE STARTING AT 'CHILD' UNTIL
3352 	 * A 'PARENT' IS REACHED */
3353 
3354 	/* objs_only ARGUMENT TELLS FUNCTION TO IGNORE OBJECT IF IT IS IN A
3355 	 * LOCATION */
3356 
3357 	int             parent;
3358 
3359 	if (object[child]->PARENT != NOWHERE) {
3360 		/* STORE THE CHILDS PARENT OBJECT */
3361 		parent = object[child]->PARENT;
3362 
3363 		if (object[parent]->attributes & LOCATION) {
3364 			if (objs_only) {
3365 				/* THE CHILDS PARENT IS LOCATION AND SEARCH IS RESTRICTED TO
3366 				 * OBJECTS */
3367 				return (child);
3368 			} else {
3369 				return (parent);
3370 			}
3371 		} else {
3372 			/* KEEP LOOKING UP THE TREE UNTIL THE CHILD HAS NO
3373 			 * PARENT */
3374 			return (grand_of(parent, objs_only));
3375 		}
3376 	} else {
3377 		/* THE SPECIFIED OBJECT HAS NO PARENT */
3378 		return (child);
3379 	}
3380 }
3381 
select_next()3382 int select_next() {
3383 	while (++*select_integer <= objects) {
3384 		switch (criterion_type) {
3385 		case CRI_ATTRIBUTE:
3386 			if (object[*select_integer]->attributes & criterion_value) {
3387 				if (!criterion_negate) {
3388 					return TRUE;
3389 				}
3390 			} else {
3391 				if (criterion_negate) {
3392 					return TRUE;
3393 				}
3394 			}
3395 			break;
3396 		case CRI_USER_ATTRIBUTE:
3397 			if (object[*select_integer]->user_attributes & criterion_value) {
3398 				if (!criterion_negate) {
3399 					return TRUE;
3400 				}
3401 			} else {
3402 				if (criterion_negate) {
3403 					return TRUE;
3404 				}
3405 			}
3406 			break;
3407 		case CRI_PARENT:
3408 			if (object[*select_integer]->PARENT == criterion_value) {
3409 				if (!criterion_negate) {
3410 					return TRUE;
3411 				}
3412 			} else {
3413 				if (criterion_negate) {
3414 					return TRUE;
3415 				}
3416 			}
3417 			break;
3418 		case CRI_SCOPE:
3419 			if (scope(*select_integer, scope_criterion)) {
3420 				if (!criterion_negate) {
3421 					return TRUE;
3422 				}
3423 			} else {
3424 				if (criterion_negate) {
3425 					return TRUE;
3426 				}
3427 			}
3428 			break;
3429 		default:
3430 			break;
3431 		}
3432 	}
3433 
3434 	return (FALSE);
3435 }
3436 
3437 /* Converts an integer value to its hex character*/
to_hex(char code)3438 char to_hex(char code) {
3439 	static char hex[] = "0123456789abcdef";
3440 	return hex[code & 15];
3441 }
3442 
3443 /* Returns a url-encoded version of str */
3444 /* IMPORTANT: be sure to free() the returned string after use */
url_encode(char * str)3445 char *url_encode(char *str) {
3446 	char *pstr = str, *buf = (char *)malloc(strlen(str) * 3 + 1), *pbuf = buf;
3447 	while (*pstr) {
3448 		if (Common::isAlnum(*pstr) || *pstr == '-' || *pstr == '_' || *pstr == '.' || *pstr == '~')
3449 			*pbuf++ = *pstr;
3450 		else if (*pstr == ' ')
3451 			*pbuf++ = '+';
3452 		else
3453 			*pbuf++ = '%', *pbuf++ = to_hex(*pstr >> 4), *pbuf++ = to_hex(*pstr & 15);
3454 		pstr++;
3455 	}
3456 	*pbuf = '\0';
3457 	return buf;
3458 }
3459 
3460 } // End of namespace JACL
3461 } // End of namespace Glk
3462