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/agt/agility.h"
24 #include "common/str.h"
25 
26 namespace Glk {
27 namespace AGT {
28 
29 /* ------------------------------------------------------------------- */
30 /* Purity flag initialization                                          */
31 /*    Logically, these belong in agtdata.c, but I wanted to keep them  */
32 /*    near the CFG reading routines.                                   */
33 /* ------------------------------------------------------------------- */
34 /*   The following are AGT 'purity' flags; they turn off features of */
35 /* my interpreter that are not fully consistent with the original AGT */
36 /* and so could break some games. Some of these are trivial improvements; */
37 /* some are more radical and should be used with caution. Several are */
38 /* only useful if a game was designed with them in mind. */
39 /*   In all cases, setting the flag to 1 more closely follows the */
40 /* behavior of the original interpreters */
41 /* WARNING: Many of these haven't been tested extenstivly in the non-default
42    state. */
43 
44 
45 rbool PURE_ANSWER = 0; /* For ME questions, requires that AND-separated
46 			  answers be in the same order in the player's
47 			 answer as they are in the game file. According
48 			 to the AGT documentation, AND should ignore
49 			 the order, but the original AGT interpreters
50 			 (at least the one I've tested) don't conform
51 			 to this. */
52 
53 rbool PURE_TIME = 1; /* Set to 0 causes time to always be increased
54 				by delta_time rather than by a random amount
55 			between 0 and delta_time. Only really of any use
56 			to a game author who wanted to write a game
57 			explicitly for AGiliTy. */
58 
59 /* rbool PURE_BOLD=1; Set to 0 causes the backslash to toggle bold on and
60 			off for all versions of AGT, not just 1.8x.
61 			I can think of no reason to do this unless
62 			you are an AGT author who wants to use the 1.8x
63 			bold feature with the Master's Edition compiler. */
64 
65 rbool PURE_AND = 1; /* increment the turn counter for each noun in a
66 				chain of <noun> AND <noun> AND ... If 0, the turn
67 			 counter will only be incremented by one in such a case.
68 			 (need to do something about metacommands, as well...) */
69 
70 rbool PURE_METAVERB = 1; /* If set, ANY and AFTER commands are run even
71 			   if you type in a metaverb (SAVE, RESTORE,...
72 			   that is, any verb that doesn't cause time to
73 			   pass). Verb specific metacommands are _always_
74 			   run. */
75 
76 rbool PURE_ROOMTITLE = 1;  /* If 0, the interpreter will print out room
77 			   names before room descriptions even for
78 			   pre-ME games */
79 
80 rbool PURE_SYN = 0; /* Treats synonyms as nouns when parsing: that is, they
81 			   must show up only as the last word and they have the
82 			   same priority as noun matches during disambiguation.
83 			  If this is 0, then synonyms can appear anywhere in
84 			   the name the player types in but are still
85 			   disambiguated as nouns. */
86 
87 rbool PURE_NOUN = 0; /* _Requires_ a noun to end a word. This is only
88 			 imperfectly supported: if there are no other
89 			 possible matches the parser will take the adjective-
90 			 only one anyhow. Frankly, I can't think of any reason
91 			 to set this to 1, but it's included for completeness
92 			 sake (and for any AGT Purists out there :-) ) */
93 
94 rbool PURE_ADJ = 1; /* Picks noun/syn-matches over pure adj matches
95 			   when disambiguating. This is redundant if PURE_NOUN=1
96 			   since in that case pure adjective matches will
97 			   be rejected anyhow. */
98 
99 rbool PURE_DUMMY = 0;  /* If set, the player can running dummy verbs
100 			 in the game by typing 'dummy_verb3'; otherwise,
101 			 this will produce an error message */
102 
103 rbool PURE_SUBNAME = 0; /* If set, the player can run subroutines from
104 			  the parse line by typing (e.g.) 'subroutine4'
105 			  (yes, the original AGT interpreters actually
106 			  allow this). If cleared, this cheat isn't
107 			  available */
108 rbool PURE_PROSUB = 0;  /* If clear, then $you$ substitutions are done
109 			everywhere $$ substitutions are, even in
110 			messages written by the game author.
111 			If set, these substitutions are only made
112 			in internal game messages */
113 
114 rbool PURE_HOSTILE = 1;  /* =0 Will allow you to leave a room with a hostile
115 			   creature if you go back the way you came */
116 rbool PURE_ALL = 1;      /* =0 will cause the parser to expand ALL */
117 rbool PURE_DISAMBIG = 1; /* =0 will cause intelligent disambiguation */
118 rbool PURE_GETHOSTILE = 1;  /* =0 will prevent the player from picking things
119 				up in a room with a hostile creature */
120 
121 rbool PURE_OBJ_DESC = 1;    /* =0 prevents [providing light] messages
122 			  from being shown */
123 
124 rbool PURE_ERROR = 0;    /* =1 means no GAME ERROR messages will be printed
125 			 out */
126 
127 rbool PURE_SIZE = 1;  /* =0 eliminates size/weight limits on how many
128 			  things the player can wear or carry. (But it's
129 			  still impossible to pick things up that are
130 			  in themselves larger than the player's capacity) */
131 
132 rbool PURE_GRAMMAR = 1; /* =0 prints error messages if the player uses a
133 			built in verb with an extra object.
134 			(e.g. YELL CHAIR). Otherwise, the extra object
135 			will just be ignored. */
136 
137 rbool PURE_SYSMSG = 1; /* =0 causes AGiliTy to always use the default
138 			   messages even if the game file has its own
139 			   standard error messages. */
140 
141 rbool PURE_AFTER = 1; /* =0 causes LOOK and other end-of-turn events
142 			   to happen *before* AFTER commands run.  */
143 
144 rbool PURE_PROPER = 1; /* Don't automatically treat creatures as proper nouns */
145 
146 rbool TWO_CYCLE = 0; /* AGT 1.83-style two-cycle metacommand execution. */
147 rbool FORCE_VERSION = 0; /* Load even if the version is wrong. */
148 
149 
150 /*-------------------------------------------------------------------------*/
151 /* .CFG reading routines                                                   */
152 /*-------------------------------------------------------------------------*/
153 
154 /* The main interpreter handles configuration in this order:
155    1) Global configuration file
156    2) First pass through game specific CFG to get the settings for
157 	   SLASH_BOLD and IBM_CHAR which we need to know _before_ reading
158 	   in the game.
159    3) Read in the game.
160    4) Main pass through game specific CFG. Doing it here ensures that
161 	  its settings will override those in the gamefile.
162   Secondary programs (such as agt2agx) usually only call this once, for
163   the game specific configuration file.
164 	  */
165 
166 #define opt(s) (strcasecmp(optstr[0],s)==0)
167 
cfg_option(int optnum,char * optstr[],rbool lastpass)168 static void cfg_option(int optnum, char *optstr[], rbool lastpass)
169 /* This is passed each of the options; it is responsible for parsing
170    them or passing them on to the platform-specific option handler
171    agt_option() */
172 /* lastpass is set if it is the last pass through this configuration
173    file; it is false only on the first pass through the game specific
174    configuration file during the run of the main interpreter */
175 {
176 	rbool setflag;
177 
178 	if (optnum == 0 || optstr[0] == NULL) return;
179 
180 	if (strncasecmp(optstr[0], "no_", 3) == 0) {
181 		optstr[0] += 3;
182 		setflag = 0;
183 	} else setflag = 1;
184 
185 	if (opt("slash_bold")) bold_mode = setflag;
186 	else if (!lastpass) {
187 		/* On the first pass, we ignore all but a few options */
188 		agil_option(optnum, optstr, setflag, lastpass);
189 		return;
190 	} else if (opt("irun")) irun_mode = setflag;
191 	else if (opt("block_hostile")) PURE_HOSTILE = setflag;
192 	else if (opt("get_hostile")) PURE_GETHOSTILE = setflag;
193 	else if (opt("debug")) {
194 		if (!agx_file && aver <= AGTME10) debug_mode = setflag;
195 		if (setflag == 0) debug_mode = 0; /* Can always turn debugging support off */
196 	} else if (opt("pure_answer")) PURE_ANSWER = setflag;
197 	else if (opt("const_time")) PURE_TIME = !setflag;
198 	else if (opt("fix_multinoun")) PURE_AND = !setflag;
199 	else if (opt("fix_metaverb")) PURE_METAVERB = !setflag;
200 	else if (opt("roomtitle")) PURE_ROOMTITLE = !setflag;
201 	else if (opt("pure_synonym")) PURE_SYN = setflag;
202 	else if (opt("adj_noun")) PURE_ADJ = !setflag;
203 	else if (opt("pure_dummy")) PURE_DUMMY = setflag;
204 	else if (opt("pure_subroutine")) PURE_SUBNAME = setflag;
205 	else if (opt("pronoun_subs")) PURE_PROSUB = !setflag;
206 	else if (opt("verbose")) verboseflag = setflag;
207 	else if (opt("fixed_font")) font_status = 1 + !setflag;
208 	else if (opt("alt_any")) mars_fix = setflag;
209 	else if (opt("smart_disambig")) PURE_DISAMBIG = !setflag;
210 	else if (opt("expand_all")) PURE_ALL = !setflag;
211 	else if (opt("object_notes")) PURE_OBJ_DESC = setflag;
212 	else if (opt("error")) PURE_ERROR = !setflag;
213 	else if (opt("ignore_size")) PURE_SIZE = !setflag;
214 	else if (opt("check_grammar")) PURE_GRAMMAR = !setflag;
215 	else if (opt("default_errors")) PURE_SYSMSG = !setflag;
216 	else if (opt("pure_after")) PURE_AFTER = !setflag;
217 	else if (opt("proper_creature")) PURE_PROPER = !setflag;
218 	else agil_option(optnum, optstr, setflag, lastpass);
219 }
220 
221 #undef opt
222 
223 /* Returns false if it there are too many tokens on the line */
parse_config_line(char * buff,rbool lastpass)224 rbool parse_config_line(char *buff, rbool lastpass) {
225 	char *opt[50], *p;
226 	int optc;
227 
228 	optc = 0;
229 	opt[0] = NULL;
230 	for (p = buff; *p; p++) {
231 		if (isspace(*p)) {  /* Whitespace */
232 			if (opt[optc] != NULL) { /*... which means this is the first whitespace */
233 				if (optc == 50) return 0; /* Too many */
234 				opt[++optc] = NULL;
235 			}
236 			*p = 0;
237 		} else  /* No whitespace */
238 			if (opt[optc] == NULL) /* ...this is the first non-whitespace */
239 				opt[optc] = p;
240 	}
241 	if (opt[optc] != NULL) opt[++optc] = NULL;
242 	cfg_option(optc, opt, lastpass);
243 	return 1;
244 }
245 
246 
247 /* For the meaning of lastpass, see comments to cfg_option() above */
read_config(genfile cfgfile,rbool lastpass)248 void read_config(genfile cfgfile, rbool lastpass) {
249 	char buff[100];
250 
251 	if (!filevalid(cfgfile, fCFG)) return;
252 
253 	while (readln(cfgfile, buff, 99)) {
254 		if (buff[0] == '#') continue; /* Comments */
255 		/* Now we parse the line into words, with opt[] pointing at the words
256 		   and optc counting how many there are. */
257 		if (!parse_config_line(buff, lastpass))
258 			rprintf("Too many tokens on configuration line.\n");
259 	}
260 	readclose(cfgfile);
261 }
262 
263 
264 
265 /*-------------------------------------------------------------------------*/
266 /* Read OPT file                                                          */
267 /*  (most of these routines used to be in agil.c)                          */
268 /*-------------------------------------------------------------------------*/
269 
270 /* .OPT reading routines */
271 /* I've put the comments on the format here because they don't really
272    belong anywhere else. (Maybe in agility.h, but I don't want to further
273    clutter that already quite cluttered file with something as peripheral
274    as this) */
275 /* OPT file format:  the .OPT file consists of 14 bytes. They are:
276    0  Screen size(0=43/50 rows, 1=25 rows)
277    1  Status line(1=top, 0=none, -1=bottom)
278    2  Unknown, always seems to be 0
279    3  Put box around status line?
280    4  Sound on?
281    5  Menus on?
282    6  Fixed input line?
283    7  Print transcript?
284    8  Height of menus (3, 4, 5, 6, 7, or 8)
285    9  Unknown, always seems to be 0
286    10-13  Color scheme: output/status/input/menu, specified in DOS attribute
287 	  format (Bbbbffff,  B=blink, b=backround, f=foreground,
288 	  MSB of foreground specifies intensity ("bold") ). */
289 /* The interpreter ignores almost all of this. */
290 
read_opt(fc_type fc)291 void read_opt(fc_type fc) {
292 	const char *errstr;
293 	genfile optfile;
294 
295 	have_opt = 0;
296 	optfile = openbin(fc, fOPT, NULL, 0);
297 	if (filevalid(optfile, fOPT)) {
298 		if (!binread(optfile, opt_data, 14, 1, &errstr))
299 			fatal("Invalid OPT file.");
300 		have_opt = 1;
301 		readclose(optfile);
302 	}
303 }
304 
305 
306 /*-------------------------------------------------------------------------*/
307 /* Read and process TTL                                                    */
308 /*  (most of these routines used to be in agil.c)                          */
309 /*-------------------------------------------------------------------------*/
310 
311 /* Shades of Gray uses a custom interpreter that prints out the names
312    of the authors as the program loads. */
313 /* Normally I wouldn't bother with this, but Shades of Gray is probably
314    the best known of all AGT games */
315 
316 #define SOGCREDIT 7
317 static const char *sogauthor[SOGCREDIT] = {
318 	"Mark \"Sam\" Baker",
319 	"Steve \"Aaargh\" Bauman",
320 	"Belisana \"The\" Magnificent",
321 	"Mike \"of Locksley\" Laskey",
322 	"Judith \"Teela Brown\" Pintar",
323 	"Hercules \"The Loyal\" SysOp",
324 	"Cindy \"Nearly Amelia\" Yans"
325 };
326 
check_dollar(char * s)327 static rbool check_dollar(char *s)
328 /* Determines if s consists of an empty string with a single dollar sign
329  and possibly whitespace */
330 {
331 	rbool dfound;
332 	dfound = 0;
333 	for (; *s != 0; s++)
334 		if (*s == '$' && !dfound) dfound = 1;
335 		else if (!rspace(*s)) return 0;
336 	return dfound;
337 }
338 
read_ttl(fc_type fc)339 descr_line *read_ttl(fc_type fc) {
340 	genfile ttlfile;
341 	int i, j, height;
342 	descr_line *buff;
343 
344 	ttlfile = openfile(fc, fTTL, NULL, 0);
345 	/* "Warning: Could not open title file '%s'." */
346 	if (!filevalid(ttlfile, fTTL)) return NULL;
347 	build_fixchar();
348 
349 	buff = (descr_line *)rmalloc(sizeof(descr_line));
350 	i = 0;
351 	while (NULL != (buff[i] = readln(ttlfile, NULL, 0))) {
352 		if (strncmp(buff[i], "END OF FILE", 11) == 0) break;
353 		else if (aver >= AGT18 && aver <= AGT18MAX && check_dollar(buff[i]))
354 			statusmode = 4;
355 		else {
356 			for (j = 0; buff[i][j] != 0; j++)
357 				buff[i][j] = fixchar[(uchar)buff[i][j]];
358 			/* Advance i and set the next pointer to NULL */
359 			buff = (descr_line *)rrealloc(buff, sizeof(descr_line) * (++i + 1));
360 			buff[i] = NULL;
361 		}
362 		rfree(buff[i]);
363 	}
364 	readclose(ttlfile);
365 
366 	rfree(buff[i]);
367 	while (buff[i] == NULL || strlen(buff[i]) <= 1) { /* Discard 'empty' lines */
368 		if (i == 0) break;
369 		rfree(buff[i]);
370 		i--;
371 	}
372 	height = i;
373 
374 	if (aver == AGTCOS && ver == 4 && height >= 17) /* SOGGY */
375 		for (i = 0; i < SOGCREDIT; i++)
376 			if (strlen(sogauthor[i]) + 9 + i < strlen(buff[i + 7]))
377 				memcpy(buff[i + 7] + 9 + i, sogauthor[i], strlen(sogauthor[i]));
378 
379 	return buff;
380 }
381 
free_ttl(descr_line * title)382 void free_ttl(descr_line *title) {
383 	int i;
384 	if (title == NULL) return;
385 	for (i = 0; title[i] != NULL; i++)
386 		rfree(title[i]);
387 	rfree(title);
388 }
389 
390 
391 /*-------------------------------------------------------------------------*/
392 /* Read and convert VOC                                                    */
393 /*  (most of these routines used to be in agil.c)                          */
394 /*-------------------------------------------------------------------------*/
395 
396 
397 static const char *newvoc[] = { "1 Menu", "1 Restart", "1 Undo" };
398 static int newindex = 0; /* Points into newvoc */
399 
add_verbrec(const char * verb_line,rbool addnew)400 void add_verbrec(const char *verb_line, rbool addnew) {
401 	char s[3];
402 	Common::String verbStr(verb_line);
403 
404 	while (!verbStr.empty() && rspace(verbStr.firstChar()))
405 		verbStr.deleteChar(0);
406 
407 	if (verbStr.empty() || verbStr.hasPrefix("!"))
408 		return;		/* Comment or empty line */
409 
410 	/* The following guarentees automatic initialization of the verbrec structures */
411 	if (!addnew)
412 		while (newindex < 3 && strcasecmp(verbStr.c_str() + 2, newvoc[newindex] + 2) > 0)
413 			add_verbrec(newvoc[newindex++], 1);
414 
415 	verbinfo = (verbentry_rec *)rrealloc(verbinfo, (vm_size + 1) * sizeof(verbentry_rec));
416 
417 	s[0] = verbStr.firstChar();
418 	s[1] = 0;
419 	verbinfo[vm_size].objnum = strtol(s, NULL, 10) - 1;
420 
421 	verbStr.deleteChar(0);
422 	verbStr.deleteChar(0);
423 
424 	verbinfo[vm_size].verb = verbinfo[vm_size].prep = 0;
425 
426 	uint idx = 0;
427 	while (idx < verbStr.size()) {
428 		while (idx < verbStr.size() && !rspace(verbStr[idx]))
429 			++idx;
430 		if (idx < verbStr.size()) {
431 			verbStr.setChar('\0', idx);
432 			++idx;
433 		}
434 
435 		verbinfo[vm_size].verb = search_dict(verbStr.c_str());
436 		if (verbinfo[vm_size].verb == -1) {
437 			verbinfo[vm_size].verb = 0;
438 			return;
439 		}
440 		if (idx < verbStr.size()) {
441 			verbinfo[vm_size].prep = search_dict(verbStr.c_str() + idx);
442 			if (verbinfo[vm_size].prep == -1)
443 				verbinfo[vm_size].prep = 0;
444 		}
445 	}
446 
447 	vm_size++;
448 }
449 
init_verbrec(void)450 void init_verbrec(void)
451 /* Need to insert special verbs into verbinfo */
452 /* Fill in vnum field */
453 /* UNDO, RESTART, MENU  */
454 {
455 	verbinfo = NULL;
456 	vm_size = 0;
457 	newindex = 0;
458 	if (freeze_mode) newindex = 1;  /* Don't include MENU option if we can't
459 				   use it. */
460 }
461 
finish_verbrec(void)462 void finish_verbrec(void) {
463 	for (; newindex < 3; newindex++) add_verbrec(newvoc[newindex], 1);
464 }
465 
466 
read_voc(fc_type fc)467 void read_voc(fc_type fc) {
468 	char linbuf[80];
469 	genfile vocfile;
470 
471 	init_verbrec();
472 	vocfile = openfile(fc, fVOC, NULL, 0);
473 	if (filevalid(vocfile, fVOC)) { /* Vocabulary file exists */
474 		while (readln(vocfile, linbuf, 79))
475 			add_verbrec(linbuf, 0);
476 		readclose(vocfile);
477 		finish_verbrec();
478 	}
479 }
480 
481 
482 
483 
484 /*-------------------------------------------------------------------------*/
485 /* Read INS file                                                           */
486 /*  (most of these routines used to be in agil.c)                          */
487 /*-------------------------------------------------------------------------*/
488 
489 
490 static genfile insfile = BAD_TEXTFILE;
491 static char *ins_buff;
492 
493 static descr_line *ins_descr = NULL;
494 static int ins_line;  /* Current instruction line */
495 
496 
497 /* Return 1 on success, 0 on failure */
open_ins_file(fc_type fc,rbool report_error)498 rbool open_ins_file(fc_type fc, rbool report_error) {
499 	ins_buff = NULL;
500 	ins_line = 0;
501 
502 	if (ins_descr != NULL) return 1;
503 
504 	if (filevalid(insfile, fINS)) {
505 		textrewind(insfile);
506 		return 1;
507 	}
508 
509 	if (agx_file) {
510 		ins_descr = read_descr(ins_ptr.start, ins_ptr.size);
511 		if (ins_descr != NULL) return 1;
512 
513 		/* Note that if the AGX file doesn't contain an INS block, we
514 		   don't immediatly give up but try opening <fname>.INS */
515 	}
516 
517 	insfile = openfile(fc, fINS,
518 	                   report_error
519 	                   ? "Sorry, Instructions aren't available for this game"
520 	                   : NULL,
521 	                   0);
522 	return (filevalid(insfile, fINS));
523 }
524 
read_ins_line(void)525 char *read_ins_line(void) {
526 	if (ins_descr) {
527 		if (ins_descr[ins_line] != NULL)
528 			return ins_descr[ins_line++];
529 		else return NULL;
530 	} else {
531 		rfree(ins_buff);
532 		ins_buff = readln(insfile, NULL, 0);
533 		return ins_buff;
534 	}
535 }
536 
close_ins_file(void)537 void close_ins_file(void) {
538 	if (ins_descr) {
539 		free_descr(ins_descr);
540 		ins_descr = NULL;
541 	} else if (filevalid(insfile, fINS)) {
542 		rfree(ins_buff);
543 		readclose(insfile);
544 		insfile = BAD_TEXTFILE;
545 	}
546 }
547 
548 
549 
read_ins(fc_type fc)550 descr_line *read_ins(fc_type fc) {
551 	descr_line *txt;
552 	char *buff;
553 	int i;
554 
555 	i = 0;
556 	txt = NULL;
557 	if (open_ins_file(fc, 0)) {  /* Instruction file exists */
558 		while (NULL != (buff = read_ins_line())) {
559 			/* Enlarge txt; we use (i+2) here to leave space for the trailing \0 */
560 			txt = (descr_line *)rrealloc(txt, sizeof(descr_ptr) * (i + 2));
561 			txt[i++] = rstrdup(buff);
562 		}
563 		if (txt != NULL)
564 			txt[i] = 0; /* There is space for this since we used (i+2) above */
565 		close_ins_file();
566 	}
567 	return txt;
568 }
569 
570 
free_ins(descr_line * instr)571 void free_ins(descr_line *instr) {
572 	int i;
573 	if (instr == NULL) return;
574 	for (i = 0; instr[i] != NULL; i++)
575 		rfree(instr[i]);
576 	rfree(instr);
577 }
578 
579 
580 
581 /* Character translation routines, used by agtread.c and read_ttl() */
build_fixchar(void)582 void build_fixchar(void) {
583 	int i;
584 	for (i = 0; i < 256; i++) {
585 		if (i == '\r' || i == '\n') fixchar[i] = ' ';
586 		else if (i == '\\' && bold_mode) fixchar[i] = FORMAT_CODE;
587 		else if (i >= 0x80 && fix_ascii_flag)
588 			fixchar[i] = trans_ibm[i & 0x7f];
589 		else if (i == 0) /* Fix color and blink codes */
590 			fixchar[i] = FORMAT_CODE;
591 		else fixchar[i] = i;
592 	}
593 }
594 
595 } // End of namespace AGT
596 } // End of namespace Glk
597