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