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 "glk/agt/interp.h"
25 #include "glk/agt/agt.h"
26
27 namespace Glk {
28 namespace AGT {
29
30 /*
31 * Glk interface for AGiliTy 1.1.1.1
32 * -------------------------------
33 *
34 * This module contains the the Glk porting layer for AGiliTy. It
35 * defines the Glk arguments list structure, the entry points for the
36 * Glk library framework to use, and all platform-abstracted I/O to
37 * link to Glk's I/O.
38 *
39 * The following items are omitted from this Glk port:
40 *
41 * o Calls to g_vm->glk_tick(). The Glk documentation states that the
42 * interpreter should call g_vm->glk_tick() every opcode or so. This is
43 * intrusive to code (it goes outside of this module), and since
44 * most Glk libraries do precisely nothing in g_vm->glk_tick(), there is
45 * little motivation to add it.
46 *
47 * o Glk tries to assert control over _all_ file I/O. It's just too
48 * disruptive to add it to existing code, so for now, the AGiliTy
49 * interpreter is still dependent on stdio and the like.
50 */
51
52 /*
53 * True and false definitions -- usually defined in glkstart.h, but we need
54 * them early, so we'll define them here too. We also need NULL, but that's
55 * normally from stdio.h or one of it's cousins.
56 */
57 #ifndef FALSE
58 # define FALSE 0
59 #endif
60 #ifndef TRUE
61 # define TRUE (!FALSE)
62 #endif
63
64
65 /*---------------------------------------------------------------------*/
66 /* Module variables, miscellaneous externals not in header files */
67 /*---------------------------------------------------------------------*/
68
69 /* Glk AGiliTy port version number. */
70 static const glui32 GAGT_PORT_VERSION = 0x00010701;
71
72 /* Forward declaration of event wait functions. */
73 static void gagt_event_wait(glui32 wait_type, event_t *event);
74 static void gagt_event_wait_2(glui32 wait_type_1,
75 glui32 wait_type_2,
76 event_t *event);
77
78 /*
79 * Forward declaration of the g_vm->glk_exit() wrapper. Normal functions in this
80 * module should not to call g_vm->glk_exit() directly; they should always call it
81 * through the wrapper instead.
82 */
83 static void gagt_exit();
84
85
86 /*---------------------------------------------------------------------*/
87 /* Glk port utility functions */
88 /*---------------------------------------------------------------------*/
89
90 /*
91 * gagt_fatal()
92 *
93 * Fatal error handler. The function returns, expecting the caller to
94 * abort() or otherwise handle the error.
95 */
gagt_fatal(const char * string)96 static void gagt_fatal(const char *string) {
97 /*
98 * If the failure happens too early for us to have a window, print
99 * the message to stderr.
100 */
101 if (!g_vm->gagt_main_window)
102 error("INTERNAL ERROR: %s", string);
103
104 /* Cancel all possible pending window input events. */
105 g_vm->glk_cancel_line_event(g_vm->gagt_main_window, NULL);
106 g_vm->glk_cancel_char_event(g_vm->gagt_main_window);
107
108 /* Print a message indicating the error. */
109 g_vm->glk_set_window(g_vm->gagt_main_window);
110 g_vm->glk_set_style(style_Normal);
111 g_vm->glk_put_string("\n\nINTERNAL ERROR: ");
112 g_vm->glk_put_string(string);
113
114 g_vm->glk_put_string("\n\nPlease record the details of this error, try to"
115 " note down everything you did to cause it, and email"
116 " this information to simon_baldwin@yahoo.com.\n\n");
117 }
118
119
120 /*
121 * gagt_malloc()
122 * gagt_realloc()
123 *
124 * Non-failing malloc and realloc; call gagt_fatal() and exit if memory
125 * allocation fails.
126 */
gagt_malloc(size_t size)127 static void *gagt_malloc(size_t size) {
128 void *pointer;
129
130 pointer = malloc(size);
131 if (!pointer) {
132 gagt_fatal("GLK: Out of system memory");
133 gagt_exit();
134 }
135
136 return pointer;
137 }
138
gagt_realloc(void * ptr,size_t size)139 static void *gagt_realloc(void *ptr, size_t size) {
140 void *pointer;
141
142 pointer = realloc(ptr, size);
143 if (!pointer) {
144 gagt_fatal("GLK: Out of system memory");
145 gagt_exit();
146 }
147
148 return pointer;
149 }
150
151
152 /*
153 * gagt_strncasecmp()
154 * gagt_strcasecmp()
155 *
156 * Strncasecmp and strcasecmp are not ANSI functions, so here are local
157 * definitions to do the same jobs.
158 */
gagt_strncasecmp(const char * s1,const char * s2,size_t n)159 static int gagt_strncasecmp(const char *s1, const char *s2, size_t n) {
160 size_t index;
161
162 for (index = 0; index < n; index++) {
163 int diff;
164
165 diff = g_vm->glk_char_to_lower(s1[index]) - g_vm->glk_char_to_lower(s2[index]);
166 if (diff < 0 || diff > 0)
167 return diff < 0 ? -1 : 1;
168 }
169
170 return 0;
171 }
172
gagt_strcasecmp(const char * s1,const char * s2)173 static int gagt_strcasecmp(const char *s1, const char *s2) {
174 size_t s1len, s2len;
175 int result;
176
177 s1len = strlen(s1);
178 s2len = strlen(s2);
179
180 result = gagt_strncasecmp(s1, s2, s1len < s2len ? s1len : s2len);
181 if (result < 0 || result > 0)
182 return result;
183 else
184 return s1len < s2len ? -1 : s1len > s2len ? 1 : 0;
185 }
186
187
188 /*
189 * gagt_debug()
190 *
191 * Handler for module debug output. If no debug, it ignores the call,
192 * otherwise it prints a debug message, prefixed by the function name.
193 */
gagt_debug(const char * function,const char * format,...)194 static void gagt_debug(const char *function, const char *format, ...) {
195 if (DEBUG_OUT) {
196 Common::WriteStream *ws = dynamic_cast<Common::WriteStream *>(debugfile);
197 assert(ws);
198
199 ws->writeString(Common::String::format("%s (", function));
200 if (format && strlen(format) > 0) {
201 va_list va;
202
203 va_start(va, format);
204 Common::String data = Common::String::vformat(format, va);
205 ws->writeString(data);
206 va_end(va);
207 }
208
209 ws->writeString(")\n");
210 }
211 }
212
213
214 /*---------------------------------------------------------------------*/
215 /* Functions not ported - functionally unchanged from os_none.c */
216 /*---------------------------------------------------------------------*/
217
218 /*
219 * agt_tone()
220 *
221 * Produce a hz-Hertz sound for ms milliseconds.
222 */
agt_tone(int hz,int ms)223 void agt_tone(int hz, int ms) {
224 gagt_debug("agt_tone", "hz=%d, ms=%d", hz, ms);
225 }
226
227
228 /*
229 * agt_rand()
230 *
231 * Return random number from a to b inclusive. The random number generator
232 * is seeded on the first call, to a reproducible sequence if stable_random,
233 * otherwise using time().
234 */
agt_rand(int a,int b)235 int agt_rand(int a, int b) {
236 int result;
237
238 result = a + g_vm->getRandomNumber(0x7fffffff) % (b - a + 1);
239 gagt_debug("agt_rand", "a=%d, b=%d -> %d", a, b, result);
240 return result;
241 }
242
243
244 /*---------------------------------------------------------------------*/
245 /* Workrounds for bugs in core AGiliTy. */
246 /*---------------------------------------------------------------------*/
247
248 /*
249 * gagt_workround_menus()
250 *
251 * Somewhere in AGiliTy's menu handling stuff is a condition that sets up
252 * an eventual NULL dereference in rstrncpy(), called from num_name_func().
253 * For some reason, perhaps memory overruns, perhaps something else, it
254 * happens after a few turns have been made through agt_menu(). Replacing
255 * agt_menu() won't avoid it.
256 *
257 * However, the menu stuff isn't too useful, or attractive, in a game, so one
258 * solution is to simply disable it. While not possible to do this directly,
259 * there is a sneaky way, using our carnal knowledge of core AGiliTy. In
260 * runverb.c, there is code to prevent menu mode from being turned on where
261 * verbmenu is NULL. Verbmenu is set up in agil.c on loading the game, but,
262 * crucially, is set up before agil.c calls start_interface(). So... here
263 * we can free it, set it to NULL, set menu_mode to 0 (it probably is already)
264 * and AGiliTy behaves as if the game prevents menu mode.
265 */
gagt_workround_menus()266 static void gagt_workround_menus() {
267 free(verbmenu);
268 verbmenu = NULL;
269
270 menu_mode = 0;
271 }
272
273
274 /*
275 * gagt_workround_fileexist()
276 *
277 * This function verifies that the game file can be opened, in effect second-
278 * guessing run_game().
279 *
280 * AGiliTy's fileexist() has in it either a bug, or a misfeature. It always
281 * passes a nofix value of 1 into try_open_file(), which defeats the code to
282 * retry with both upper and lower cased filenames. So here we have to go
283 * round the houses, with readopen()/readclose().
284 */
gagt_workround_fileexist(fc_type fc,filetype ft)285 static int gagt_workround_fileexist(fc_type fc, filetype ft) {
286 genfile file;
287 const char *errstr;
288
289 errstr = NULL;
290 file = readopen(fc, ft, &errstr);
291
292 if (file) {
293 readclose(file);
294 return TRUE;
295 }
296 return FALSE;
297 }
298
299
300 /*---------------------------------------------------------------------*/
301 /* I/O interface start and stop functions. */
302 /*---------------------------------------------------------------------*/
303
304 /* AGiliTy font_status values that indicate what font may be used. */
305 enum {
306 GAGT_FIXED_REQUIRED = 1, GAGT_PROPORTIONAL_OKAY = 2
307 };
308
309
310 /*
311 * start_interface()
312 * close_interface()
313 *
314 * Startup and shutdown callout points. The start function for Glk looks
315 * at the value of font_status that the game sets, to see if it has a strong
316 * view of the font to use. If it does, then we'll reflect that in the
317 * module's font contol, perhaps overriding any command line options that the
318 * user has passed in.
319 */
start_interface(fc_type fc)320 void start_interface(fc_type fc) {
321 switch (font_status) {
322 case GAGT_FIXED_REQUIRED:
323 g_vm->gagt_font_mode = FONT_FIXED_WIDTH;
324 break;
325
326 case GAGT_PROPORTIONAL_OKAY:
327 g_vm->gagt_font_mode = FONT_PROPORTIONAL;
328 break;
329
330 default:
331 break;
332 }
333
334 gagt_workround_menus();
335
336 gagt_debug("start_interface", "fc=%p", fc);
337 }
338
close_interface()339 void close_interface() {
340 if (filevalid(scriptfile, fSCR))
341 close_pfile(scriptfile, 0);
342
343 gagt_debug("close_interface", "");
344 }
345
346
347 /*---------------------------------------------------------------------*/
348 /* Code page 437 to ISO 8859 Latin-1 translations */
349 /*---------------------------------------------------------------------*/
350
351 /*
352 * AGiliTy uses IBM code page 437 characters, and Glk works in ISO 8859
353 * Latin-1. There's some good news, in that a number of the characters,
354 * especially international ones, in these two sets are the same. The bad
355 * news is that, for codes above 127 (that is, beyond 7-bit ASCII), or for
356 * codes below 32, they live in different places. So, here is a table of
357 * conversions for codes not equivalent to 7-bit ASCII, and a pair of
358 * conversion routines.
359 *
360 * Note that some code page 437 characters don't have ISO 8859 Latin-1
361 * equivalents. Predominantly, these are the box-drawing characters, which
362 * is a pity, because these are the ones that are used the most. Anyway,
363 * in these cases, the table substitutes an approximated base ASCII char-
364 * acter in its place.
365 *
366 * The first entry of table comments below is the character's UNICODE value,
367 * just in case it's useful at some future date.
368 */
369 typedef const struct {
370 const unsigned char cp437; /* Code page 437 character. */
371 const unsigned char iso8859_1; /* ISO 8859 Latin-1 character. */
372 } gagt_char_t;
373 typedef gagt_char_t *gagt_charref_t;
374
375 static gagt_char_t GAGT_CHAR_TABLE[] = {
376 /*
377 * Low characters -- those below 0x20. These are the really odd code
378 * page 437 characters, rarely used by AGT games. Low characters are
379 * omitted from the reverse lookup, and participate only in the forwards
380 * lookup from code page 437 to ISO 8859 Latin-1.
381 */
382 {0x01, '@'}, /* 263a White smiling face */
383 {0x02, '@'}, /* 263b Black smiling face */
384 {0x03, '?'}, /* 2665 Black heart suit */
385 {0x04, '?'}, /* 2666 Black diamond suit */
386 {0x05, '?'}, /* 2663 Black club suit */
387 {0x06, '?'}, /* 2660 Black spade suit */
388 {0x07, 0xb7}, /* 2022 Bullet */
389 {0x08, 0xb7}, /* 25d8 Inverse bullet */
390 {0x09, 0xb7}, /* 25e6 White bullet */
391 {0x0a, 0xb7}, /* 25d9 Inverse white circle */
392 {0x0b, '?'}, /* 2642 Male sign */
393 {0x0c, '?'}, /* 2640 Female sign */
394 {0x0d, '?'}, /* 266a Eighth note */
395 {0x0e, '?'}, /* 266b Beamed eighth notes */
396 {0x0f, 0xa4}, /* 263c White sun with rays */
397 {0x10, '>'}, /* 25b6 Black right-pointing triangle */
398 {0x11, '<'}, /* 25c0 Black left-pointing triangle */
399 {0x12, 0xa6}, /* 2195 Up down arrow */
400 {0x13, '!'}, /* 203c Double exclamation mark */
401 {0x14, 0xb6}, /* 00b6 Pilcrow sign */
402 {0x15, 0xa7}, /* 00a7 Section sign */
403 {0x16, '#'}, /* 25ac Black rectangle */
404 {0x17, 0xa6}, /* 21a8 Up down arrow with base */
405 {0x18, '^'}, /* 2191 Upwards arrow */
406 {0x19, 'v'}, /* 2193 Downwards arrow */
407 {0x1a, '>'}, /* 2192 Rightwards arrow */
408 {0x1b, '<'}, /* 2190 Leftwards arrow */
409 {0x1c, '?'}, /* 2310 Reversed not sign */
410 {0x1d, '-'}, /* 2194 Left right arrow */
411 {0x1e, '^'}, /* 25b2 Black up-pointing triangle */
412 {0x1f, 'v'}, /* 25bc Black down-pointing triangle */
413
414 /*
415 * High characters -- those above 0x7f. These are more often used by AGT
416 * games, particularly for box drawing.
417 */
418 {0x80, 0xc7}, /* 00c7 Latin capital letter c with cedilla */
419 {0x81, 0xfc}, /* 00fc Latin small letter u with diaeresis */
420 {0x82, 0xe9}, /* 00e9 Latin small letter e with acute */
421 {0x83, 0xe2}, /* 00e2 Latin small letter a with circumflex */
422 {0x84, 0xe4}, /* 00e4 Latin small letter a with diaeresis */
423 {0x85, 0xe0}, /* 00e0 Latin small letter a with grave */
424 {0x86, 0xe5}, /* 00e5 Latin small letter a with ring above */
425 {0x87, 0xe7}, /* 00e7 Latin small letter c with cedilla */
426 {0x88, 0xea}, /* 00ea Latin small letter e with circumflex */
427 {0x89, 0xeb}, /* 00eb Latin small letter e with diaeresis */
428 {0x8a, 0xe8}, /* 00e8 Latin small letter e with grave */
429 {0x8b, 0xef}, /* 00ef Latin small letter i with diaeresis */
430 {0x8c, 0xee}, /* 00ee Latin small letter i with circumflex */
431 {0x8d, 0xec}, /* 00ec Latin small letter i with grave */
432 {0x8e, 0xc4}, /* 00c4 Latin capital letter a with diaeresis */
433 {0x8f, 0xc5}, /* 00c5 Latin capital letter a with ring above */
434 {0x90, 0xc9}, /* 00c9 Latin capital letter e with acute */
435 {0x91, 0xe6}, /* 00e6 Latin small ligature ae */
436 {0x92, 0xc6}, /* 00c6 Latin capital ligature ae */
437 {0x93, 0xf4}, /* 00f4 Latin small letter o with circumflex */
438 {0x94, 0xf6}, /* 00f6 Latin small letter o with diaeresis */
439 {0x95, 0xf2}, /* 00f2 Latin small letter o with grave */
440 {0x96, 0xfb}, /* 00fb Latin small letter u with circumflex */
441 {0x97, 0xf9}, /* 00f9 Latin small letter u with grave */
442 {0x98, 0xff}, /* 00ff Latin small letter y with diaeresis */
443 {0x99, 0xd6}, /* 00d6 Latin capital letter o with diaeresis */
444 {0x9a, 0xdc}, /* 00dc Latin capital letter u with diaeresis */
445 {0x9b, 0xa2}, /* 00a2 Cent sign */
446 {0x9c, 0xa3}, /* 00a3 Pound sign */
447 {0x9d, 0xa5}, /* 00a5 Yen sign */
448 {0x9e, 'p'}, /* 20a7 Peseta sign */
449 {0x9f, 'f'}, /* 0192 Latin small letter f with hook */
450 {0xa0, 0xe1}, /* 00e1 Latin small letter a with acute */
451 {0xa1, 0xed}, /* 00ed Latin small letter i with acute */
452 {0xa2, 0xf3}, /* 00f3 Latin small letter o with acute */
453 {0xa3, 0xfa}, /* 00fa Latin small letter u with acute */
454 {0xa4, 0xf1}, /* 00f1 Latin small letter n with tilde */
455 {0xa5, 0xd1}, /* 00d1 Latin capital letter n with tilde */
456 {0xa6, 0xaa}, /* 00aa Feminine ordinal indicator */
457 {0xa7, 0xba}, /* 00ba Masculine ordinal indicator */
458 {0xa8, 0xbf}, /* 00bf Inverted question mark */
459 {0xa9, '.'}, /* 2310 Reversed not sign */
460 {0xaa, 0xac}, /* 00ac Not sign */
461 {0xab, 0xbd}, /* 00bd Vulgar fraction one half */
462 {0xac, 0xbc}, /* 00bc Vulgar fraction one quarter */
463 {0xad, 0xa1}, /* 00a1 Inverted exclamation mark */
464 {0xae, 0xab}, /* 00ab Left-pointing double angle quotation mark */
465 {0xaf, 0xbb}, /* 00bb Right-pointing double angle quotation mark */
466 {0xb0, '#'}, /* 2591 Light shade */
467 {0xb1, '#'}, /* 2592 Medium shade */
468 {0xb2, '#'}, /* 2593 Dark shade */
469 {0xb3, '|'}, /* 2502 Box light vertical */
470 {0xb4, '+'}, /* 2524 Box light vertical and left */
471 {0xb5, '+'}, /* 2561 Box vertical single and left double */
472 {0xb6, '|'}, /* 2562 Box vertical double and left single */
473 {0xb7, '+'}, /* 2556 Box down double and left single */
474 {0xb8, '+'}, /* 2555 Box down single and left double */
475 {0xb9, '+'}, /* 2563 Box double vertical and left */
476 {0xba, '|'}, /* 2551 Box double vertical */
477 {0xbb, '\\'}, /* 2557 Box double down and left */
478 {0xbc, '/'}, /* 255d Box double up and left */
479 {0xbd, '+'}, /* 255c Box up double and left single */
480 {0xbe, '+'}, /* 255b Box up single and left double */
481 {0xbf, '\\'}, /* 2510 Box light down and left */
482 {0xc0, '\\'}, /* 2514 Box light up and right */
483 {0xc1, '+'}, /* 2534 Box light up and horizontal */
484 {0xc2, '+'}, /* 252c Box light down and horizontal */
485 {0xc3, '+'}, /* 251c Box light vertical and right */
486 {0xc4, '-'}, /* 2500 Box light horizontal */
487 {0xc5, '+'}, /* 253c Box light vertical and horizontal */
488 {0xc6, '|'}, /* 255e Box vertical single and right double */
489 {0xc7, '|'}, /* 255f Box vertical double and right single */
490 {0xc8, '\\'}, /* 255a Box double up and right */
491 {0xc9, '/'}, /* 2554 Box double down and right */
492 {0xca, '+'}, /* 2569 Box double up and horizontal */
493 {0xcb, '+'}, /* 2566 Box double down and horizontal */
494 {0xcc, '+'}, /* 2560 Box double vertical and right */
495 {0xcd, '='}, /* 2550 Box double horizontal */
496 {0xce, '+'}, /* 256c Box double vertical and horizontal */
497 {0xcf, '='}, /* 2567 Box up single and horizontal double */
498 {0xd0, '+'}, /* 2568 Box up double and horizontal single */
499 {0xd1, '='}, /* 2564 Box down single and horizontal double */
500 {0xd2, '+'}, /* 2565 Box down double and horizontal single */
501 {0xd3, '+'}, /* 2559 Box up double and right single */
502 {0xd4, '+'}, /* 2558 Box up single and right double */
503 {0xd5, '+'}, /* 2552 Box down single and right double */
504 {0xd6, '+'}, /* 2553 Box down double and right single */
505 {0xd7, '+'}, /* 256b Box vertical double and horizontal single */
506 {0xd8, '+'}, /* 256a Box vertical single and horizontal double */
507 {0xd9, '/'}, /* 2518 Box light up and left */
508 {0xda, '/'}, /* 250c Box light down and right */
509 {0xdb, '@'}, /* 2588 Full block */
510 {0xdc, '@'}, /* 2584 Lower half block */
511 {0xdd, '@'}, /* 258c Left half block */
512 {0xde, '@'}, /* 2590 Right half block */
513 {0xdf, '@'}, /* 2580 Upper half block */
514 {0xe0, 'a'}, /* 03b1 Greek small letter alpha */
515 {0xe1, 0xdf}, /* 00df Latin small letter sharp s */
516 {0xe2, 'G'}, /* 0393 Greek capital letter gamma */
517 {0xe3, 'p'}, /* 03c0 Greek small letter pi */
518 {0xe4, 'S'}, /* 03a3 Greek capital letter sigma */
519 {0xe5, 's'}, /* 03c3 Greek small letter sigma */
520 {0xe6, 0xb5}, /* 00b5 Micro sign */
521 {0xe7, 't'}, /* 03c4 Greek small letter tau */
522 {0xe8, 'F'}, /* 03a6 Greek capital letter phi */
523 {0xe9, 'T'}, /* 0398 Greek capital letter theta */
524 {0xea, 'O'}, /* 03a9 Greek capital letter omega */
525 {0xeb, 'd'}, /* 03b4 Greek small letter delta */
526 {0xec, '.'}, /* 221e Infinity */
527 {0xed, 'f'}, /* 03c6 Greek small letter phi */
528 {0xee, 'e'}, /* 03b5 Greek small letter epsilon */
529 {0xef, '^'}, /* 2229 Intersection */
530 {0xf0, '='}, /* 2261 Identical to */
531 {0xf1, 0xb1}, /* 00b1 Plus-minus sign */
532 {0xf2, '>'}, /* 2265 Greater-than or equal to */
533 {0xf3, '<'}, /* 2264 Less-than or equal to */
534 {0xf4, 'f'}, /* 2320 Top half integral */
535 {0xf5, 'j'}, /* 2321 Bottom half integral */
536 {0xf6, 0xf7}, /* 00f7 Division sign */
537 {0xf7, '='}, /* 2248 Almost equal to */
538 {0xf8, 0xb0}, /* 00b0 Degree sign */
539 {0xf9, 0xb7}, /* 2219 Bullet operator */
540 {0xfa, 0xb7}, /* 00b7 Middle dot */
541 {0xfb, '/'}, /* 221a Square root */
542 {0xfc, 'n'}, /* 207f Superscript latin small letter n */
543 {0xfd, 0xb2}, /* 00b2 Superscript two */
544 {0xfe, '#'}, /* 25a0 Black square */
545 {0xff, 0xa0}, /* 00a0 No-break space */
546 {0, 0} /* 0000 [END OF TABLE] */
547 };
548
549
550 /*
551 * gagt_cp_to_iso()
552 *
553 * Convert a string from code page 437 into ISO 8859 Latin-1. The input and
554 * output buffers may be one and the same.
555 */
gagt_cp_to_iso(const unsigned char * from_string,unsigned char * to_string)556 static void gagt_cp_to_iso(const unsigned char *from_string, unsigned char *to_string) {
557 static int is_initialized = FALSE;
558 static unsigned char table[BYTE_MAX_VAL + 1];
559
560 int index;
561 unsigned char cp437, iso8859_1;
562 assert(from_string && to_string);
563
564 if (!is_initialized) {
565 gagt_charref_t entry;
566
567 /*
568 * Create a lookup entry for each code in the main table. Fill in gaps
569 * for 7-bit characters with their ASCII equivalent values. Any
570 * remaining codes not represented in the main table will map to zeroes
571 * in the lookup table, as static variables are initialized to zero.
572 */
573 for (entry = GAGT_CHAR_TABLE; entry->cp437; entry++) {
574 cp437 = entry->cp437;
575 iso8859_1 = entry->iso8859_1;
576
577 // assert(cp437 < 0x20 || (cp437 > INT8_MAX_VAL && cp437 <= BYTE_MAX_VAL));
578 table[cp437] = iso8859_1;
579 }
580 for (index = 0; index <= INT8_MAX_VAL; index++) {
581 if (table[index] == 0)
582 table[index] = index;
583 }
584
585 is_initialized = TRUE;
586 }
587
588 for (index = 0; from_string[index] != '\0'; index++) {
589 cp437 = from_string[index];
590 iso8859_1 = table[cp437];
591
592 to_string[index] = iso8859_1 ? iso8859_1 : cp437;
593 }
594
595 to_string[index] = '\0';
596 }
597
598
599 /*
600 * gagt_iso_to_cp()
601 *
602 * Convert a string from ISO 8859 Latin-1 to code page 437. The input and
603 * output buffers may be one and the same.
604 */
gagt_iso_to_cp(const unsigned char * from_string,unsigned char * to_string)605 static void gagt_iso_to_cp(const unsigned char *from_string, unsigned char *to_string) {
606 static int is_initialized = FALSE;
607 static unsigned char table[BYTE_MAX_VAL + 1];
608
609 int index;
610 unsigned char iso8859_1, cp437;
611 assert(from_string && to_string);
612
613 if (!is_initialized) {
614 gagt_charref_t entry;
615
616 /*
617 * Create a reverse lookup entry for each code in the main table,
618 * overriding all of the low table entries (that is, anything under
619 * 128) with their ASCII no matter what the table contained.
620 *
621 * Any codes not represented in the main table will map to zeroes in
622 * the reverse lookup table, since static variables are initialized to
623 * zero. The first 128 characters are equivalent to ASCII. Moreover,
624 * some ISO 8859 Latin-1 entries are faked as base ASCII; where an
625 * entry is already occupied, the main table entry is skipped, so the
626 * match, which is n:1 in the reverse direction, works in first-found
627 * mode.
628 */
629 for (entry = GAGT_CHAR_TABLE; entry->iso8859_1; entry++) {
630 cp437 = entry->cp437;
631 iso8859_1 = entry->iso8859_1;
632
633 if (table[iso8859_1] == 0)
634 table[iso8859_1] = cp437;
635 }
636 for (index = 0; index <= INT8_MAX_VAL; index++)
637 table[index] = index;
638
639 is_initialized = TRUE;
640 }
641
642 for (index = 0; from_string[index] != '\0'; index++) {
643 iso8859_1 = from_string[index];
644 cp437 = table[iso8859_1];
645
646 to_string[index] = cp437 ? cp437 : iso8859_1;
647 }
648
649 to_string[index] = '\0';
650 }
651
652
653 /*---------------------------------------------------------------------*/
654 /* Glk port status line functions */
655 /*---------------------------------------------------------------------*/
656
657 /*
658 * Buffered copy of the latest status line passed in by the interpreter.
659 * Buffering it means it's readily available to print for Glk libraries
660 * that don't support separate windows. We also need a copy of the last
661 * status buffer printed for non-windowing Glk libraries, for comparison.
662 */
663 static char *gagt_status_buffer = NULL,
664 *gagt_status_buffer_printed = NULL;
665
666 /*
667 * Indication that we are in mid-delay. The delay is silent, and can look
668 * kind of confusing, so to try to make it less so, we'll have the status
669 * window show something about it.
670 */
671 static int gagt_inside_delay = FALSE;
672
673
674 /*
675 * agt_statline()
676 *
677 * This function is called from our call to print_statline(). Here we'll
678 * convert the string and buffer in an allocated area for later use.
679 */
agt_statline(const char * cp_string)680 void agt_statline(const char *cp_string) {
681 assert(cp_string);
682
683 free(gagt_status_buffer);
684 gagt_status_buffer = (char *)gagt_malloc(strlen(cp_string) + 1);
685 gagt_cp_to_iso((const unsigned char *)cp_string, (unsigned char *)gagt_status_buffer);
686
687 gagt_debug("agt_statline", "string='%s'", cp_string);
688 }
689
690
691 /*
692 * gagt_status_update_extended()
693 *
694 * Helper for gagt_status_update() and gagt_status_in_delay(). This function
695 * displays the second line of any extended status display, giving a list of
696 * exits from the compass rose, and if in an AGT delay, a waiting indicator.
697 */
gagt_status_update_extended()698 static void gagt_status_update_extended() {
699 uint width, height;
700 assert(g_vm->gagt_status_window);
701
702 g_vm->glk_window_get_size(g_vm->gagt_status_window, &width, &height);
703 if (height > 1) {
704 uint32 index;
705 int exit;
706
707 /* Clear the second status line only. */
708 g_vm->glk_window_move_cursor(g_vm->gagt_status_window, 0, 1);
709 g_vm->glk_set_window(g_vm->gagt_status_window);
710 g_vm->glk_set_style(style_User1);
711 for (index = 0; index < width; index++)
712 g_vm->glk_put_char(' ');
713
714 /*
715 * Check bits in the compass rose, and print out exit names from
716 * the exitname[] array.
717 */
718 g_vm->glk_window_move_cursor(g_vm->gagt_status_window, 0, 1);
719 g_vm->glk_put_string(" Exits: ");
720 for (exit = 0; exit < (int)sizeof(exitname) / (int)sizeof(exitname[0]); exit++) {
721 if (compass_rose & (1 << exit)) {
722 g_vm->glk_put_string(exitname[exit]);
723 g_vm->glk_put_char(' ');
724 }
725 }
726
727 /* If the delay flag is set, print a waiting indicator at the right. */
728 if (gagt_inside_delay) {
729 g_vm->glk_window_move_cursor(g_vm->gagt_status_window,
730 width - strlen("Waiting... "), 1);
731 g_vm->glk_put_string("Waiting... ");
732 }
733
734 g_vm->glk_set_window(g_vm->gagt_main_window);
735 }
736 }
737
738
739 /*
740 * gagt_status_update()
741 *
742 *
743 * This function calls print_statline() to prompt the interpreter into calling
744 * our agt_statline(), then if we have a status window, displays the status
745 * string, and calls gagt_status_update_extended() if necessary to handle the
746 * second status line. If we don't see a call to our agt_statline, we output
747 * a default status string.
748 */
gagt_status_update()749 static void gagt_status_update() {
750 uint width, height;
751 uint32 index;
752 assert(g_vm->gagt_status_window);
753
754 g_vm->glk_window_get_size(g_vm->gagt_status_window, &width, &height);
755 if (height > 0) {
756 g_vm->glk_window_clear(g_vm->gagt_status_window);
757 g_vm->glk_window_move_cursor(g_vm->gagt_status_window, 0, 0);
758 g_vm->glk_set_window(g_vm->gagt_status_window);
759
760 g_vm->glk_set_style(style_User1);
761 for (index = 0; index < width; index++)
762 g_vm->glk_put_char(' ');
763 g_vm->glk_window_move_cursor(g_vm->gagt_status_window, 0, 0);
764
765 /* Call print_statline() to refresh status line buffer contents. */
766 print_statline();
767
768 /* See if we have a buffered status line available. */
769 if (gagt_status_buffer) {
770 glui32 print_width;
771
772 /*
773 * Print the basic buffered status string, truncating to the
774 * current status window width if necessary, then try adding a
775 * second line if extended status enabled.
776 */
777 print_width = width < strlen(gagt_status_buffer)
778 ? width : strlen(gagt_status_buffer);
779 g_vm->glk_put_buffer(gagt_status_buffer, print_width);
780
781 if (g_vm->gagt_extended_status_enabled)
782 gagt_status_update_extended();
783 } else {
784 /*
785 * We don't (yet) have a status line. Perhaps we're at the
786 * very start of a game. Print a standard message.
787 */
788 g_vm->glk_put_string("Glk AGiliTy version 1.1.1.1");
789 }
790
791 g_vm->glk_set_window(g_vm->gagt_main_window);
792 }
793 }
794
795
796 /*
797 * gagt_status_print()
798 *
799 * Print the current contents of the completed status line buffer out in the
800 * main window, if it has changed since the last call. This is for non-
801 * windowing Glk libraries.
802 *
803 * Like gagt_status_update(), this function calls print_statline() to prompt
804 * the interpreter into calling our agt_statline(), then if we have a new
805 * status line, it prints it.
806 */
gagt_status_print()807 static void gagt_status_print() {
808 /* Call print_statline() to refresh status line buffer contents. */
809 print_statline();
810
811 /*
812 * Do no more if there is no status line to print, or if the status
813 * line hasn't changed since last printed.
814 */
815 if (!gagt_status_buffer
816 || (gagt_status_buffer_printed
817 && strcmp(gagt_status_buffer, gagt_status_buffer_printed) == 0))
818 return;
819
820 /* Set fixed width font to try to preserve status line formatting. */
821 g_vm->glk_set_style(style_Preformatted);
822
823 /*
824 * Bracket, and output the status line buffer. We don't need to put any
825 * spacing after the opening bracket or before the closing one, because
826 * AGiliTy puts leading/trailing spaces on its status lines.
827 */
828 g_vm->glk_put_string("[");
829 g_vm->glk_put_string(gagt_status_buffer);
830 g_vm->glk_put_string("]\n");
831
832 /* Save the details of the printed status buffer. */
833 free(gagt_status_buffer_printed);
834 gagt_status_buffer_printed = (char *)gagt_malloc(strlen(gagt_status_buffer) + 1);
835 strcpy(gagt_status_buffer_printed, gagt_status_buffer);
836 }
837
838
839 /*
840 * gagt_status_notify()
841 *
842 * Front end function for updating status. Either updates the status window
843 * or prints the status line to the main window.
844 *
845 * Functions interested in updating the status line should call either this
846 * function, or gagt_status_redraw(), and not print_statline().
847 */
gagt_status_notify()848 static void gagt_status_notify() {
849 if (!BATCH_MODE) {
850 if (g_vm->gagt_status_window)
851 gagt_status_update();
852 else
853 gagt_status_print();
854 }
855 }
856
857
858 /*
859 * gagt_status_redraw()
860 *
861 * Redraw the contents of any status window with the buffered status string.
862 * This function handles window sizing, and updates the interpreter with
863 * status_width, so may, and should, be called on resize and arrange events.
864 *
865 * Functions interested in updating the status line should call either this
866 * function, or gagt_status_notify(), and not print_statline().
867 */
gagt_status_redraw()868 static void gagt_status_redraw() {
869 if (!BATCH_MODE) {
870 if (g_vm->gagt_status_window) {
871 uint width, height;
872 winid_t parent;
873
874 /*
875 * Measure the status window, and update the interpreter's
876 * status_width variable.
877 */
878 g_vm->glk_window_get_size(g_vm->gagt_status_window, &width, &height);
879 status_width = width;
880
881 /*
882 * Rearrange the status window, without changing its actual
883 * arrangement in any way. This is a hack to work round
884 * incorrect window repainting in Xglk; it forces a complete
885 * repaint of affected windows on Glk window resize and
886 * arrange events, and works in part because Xglk doesn't
887 * check for actual arrangement changes in any way before
888 * invalidating its windows. The hack should be harmless to
889 * Glk libraries other than Xglk, moreover, we're careful to
890 * activate it only on resize and arrange events.
891 */
892 parent = g_vm->glk_window_get_parent(g_vm->gagt_status_window);
893 g_vm->glk_window_set_arrangement(parent,
894 winmethod_Above | winmethod_Fixed,
895 height, NULL);
896
897 gagt_status_update();
898 }
899 }
900 }
901
902
903 /*
904 * gagt_status_in_delay()
905 *
906 * Tells status line functions whether the game is delaying, or not. This
907 * function updates the extended status line, if present, automatically.
908 */
gagt_status_in_delay(int inside_delay)909 static void gagt_status_in_delay(int inside_delay) {
910 if (!BATCH_MODE) {
911 /* Save the new delay status flag. */
912 gagt_inside_delay = inside_delay;
913
914 /*
915 * Update just the second line of the status window display, if
916 * extended status is being displayed.
917 */
918 if (g_vm->gagt_status_window && g_vm->gagt_extended_status_enabled)
919 gagt_status_update_extended();
920 }
921 }
922
923
924 /*
925 * gagt_status_cleanup()
926 *
927 * Free memory resources allocated by status line functions. Called on game
928 * end.
929 */
gagt_status_cleanup()930 static void gagt_status_cleanup() {
931 free(gagt_status_buffer);
932 gagt_status_buffer = NULL;
933
934 free(gagt_status_buffer_printed);
935 gagt_status_buffer_printed = NULL;
936 }
937
938
939 /*---------------------------------------------------------------------*/
940 /* Glk port color and text attribute handling */
941 /*---------------------------------------------------------------------*/
942
943 /*
944 * AGT color and character attribute definitions. This is the range of
945 * values passed in to agt_textcolor().
946 */
947 enum {
948 AGT_BLACK = 0,
949 AGT_BLUE = 1,
950 AGT_GREEN = 2,
951 AGT_CYAN = 3,
952 AGT_RED = 4,
953 AGT_MAGENTA = 5,
954 AGT_BROWN = 6,
955 AGT_NORMAL = 7,
956 AGT_BLINKING = 8,
957 AGT_WHITE = 9,
958 AGT_FIXED_FONT = 10,
959 AGT_VARIABLE_FONT = 11,
960 AGT_EMPHASIS = -1,
961 AGT_DE_EMPHASIS = -2
962 };
963
964 /*
965 * AGiliTy colors and text attributes seem a bit confused. Let's see if we
966 * can sort them out. Sadly, once we have, it's often not possible to
967 * render the full range in all Glk's anyway. Nevertheless...
968 */
969 struct gagt_attrset_t {
970 int color; /* Text color. */
971 int blink; /* Text blinking flag. */
972 int fixed; /* Text fixed font flag. */
973 int emphasis; /* Text emphasized flag. */
974 };
975
976 /*
977 * Attributes as currently set by AGiliTy. The default values set up here
978 * correspond to AGT_NORMAL.
979 */
980 static gagt_attrset_t gagt_current_attribute_set = { AGT_WHITE, FALSE,
981 FALSE, FALSE
982 };
983
984 /*
985 * An extra flag to indicate if we have coerced fixed font override. On
986 * some occasions, we need to ensure that we get fixed font no matter what
987 * the game says.
988 */
989 static int gagt_coerced_fixed = FALSE;
990
991 /*
992 * Bit masks for packing colors and attributes. Normally, I don't like
993 * bit-twiddling all that much, but for packing all of the above into a
994 * single byte, that's what we need. Stuff color into the low four bits,
995 * convenient since color is from 0 to 9, then use three bits for the other
996 * attributes.
997 */
998 static const unsigned char GAGT_COLOR_MASK = 0x0f,
999 GAGT_BLINK_MASK = 1 << 4,
1000 GAGT_FIXED_MASK = 1 << 5,
1001 GAGT_EMPHASIS_MASK = 1 << 6;
1002
1003 /* Forward declaration of message function. */
1004 static void gagt_standout_string(const char *message);
1005
1006
1007 /*
1008 * agt_textcolor()
1009 *
1010 * The AGiliTy porting guide defines the use of this function as:
1011 *
1012 * Set text color to color #c, where the colors are as follows:
1013 * 0=Black, 1=Blue, 2=Green, 3=Cyan,
1014 * 4=Red, 5=Magenta, 6=Brown,
1015 * 7=Normal("White"-- which may actually be some other color)
1016 * This should turn off blinking, bold, color, etc. and restore
1017 * the text mode to its default appearance.
1018 * 8=Turn on blinking.
1019 * 9= *Just* White (not neccessarily "normal" and no need to turn off
1020 * blinking)
1021 * 10=Turn on fixed pitch font.
1022 * 11=Turn off fixed pitch font
1023 * Also used to set other text attributes:
1024 * -1=emphasized text, used (e.g.) for room titles
1025 * -2=end emphasized text
1026 *
1027 * Here we try to make sense of all this. Given an argument, we'll try to
1028 * update our separated color and text attributes flags to reflect the
1029 * expected text rendering.
1030 */
agt_textcolor(int color)1031 void agt_textcolor(int color) {
1032 switch (color) {
1033 case AGT_BLACK:
1034 case AGT_BLUE:
1035 case AGT_GREEN:
1036 case AGT_CYAN:
1037 case AGT_RED:
1038 case AGT_MAGENTA:
1039 case AGT_BROWN:
1040 case AGT_WHITE:
1041 gagt_current_attribute_set.color = color;
1042 break;
1043
1044 case AGT_NORMAL:
1045 gagt_current_attribute_set.color = AGT_WHITE;
1046 gagt_current_attribute_set.blink = FALSE;
1047 gagt_current_attribute_set.fixed = FALSE;
1048 gagt_current_attribute_set.emphasis = FALSE;
1049 break;
1050
1051 case AGT_BLINKING:
1052 gagt_current_attribute_set.blink = TRUE;
1053 break;
1054
1055 case AGT_FIXED_FONT:
1056 gagt_current_attribute_set.fixed = TRUE;
1057 break;
1058
1059 case AGT_VARIABLE_FONT:
1060 gagt_current_attribute_set.fixed = FALSE;
1061 break;
1062
1063 case AGT_EMPHASIS:
1064 gagt_current_attribute_set.emphasis = TRUE;
1065 break;
1066
1067 case AGT_DE_EMPHASIS:
1068 gagt_current_attribute_set.emphasis = FALSE;
1069 break;
1070
1071 default:
1072 gagt_fatal("GLK: Unknown color encountered");
1073 gagt_exit();
1074 }
1075
1076 gagt_debug("agt_textcolor", "color=% d -> %d%s%s%s",
1077 color,
1078 gagt_current_attribute_set.color,
1079 gagt_current_attribute_set.blink ? " blink" : "",
1080 gagt_current_attribute_set.fixed ? " fixed" : "",
1081 gagt_current_attribute_set.emphasis ? " bold" : "");
1082 }
1083
1084
1085 /*
1086 * gagt_coerce_fixed_font()
1087 *
1088 * This coerces, or relaxes, a fixed font setting. Used by box drawing, to
1089 * ensure that we get a temporary fixed font setting for known differenti-
1090 * ated parts of game output text. Pass in TRUE to coerce fixed font, and
1091 * FALSE to relax it.
1092 */
gagt_coerce_fixed_font(int coerce)1093 static void gagt_coerce_fixed_font(int coerce) {
1094 gagt_coerced_fixed = coerce;
1095 }
1096
1097
1098 /*
1099 * gagt_pack_attributes()
1100 *
1101 * Pack a set of color and text rendering attributes into a single byte,
1102 * and return it. This function is used so that a set of text attributes
1103 * can be encoded into a byte array that parallels the output strings that
1104 * we buffer from the interpreter.
1105 */
gagt_pack_attributes(const gagt_attrset_t * attribute_set,int coerced)1106 static unsigned char gagt_pack_attributes(const gagt_attrset_t *attribute_set, int coerced) {
1107 unsigned char packed;
1108 assert(attribute_set);
1109
1110 /* Set the initial result to be color; these are the low bits. */
1111 assert((attribute_set->color & ~GAGT_COLOR_MASK) == 0);
1112 packed = attribute_set->color;
1113
1114 /*
1115 * Now OR in the text attributes settings, taking either the value for
1116 * fixed or the coerced fixed font.
1117 */
1118 packed |= attribute_set->blink ? GAGT_BLINK_MASK : 0;
1119 packed |= attribute_set->fixed || coerced ? GAGT_FIXED_MASK : 0;
1120 packed |= attribute_set->emphasis ? GAGT_EMPHASIS_MASK : 0;
1121
1122 return packed;
1123 }
1124
1125
1126 /*
1127 * gagt_unpack_attributes()
1128 *
1129 * Unpack a set of packed current color and text rendering attributes from a
1130 * single byte, and return the result of unpacking. This reconstitutes the
1131 * text attributes that were current at the time of packing.
1132 */
gagt_unpack_attributes(unsigned char packed,gagt_attrset_t * attribute_set)1133 static void gagt_unpack_attributes(unsigned char packed, gagt_attrset_t *attribute_set) {
1134 assert(attribute_set);
1135
1136 attribute_set->color = packed & GAGT_COLOR_MASK;
1137 attribute_set->blink = (packed & GAGT_BLINK_MASK) != 0;
1138 attribute_set->fixed = (packed & GAGT_FIXED_MASK) != 0;
1139 attribute_set->emphasis = (packed & GAGT_EMPHASIS_MASK) != 0;
1140 }
1141
1142
1143 /*
1144 * gagt_pack_current_attributes()
1145 *
1146 * Pack the current color and text rendering attributes into a single byte,
1147 * and return it.
1148 */
gagt_pack_current_attributes()1149 static unsigned char gagt_pack_current_attributes() {
1150 return gagt_pack_attributes(&gagt_current_attribute_set, gagt_coerced_fixed);
1151 }
1152
1153
1154 /*
1155 * gagt_init_user_styles()
1156 *
1157 * Attempt to set up two defined styles, User1 and User2, to represent
1158 * fixed font with AGT emphasis (rendered as Glk subheader), and fixed font
1159 * with AGT blink (rendered as Glk emphasis), respectively.
1160 *
1161 * The Glk stylehints here may not actually be honored by the Glk library.
1162 * We'll try to detect this later on.
1163 */
gagt_init_user_styles()1164 static void gagt_init_user_styles() {
1165 /*
1166 * Set User1 to be fixed width, bold, and not italic. Here we're sort of
1167 * assuming that the style starts life equal to Normal.
1168 */
1169 g_vm->glk_stylehint_set(wintype_TextBuffer, style_User1,
1170 stylehint_Proportional, 0);
1171 g_vm->glk_stylehint_set(wintype_TextBuffer, style_User1, stylehint_Weight, 1);
1172 g_vm->glk_stylehint_set(wintype_TextBuffer, style_User1, stylehint_Oblique, 0);
1173
1174 /*
1175 * Set User2 to be fixed width, normal, and italic, with the same
1176 * assumptions.
1177 */
1178 g_vm->glk_stylehint_set(wintype_TextBuffer, style_User2,
1179 stylehint_Proportional, 0);
1180 g_vm->glk_stylehint_set(wintype_TextBuffer, style_User2, stylehint_Weight, 0);
1181 g_vm->glk_stylehint_set(wintype_TextBuffer, style_User2, stylehint_Oblique, 1);
1182 }
1183
1184
1185 /*
1186 * gagt_confirm_appearance()
1187 *
1188 * Attempt to find out if a Glk style's on screen appearance matches a given
1189 * expectation. There's a chance (often 100% with current Xglk) that we
1190 * can't tell, in which case we'll play safe, and say that it doesn't (our
1191 * caller is hoping it does).
1192 *
1193 * That is, when we return FALSE, we mean either it's not as expected, or we
1194 * don't know.
1195 */
gagt_confirm_appearance(glui32 style,glui32 stylehint,glui32 expected)1196 static int gagt_confirm_appearance(glui32 style, glui32 stylehint, glui32 expected) {
1197 uint result;
1198
1199 if (g_vm->glk_style_measure(g_vm->gagt_main_window, style, stylehint, &result)) {
1200 /*
1201 * Measurement succeeded, so return TRUE if the result matches the
1202 * caller's expectation.
1203 */
1204 if (result == expected)
1205 return TRUE;
1206 }
1207
1208 /* No straight answer, or the style's stylehint failed to match. */
1209 return FALSE;
1210 }
1211
1212
1213 /*
1214 * gagt_is_style_fixed()
1215 * gagt_is_style_bold()
1216 * gagt_is_style_oblique()
1217 *
1218 * Convenience functions for gagt_select_style(). A return of TRUE indicates
1219 * that the style has this attribute; FALSE indicates either that it hasn't,
1220 * or that it's not determinable.
1221 */
gagt_is_style_fixed(glui32 style)1222 static int gagt_is_style_fixed(glui32 style) {
1223 return gagt_confirm_appearance(style, stylehint_Proportional, 0);
1224 }
1225
gagt_is_style_bold(glui32 style)1226 static int gagt_is_style_bold(glui32 style) {
1227 return gagt_confirm_appearance(style, stylehint_Weight, 1);
1228 }
1229
gagt_is_style_oblique(glui32 style)1230 static int gagt_is_style_oblique(glui32 style) {
1231 return gagt_confirm_appearance(style, stylehint_Oblique, 1);
1232 }
1233
1234
1235 /*
1236 * gagt_select_style()
1237 *
1238 * Given a set of AGT text attributes, this function returns a Glk style that
1239 * is suitable (or more accurately, the best we can come up with) for render-
1240 * ing this set of attributes.
1241 *
1242 * For now, we ignore color totally, and just concentrate on the other attr-
1243 * ibutes. This is because few, if any, games use color (no Photopia here),
1244 * few Glk libraries, at least on Linux, allow fine grained control over text
1245 * color, and even if you can get it, the scarcity of user-defined styles in
1246 * Glk makes it too painful to contemplate.
1247 */
gagt_select_style(gagt_attrset_t * attribute_set)1248 static glui32 gagt_select_style(gagt_attrset_t *attribute_set) {
1249 glui32 style;
1250 assert(attribute_set);
1251
1252 /*
1253 * Glk styles are mutually exclusive, so here we'll work here by making a
1254 * precedence selection: AGT emphasis take precedence over AGT blinking,
1255 * which itself takes precedence over normal text. Fortunately, few, if
1256 * any, AGT games set both emphasis and blinking (not likely to be a
1257 * pleasant combination).
1258 *
1259 * We'll try to map AGT emphasis to Glk Subheader, AGT blink to Glk
1260 * Emphasized, and normal text to Glk Normal, with modifications to this
1261 * for fixed width requests.
1262 *
1263 * First, then, see if emphasized text is requested in the attributes.
1264 */
1265 if (attribute_set->emphasis) {
1266 /*
1267 * Consider whether something requested a fixed width font or
1268 * disallowed a proportional one.
1269 *
1270 * Glk Preformatted is boring, flat, and lifeless. It often offers no
1271 * fine grained control over emphasis, and so on. So here we try to
1272 * find something better. However, not all Glk libraries implement
1273 * stylehints, so we need to try to be careful to ensure that we get a
1274 * fixed width font, no matter what else we may miss out on.
1275 */
1276 if (attribute_set->fixed) {
1277 /*
1278 * To start off, we'll see if User1, the font we set up for fixed
1279 * width bold, really is fixed width and bold. If it is, we'll
1280 * use it.
1281 *
1282 * If it isn't, we'll check Subheader. Our Glk library probably
1283 * isn't implementing stylehints, but if Subheader is fixed width,
1284 * it may provide a better look than Preformatted -- certainly
1285 * it's worth a go.
1286 *
1287 * If Subheader isn't fixed width, we'll take another look at User1.
1288 * It could be that the check for bold wasn't definitive, but it
1289 * is nevertheless bold. So check for fixed width -- if set, it's
1290 * probably good enough to use this font, certainly no worse than
1291 * Preformatted.
1292 *
1293 * If Subheader isn't guaranteed fixed width, nor is User1, we're
1294 * cornered into Preformatted.
1295 */
1296 if (gagt_is_style_fixed(style_User1)
1297 && gagt_is_style_bold(style_User1))
1298 style = style_User1;
1299
1300 else if (gagt_is_style_fixed(style_Subheader))
1301 style = style_Subheader;
1302
1303 else if (gagt_is_style_fixed(style_User1))
1304 style = style_User1;
1305
1306 else
1307 style = style_Preformatted;
1308 } else
1309 /* This is the easy case, use Subheader. */
1310 style = style_Subheader;
1311 } else if (attribute_set->blink) {
1312 /*
1313 * Again, consider whether something requested a fixed width
1314 * font or disallowed a proportional one.
1315 */
1316 if (attribute_set->fixed) {
1317 /*
1318 * As above, try to find something better than Preformatted, first
1319 * trying User2, then Emphasized, then User2 again, and finally
1320 * settling for Preformatted if neither of these two looks any
1321 * better.
1322 */
1323 if (gagt_is_style_fixed(style_User2)
1324 && gagt_is_style_oblique(style_User2))
1325 style = style_User2;
1326
1327 else if (gagt_is_style_fixed(style_Emphasized))
1328 style = style_Emphasized;
1329
1330 else if (gagt_is_style_fixed(style_User2))
1331 style = style_User2;
1332
1333 else
1334 style = style_Preformatted;
1335 } else
1336 /* This is the easy case, use Emphasized. */
1337 style = style_Emphasized;
1338 } else {
1339 /*
1340 * There's no emphasis or blinking in the attributes. In this case,
1341 * use Preformatted for fixed width, and Normal for text that can be
1342 * rendered proportionally.
1343 */
1344 if (attribute_set->fixed)
1345 style = style_Preformatted;
1346 else
1347 style = style_Normal;
1348 }
1349
1350 return style;
1351 }
1352
1353
1354 /*---------------------------------------------------------------------*/
1355 /* Glk port output buffering functions */
1356 /*---------------------------------------------------------------------*/
1357
1358 /*
1359 * Buffering game output happens at two levels. The first level is a single
1360 * line buffer, used to catch text sent to us with agt_puts(). In parallel
1361 * with the text strings, we keep and buffer the game text attributes, as
1362 * handed to agt_textcolor(), that are in effect at the time the string is
1363 * handed to us, packed for brevity.
1364 *
1365 * As each line is completed, by a call to agt_newline(), this single line
1366 * buffer is transferred to a main text page buffer. The main page buffer
1367 * has places in it where we can assign paragraph, font hints, and perhaps
1368 * other marker information to a line. Initially unset, they're filled in
1369 * at the point where we need to display the buffer.
1370 */
1371
1372 /*
1373 * Definition of font hints values. Font hints may be:
1374 * o none, for lines not in a definite paragraph;
1375 * o proportional, for lines that can probably be safely rendered in a
1376 * proportional font (if the AGT game text attributes allow it) and
1377 * where the newline may be replaced by a space;
1378 * o proportional_newline, for lines that may be rendered using a
1379 * proportional font, but where the newline looks like it matters;
1380 * o proportional_newline_standout, for proportional_newline lines that
1381 * are also standout (for spacing in display functions);
1382 * o fixed_width, for tables and other text that looks like it is a
1383 * candidate for fixed font output.
1384 */
1385 typedef enum {
1386 HINT_NONE,
1387 HINT_PROPORTIONAL,
1388 HINT_PROPORTIONAL_NEWLINE,
1389 HINT_PROPORTIONAL_NEWLINE_STANDOUT,
1390 HINT_FIXED_WIDTH
1391 } gagt_font_hint_t;
1392
1393 /* Magic number used to ensure a pointer points to a page buffer line. */
1394 static const unsigned int GAGT_LINE_MAGIC = 0x5bc14482;
1395
1396 /*
1397 * Definition of a single line buffer. This is a growable string and a
1398 * parallel growable attributes array. The string is buffered without any
1399 * null terminator -- not needed since we retain length.
1400 */
1401 typedef struct {
1402 unsigned char *data; /* Buffered character data. */
1403 unsigned char *attributes; /* Parallel character attributes, packed. */
1404 int allocation; /* Bytes allocated to each of the above. */
1405 int length; /* Amount of data actually buffered. */
1406 } gagt_string_t;
1407 typedef gagt_string_t *gagt_stringref_t;
1408
1409 /*
1410 * Definition of a page buffer entry. This is a structure that holds the
1411 * the result of a single line buffer above, plus additional areas that
1412 * describe line text positioning, a blank line flag, a paragraph pointer
1413 * (NULL if not in a paragraph), and a font hint.
1414 */
1415 typedef struct gagt_line_s *gagt_lineref_t;
1416 typedef struct gagt_paragraph_s *gagt_paragraphref_t;
1417
1418 struct gagt_line_s {
1419 unsigned int magic; /* Assertion check dog-tag. */
1420
1421 gagt_string_t buffer; /* Buffered line string data. */
1422
1423 int indent; /* Line indentation. */
1424 int outdent; /* Trailing line whitespace. */
1425 int real_length; /* Real line length. */
1426 int is_blank; /* Line blank flag. */
1427 int is_hyphenated; /* Line hyphenated flag. */
1428
1429 gagt_paragraphref_t paragraph; /* Paragraph containing the line. */
1430 gagt_font_hint_t font_hint; /* Line's font hint. */
1431
1432 gagt_lineref_t next; /* List next element. */
1433 gagt_lineref_t prior; /* List prior element. */
1434 };
1435
1436 /*
1437 * Definition of the actual page buffer. This is a doubly-linked list of
1438 * lines, with a tail pointer to facilitate adding entries at the end.
1439 */
1440 static gagt_lineref_t gagt_page_head = NULL,
1441 gagt_page_tail = NULL;
1442
1443 /*
1444 * Definition of the current output line; this one is appended to on
1445 * agt_puts(), and transferred into the page buffer on agt_newline().
1446 */
1447 static gagt_string_t gagt_current_buffer = { NULL, NULL, 0, 0 };
1448
1449 /*
1450 * gagt_string_append()
1451 * gagt_string_transfer()
1452 * gagt_string_free()
1453 *
1454 * String append, move, and allocation free for string_t buffers.
1455 */
gagt_string_append(gagt_stringref_t buffer,const char * string,unsigned char packed_attributes)1456 static void gagt_string_append(gagt_stringref_t buffer, const char *string,
1457 unsigned char packed_attributes) {
1458 int length, bytes;
1459
1460 /*
1461 * Find the size we'll need from the line buffer to add this string,
1462 * and grow buffer if necessary.
1463 */
1464 length = strlen(string);
1465 for (bytes = buffer->allocation; bytes < buffer->length + length;)
1466 bytes = bytes == 0 ? 1 : bytes << 1;
1467
1468 if (bytes > buffer->allocation) {
1469 buffer->data = (uchar *)gagt_realloc(buffer->data, bytes);
1470 buffer->attributes = (uchar *)gagt_realloc(buffer->attributes, bytes);
1471
1472 buffer->allocation = bytes;
1473 }
1474
1475 /* Add string to the line buffer, and store packed text attributes. */
1476 memcpy(buffer->data + buffer->length, string, length);
1477 memset(buffer->attributes + buffer->length, packed_attributes, length);
1478
1479 buffer->length += length;
1480 }
1481
gagt_string_transfer(gagt_stringref_t from,gagt_stringref_t to)1482 static void gagt_string_transfer(gagt_stringref_t from, gagt_stringref_t to) {
1483 *to = *from;
1484 from->data = from->attributes = NULL;
1485 from->allocation = from->length = 0;
1486 }
1487
gagt_string_free(gagt_stringref_t buffer)1488 static void gagt_string_free(gagt_stringref_t buffer) {
1489 free(buffer->data);
1490 free(buffer->attributes);
1491 buffer->data = buffer->attributes = NULL;
1492 buffer->allocation = buffer->length = 0;
1493 }
1494
1495
1496 /*
1497 * gagt_get_string_indent()
1498 * gagt_get_string_outdent()
1499 * gagt_get_string_real_length()
1500 * gagt_is_string_blank()
1501 * gagt_is_string_hyphenated()
1502 *
1503 * Metrics functions for string_t buffers.
1504 */
gagt_get_string_indent(const gagt_stringref_t buffer)1505 static int gagt_get_string_indent(const gagt_stringref_t buffer) {
1506 int indent, index;
1507
1508 indent = 0;
1509 for (index = 0;
1510 index < buffer->length && isspace(buffer->data[index]);
1511 index++)
1512 indent++;
1513
1514 return indent;
1515 }
1516
gagt_get_string_outdent(const gagt_stringref_t buffer)1517 static int gagt_get_string_outdent(const gagt_stringref_t buffer) {
1518 int outdent, index;
1519
1520 outdent = 0;
1521 for (index = buffer->length - 1;
1522 index >= 0 && isspace(buffer->data[index]); index--)
1523 outdent++;
1524
1525 return outdent;
1526 }
1527
1528
gagt_get_string_real_length(const gagt_stringref_t buffer)1529 static int gagt_get_string_real_length(const gagt_stringref_t buffer) {
1530 int indent, outdent;
1531
1532 indent = gagt_get_string_indent(buffer);
1533 outdent = gagt_get_string_outdent(buffer);
1534
1535 return indent == buffer->length ? 0 : buffer->length - indent - outdent;
1536 }
1537
gagt_is_string_blank(const gagt_stringref_t buffer)1538 static int gagt_is_string_blank(const gagt_stringref_t buffer) {
1539 return gagt_get_string_indent(buffer) == buffer->length;
1540 }
1541
gagt_is_string_hyphenated(const gagt_stringref_t buffer)1542 static int gagt_is_string_hyphenated(const gagt_stringref_t buffer) {
1543 int is_hyphenated;
1544
1545 is_hyphenated = FALSE;
1546
1547 if (!gagt_is_string_blank(buffer)
1548 && gagt_get_string_real_length(buffer) > 1) {
1549 int last;
1550
1551 last = buffer->length - gagt_get_string_outdent(buffer) - 1;
1552
1553 if (buffer->data[last] == '-') {
1554 if (isalpha(buffer->data[last - 1]))
1555 is_hyphenated = TRUE;
1556 }
1557 }
1558
1559 return is_hyphenated;
1560 }
1561
1562
1563 /*
1564 * gagt_output_delete()
1565 *
1566 * Delete all buffered page and line text. Free all malloc'ed buffer memory,
1567 * and return the buffer variables to their initial values.
1568 */
gagt_output_delete()1569 static void gagt_output_delete() {
1570 gagt_lineref_t line, next_line;
1571
1572 for (line = gagt_page_head; line; line = next_line) {
1573 assert(line->magic == GAGT_LINE_MAGIC);
1574 next_line = line->next;
1575
1576 gagt_string_free(&line->buffer);
1577
1578 memset(line, 0, sizeof(*line));
1579 free(line);
1580 }
1581
1582 gagt_page_head = gagt_page_tail = NULL;
1583
1584 gagt_string_free(&gagt_current_buffer);
1585 }
1586
1587
1588 /*
1589 * agt_puts()
1590 *
1591 * Buffer the string passed in into our current single line buffer. The
1592 * function converts to ISO 8859 Latin-1 encoding before buffering.
1593 */
agt_puts(const char * cp_string)1594 void agt_puts(const char *cp_string) {
1595 assert(cp_string);
1596
1597 if (!BATCH_MODE) {
1598 char *iso_string;
1599 unsigned char packed;
1600 int length;
1601
1602 /* Update the apparent (virtual) window x position. */
1603 length = strlen(cp_string);
1604 curr_x += length;
1605
1606 /*
1607 * Convert the buffer from IBM cp 437 to Glk's ISO 8859 Latin-1, and
1608 * add string and packed text attributes to the current line buffer.
1609 */
1610 iso_string = (char *)gagt_malloc(length + 1);
1611 gagt_cp_to_iso((const uchar *)cp_string, (uchar *)iso_string);
1612 packed = gagt_pack_current_attributes();
1613 gagt_string_append(&gagt_current_buffer, iso_string, packed);
1614
1615 /* Add the string to any script file. */
1616 if (script_on)
1617 textputs(scriptfile, iso_string);
1618
1619 free(iso_string);
1620 gagt_debug("agt_puts", "string='%s'", cp_string);
1621 }
1622 }
1623
1624
1625 /*
1626 * agt_newline()
1627 *
1628 * Accept a newline to the main window. Our job here is to append the
1629 * current line buffer to the page buffer, and clear the line buffer to
1630 * begin accepting new text.
1631 */
agt_newline()1632 void agt_newline() {
1633 if (!BATCH_MODE) {
1634 gagt_lineref_t line;
1635
1636 /* Update the apparent (virtual) window x position. */
1637 curr_x = 0;
1638
1639 /* Create a new line entry for the page buffer. */
1640 line = (gagt_lineref_t)gagt_malloc(sizeof(*line));
1641 line->magic = GAGT_LINE_MAGIC;
1642
1643 /* Move the line from the line buffer into the page buffer. */
1644 gagt_string_transfer(&gagt_current_buffer, &line->buffer);
1645
1646 /* Fill in the line buffer metrics. */
1647 line->indent = gagt_get_string_indent(&line->buffer);
1648 line->outdent = gagt_get_string_outdent(&line->buffer);
1649 line->real_length = gagt_get_string_real_length(&line->buffer);
1650 line->is_blank = gagt_is_string_blank(&line->buffer);
1651 line->is_hyphenated = gagt_is_string_hyphenated(&line->buffer);
1652
1653 /* For now, default the remaining page buffer fields for the line. */
1654 line->paragraph = NULL;
1655 line->font_hint = HINT_NONE;
1656
1657 /* Add to the list, creating a new list if necessary. */
1658 line->next = NULL;
1659 line->prior = gagt_page_tail;
1660 if (gagt_page_head)
1661 gagt_page_tail->next = line;
1662 else
1663 gagt_page_head = line;
1664 gagt_page_tail = line;
1665
1666 /* Add a newline to any script file. */
1667 if (script_on)
1668 textputs(scriptfile, "\n");
1669
1670 gagt_debug("agt_newline", "");
1671 }
1672 }
1673
1674
1675 /*
1676 * gagt_get_first_page_line()
1677 * gagt_get_next_page_line()
1678 * gagt_get_prior_page_line()
1679 *
1680 * Iterator functions for the page buffer. These functions return the first
1681 * line from the page buffer, the next line, or the previous line, given a
1682 * line, respectively. They return NULL if no lines, or no more lines, are
1683 * available.
1684 */
gagt_get_first_page_line()1685 static gagt_lineref_t gagt_get_first_page_line() {
1686 gagt_lineref_t line;
1687
1688 line = gagt_page_head;
1689 assert(!line || line->magic == GAGT_LINE_MAGIC);
1690 return line;
1691 }
1692
gagt_get_next_page_line(const gagt_lineref_t line)1693 static gagt_lineref_t gagt_get_next_page_line(const gagt_lineref_t line) {
1694 gagt_lineref_t next_line;
1695 assert(line && line->magic == GAGT_LINE_MAGIC);
1696
1697 next_line = line->next;
1698 assert(!next_line || next_line->magic == GAGT_LINE_MAGIC);
1699 return next_line;
1700 }
1701
gagt_get_prior_page_line(const gagt_lineref_t line)1702 static gagt_lineref_t gagt_get_prior_page_line(const gagt_lineref_t line) {
1703 gagt_lineref_t prior_line;
1704 assert(line && line->magic == GAGT_LINE_MAGIC);
1705
1706 prior_line = line->prior;
1707 assert(!prior_line || prior_line->magic == GAGT_LINE_MAGIC);
1708 return prior_line;
1709 }
1710
1711
1712 /*---------------------------------------------------------------------*/
1713 /* Glk port paragraphing functions and data */
1714 /*---------------------------------------------------------------------*/
1715
1716 /* Magic number used to ensure a pointer points to a paragraph. */
1717 static const unsigned int GAGT_PARAGRAPH_MAGIC = 0xb9a2297b;
1718
1719 /* Forward definition of special paragraph reference. */
1720 typedef const struct gagt_special_s *gagt_specialref_t;
1721
1722 /*
1723 * Definition of a paragraph entry. This is a structure that holds a
1724 * pointer to the first line buffer in the paragraph.
1725 */
1726 struct gagt_paragraph_s {
1727 unsigned int magic; /* Assertion check dog-tag. */
1728
1729 gagt_lineref_t first_line; /* First line in the paragraph. */
1730 gagt_specialref_t special; /* Special paragraph entry. */
1731
1732 int line_count; /* Number of lines in the paragraph. */
1733 int id; /* Paragraph id, sequence, for debug only. */
1734
1735 gagt_paragraphref_t next; /* List next element. */
1736 gagt_paragraphref_t prior; /* List prior element. */
1737 };
1738
1739 /*
1740 * A doubly-linked list of paragraphs, with a tail pointer to facilitate
1741 * adding entries at the end.
1742 */
1743 static gagt_paragraphref_t gagt_paragraphs_head = NULL,
1744 gagt_paragraphs_tail = NULL;
1745
1746 /*
1747 * gagt_paragraphs_delete()
1748 *
1749 * Delete paragraphs held in the list. This function doesn't delete the
1750 * page buffer lines, just the paragraphs describing the page.
1751 */
gagt_paragraphs_delete()1752 static void gagt_paragraphs_delete() {
1753 gagt_paragraphref_t paragraph, next_paragraph;
1754
1755 for (paragraph = gagt_paragraphs_head; paragraph; paragraph = next_paragraph) {
1756 assert(paragraph->magic == GAGT_PARAGRAPH_MAGIC);
1757 next_paragraph = paragraph->next;
1758
1759 memset(paragraph, 0, sizeof(*paragraph));
1760 free(paragraph);
1761 }
1762
1763 gagt_paragraphs_head = gagt_paragraphs_tail = NULL;
1764 }
1765
1766
1767 /*
1768 * gagt_find_paragraph_start()
1769 *
1770 * Find and return the next non-blank line in the page buffer, given a start
1771 * point. Returns NULL if there are no more blank lines.
1772 */
gagt_find_paragraph_start(const gagt_lineref_t begin)1773 static gagt_lineref_t gagt_find_paragraph_start(const gagt_lineref_t begin) {
1774 gagt_lineref_t line, match;
1775
1776 /*
1777 * Advance line to the beginning of the next paragraph, stopping on the
1778 * first non-blank line, or at the end of the page buffer.
1779 */
1780 match = NULL;
1781 for (line = begin; line; line = gagt_get_next_page_line(line)) {
1782 if (!line->is_blank) {
1783 match = line;
1784 break;
1785 }
1786 }
1787
1788 return match;
1789 }
1790
1791
1792 /*
1793 * gagt_find_block_end()
1794 * gagt_find_blank_line_block_end()
1795 *
1796 * Find and return the apparent end of a paragraph from the page buffer,
1797 * given a start point, and an indentation reference. The end is either
1798 * the point where indentation returns to the reference indentation, or
1799 * the next blank line.
1800 *
1801 * Indentation reference can be -1, indicating that only the next blank
1802 * line will end the paragraph. Indentation references less than 1 are
1803 * also ignored.
1804 */
gagt_find_block_end(const gagt_lineref_t begin,int indent)1805 static gagt_lineref_t gagt_find_block_end(const gagt_lineref_t begin, int indent) {
1806 gagt_lineref_t line, match;
1807
1808 /*
1809 * Initialize the match to be the start of the block, then advance line
1810 * until we hit a blank line or the end of the page buffer. At this point,
1811 * match contains the last line checked.
1812 */
1813 match = begin;
1814 for (line = begin; line; line = gagt_get_next_page_line(line)) {
1815 /*
1816 * Found if we reach a blank line, or when given an indentation to
1817 * check for, we find it.
1818 */
1819 if (line->is_blank || (indent > 0 && line->indent == indent))
1820 break;
1821
1822 match = line;
1823 }
1824
1825 return match;
1826 }
1827
gagt_find_blank_line_block_end(const gagt_lineref_t begin)1828 static gagt_lineref_t gagt_find_blank_line_block_end(const gagt_lineref_t begin) {
1829 return gagt_find_block_end(begin, -1);
1830 }
1831
1832
1833 /*
1834 * gagt_find_paragraph_end()
1835 *
1836 * Find and return the apparent end of a paragraph from the page buffer,
1837 * given a start point. The function attempts to recognize paragraphs by
1838 * the "shape" of indentation.
1839 */
gagt_find_paragraph_end(const gagt_lineref_t first_line)1840 static gagt_lineref_t gagt_find_paragraph_end(const gagt_lineref_t first_line) {
1841 gagt_lineref_t second_line;
1842
1843 /*
1844 * If the start line is the last line in the buffer, or if the next line
1845 * is a blank line, return the start line as also being the end of the
1846 * paragraph.
1847 */
1848 second_line = gagt_get_next_page_line(first_line);
1849 if (!second_line || second_line->is_blank) {
1850 return first_line;
1851 }
1852
1853 /*
1854 * Time to look at line indentations...
1855 *
1856 * If either line is grossly indented, forget about trying to infer
1857 * anything from this, and just break the paragraph on the next blank line.
1858 */
1859 if (first_line->indent > screen_width / 4
1860 || second_line->indent > screen_width / 4) {
1861 return gagt_find_blank_line_block_end(second_line);
1862 }
1863
1864 /*
1865 * If the first line is indented more than the second, end the paragraph
1866 * on a blank line, or on a return in indentation to the level of the
1867 * first line. Here we're looking for paragraphs with the shape
1868 *
1869 * aksjdj jfkasjd fjkasjd ajksdj fkaj djf akjsd fkjas dfs
1870 * kasjdlkfjkj fj aksd jfjkasj dlkfja skjdk flaks dlf jalksdf
1871 * ksjdf kjs kdf lasjd fkjalks jdfkjasjd flkjasl djfkasjfdkl
1872 */
1873 else if (first_line->indent > second_line->indent) {
1874 return gagt_find_block_end(second_line, first_line->indent);
1875 }
1876
1877 /*
1878 * If the second line is more indented than the first, this may indicate
1879 * a title line, followed by normal indented paragraphing. In this case,
1880 * use the second line indentation as the reference, and begin searching
1881 * at the next line. This finds
1882 *
1883 * ksjdkfjask ksadf
1884 * kajskd fksjkfj jfkj jfkslaj fksjlfj jkjskjlfa j fjksal
1885 * sjkkdjf sj fkjkajkdlfj lsjak dfjk djkfjskl dklf alks dfll
1886 * fjksja jkj dksja kjdk kaj dskfj aksjdf aksjd kfjaks fjks
1887 *
1888 * and
1889 *
1890 * asdfj kjsdf kjs
1891 * akjsdkj fkjs kdjfa lskjdl fjalsj dlfjksj kdj fjkd jlsjd
1892 * jalksj jfk slj lkfjsa lkjd lfjlaks dlfkjals djkj alsjd
1893 * kj jfksj fjksjl alkjs dlkjf lakjsd fkjas ldkj flkja fsd
1894 */
1895 else if (second_line->indent > first_line->indent) {
1896 gagt_lineref_t third_line;
1897
1898 /*
1899 * See if we have a third buffer line to look at. If we don't, or if
1900 * we do but it's blank, the paragraph ends here.
1901 */
1902 third_line = gagt_get_next_page_line(second_line);
1903 if (!third_line || third_line->is_blank) {
1904 return second_line;
1905 }
1906
1907 /* As above, give up on gross indentation. */
1908 if (second_line->indent > screen_width / 4
1909 || third_line->indent > screen_width / 4) {
1910 return gagt_find_blank_line_block_end(third_line);
1911 }
1912
1913 /*
1914 * If the second line indentation exceeds the third, this is probably
1915 * a paragraph with a title line. In this case, end the paragraph on
1916 * a return to the indentation of the second line. If not, just find
1917 * the next blank line.
1918 */
1919 else if (second_line->indent > third_line->indent) {
1920 return gagt_find_block_end(third_line, second_line->indent);
1921 } else {
1922 return gagt_find_blank_line_block_end(third_line);
1923 }
1924 }
1925
1926 /*
1927 * Otherwise, the first and second line indentations are the same, so
1928 * break only on the next empty line. This finds the simple
1929 *
1930 * ksd kjal jdljf lakjsd lkj lakjsdl jfla jsldj lfaksdj fksj
1931 * lskjd fja kjsdlk fjlakjs ldkjfksj lkjdf kjalskjd fkjklal
1932 * skjd fkaj djfkjs dkfjal sjdlkfj alksjdf lkajs ldkjf alljjf
1933 */
1934 else {
1935 assert(second_line->indent == first_line->indent);
1936 return gagt_find_blank_line_block_end(second_line);
1937 }
1938 }
1939
1940
1941 /*
1942 * gagt_paragraph_page()
1943 *
1944 * This function breaks the page buffer into what appear to be paragraphs,
1945 * based on observations of indentation and blank separator lines.
1946 */
gagt_paragraph_page()1947 static void gagt_paragraph_page() {
1948 gagt_lineref_t start;
1949
1950 assert(!gagt_paragraphs_head && !gagt_paragraphs_tail);
1951
1952 /* Find the start of the first paragraph. */
1953 start = gagt_find_paragraph_start(gagt_get_first_page_line());
1954 while (start) {
1955 gagt_paragraphref_t paragraph;
1956 gagt_lineref_t end, line;
1957
1958 /* Create a new paragraph entry. */
1959 paragraph = (gagt_paragraphref_t)gagt_malloc(sizeof(*paragraph));
1960 paragraph->magic = GAGT_PARAGRAPH_MAGIC;
1961 paragraph->first_line = start;
1962 paragraph->special = NULL;
1963 paragraph->line_count = 1;
1964 paragraph->id = gagt_paragraphs_tail ? gagt_paragraphs_tail->id + 1 : 0;
1965
1966 /* Add to the list, creating a new list if necessary. */
1967 paragraph->next = NULL;
1968 paragraph->prior = gagt_paragraphs_tail;
1969 if (gagt_paragraphs_head)
1970 gagt_paragraphs_tail->next = paragraph;
1971 else
1972 gagt_paragraphs_head = paragraph;
1973 gagt_paragraphs_tail = paragraph;
1974
1975 /* From the start, identify the paragraph end. */
1976 end = gagt_find_paragraph_end(start);
1977
1978 /*
1979 * Set paragraph in each line identified as part of this paragraph,
1980 * and increment the paragraph's line count.
1981 */
1982 for (line = start;
1983 line != end; line = gagt_get_next_page_line(line)) {
1984 line->paragraph = paragraph;
1985 paragraph->line_count++;
1986 }
1987 end->paragraph = paragraph;
1988
1989 /*
1990 * If there's another line, look for the next paragraph there,
1991 * otherwise we're done.
1992 */
1993 line = gagt_get_next_page_line(end);
1994 if (line)
1995 start = gagt_find_paragraph_start(line);
1996 else
1997 start = NULL;
1998 }
1999 }
2000
2001
2002 /*
2003 * gagt_get_first_paragraph()
2004 * gagt_get_next_paragraph()
2005 *
2006 * Iterator functions for the paragraphs list.
2007 */
gagt_get_first_paragraph()2008 static gagt_paragraphref_t gagt_get_first_paragraph() {
2009 gagt_paragraphref_t paragraph;
2010
2011 paragraph = gagt_paragraphs_head;
2012 assert(!paragraph || paragraph->magic == GAGT_PARAGRAPH_MAGIC);
2013 return paragraph;
2014 }
2015
gagt_get_next_paragraph(const gagt_paragraphref_t paragraph)2016 static gagt_paragraphref_t gagt_get_next_paragraph(const gagt_paragraphref_t paragraph) {
2017 gagt_paragraphref_t next_paragraph;
2018 assert(paragraph && paragraph->magic == GAGT_PARAGRAPH_MAGIC);
2019
2020 next_paragraph = paragraph->next;
2021 assert(!next_paragraph || next_paragraph->magic == GAGT_PARAGRAPH_MAGIC);
2022 return next_paragraph;
2023 }
2024
2025
2026 /*
2027 * gagt_get_first_paragraph_line()
2028 * gagt_get_next_paragraph_line()
2029 * gagt_get_prior_paragraph_line()
2030 *
2031 * Iterator functions for the page buffer. These functions implement a
2032 * paragraph-based view of the page buffer.
2033 *
2034 * The functions find the first line of a given paragraph; given a line,
2035 * the next line in the same paragraph, or NULL if line is the last para-
2036 * graph line (or the last line in the page buffer); and given a line,
2037 * the previous line in the same paragraph, or NULL if line is the first
2038 * paragraph line (or the first line in the page buffer).
2039 */
gagt_get_first_paragraph_line(const gagt_paragraphref_t paragraph)2040 static gagt_lineref_t gagt_get_first_paragraph_line(const gagt_paragraphref_t paragraph) {
2041 assert(paragraph && paragraph->magic == GAGT_PARAGRAPH_MAGIC);
2042
2043 /* Return the first line for the requested paragraph. */
2044 return paragraph->first_line;
2045 }
2046
gagt_get_next_paragraph_line(const gagt_lineref_t line)2047 static gagt_lineref_t gagt_get_next_paragraph_line(const gagt_lineref_t line) {
2048 gagt_lineref_t next_line;
2049
2050 /* Get the next line; return it if the paragraph matches, else NULL. */
2051 next_line = gagt_get_next_page_line(line);
2052 if (next_line && next_line->paragraph == line->paragraph)
2053 return next_line;
2054 else
2055 return NULL;
2056 }
2057
gagt_get_prior_paragraph_line(const gagt_lineref_t line)2058 static gagt_lineref_t gagt_get_prior_paragraph_line(const gagt_lineref_t line) {
2059 gagt_lineref_t prior_line;
2060
2061 /* Get the previous line; return it if the paragraph matches, else NULL. */
2062 prior_line = gagt_get_prior_page_line(line);
2063 if (prior_line && prior_line->paragraph == line->paragraph)
2064 return prior_line;
2065 else
2066 return NULL;
2067 }
2068
2069
2070 /*
2071 * gagt_get_paragraph_line_count()
2072 *
2073 * Return the count of lines contained in the paragraph.
2074 */
gagt_get_paragraph_line_count(const gagt_paragraphref_t paragraph)2075 static int gagt_get_paragraph_line_count(const gagt_paragraphref_t paragraph) {
2076 assert(paragraph && paragraph->magic == GAGT_PARAGRAPH_MAGIC);
2077
2078 return paragraph->line_count;
2079 }
2080
2081
2082 /*---------------------------------------------------------------------*/
2083 /* Glk port page buffer analysis functions */
2084 /*---------------------------------------------------------------------*/
2085
2086 /*
2087 * Threshold for consecutive punctuation/spaces before we decide that a line
2088 * is in fact part of a table, and a small selection of characters to apply
2089 * a somewhat larger threshold to when looking for punctuation (typically,
2090 * characters that appear together multiple times in non-table text).
2091 */
2092 static const int GAGT_THRESHOLD = 4,
2093 GAGT_COMMON_THRESHOLD = 8;
2094 static const char *const GAGT_COMMON_PUNCTUATION = ".!?";
2095
2096
2097 /*
2098 * gagt_line_is_standout()
2099 *
2100 * Return TRUE if a page buffer line appears to contain "standout" text.
2101 * This is one of:
2102 * - a line where all characters have some form of AGT text attribute
2103 * set (blinking, fixed width font, or emphasis),
2104 * - a line where each alphabetical character is uppercase.
2105 * Typically, this describes room and other miscellaneous header lines.
2106 */
gagt_line_is_standout(const gagt_lineref_t line)2107 static int gagt_line_is_standout(const gagt_lineref_t line) {
2108 int index, all_formatted, upper_count, lower_count;
2109
2110 /*
2111 * Look at the line, for cases where all characters in it have AGT font
2112 * attributes, and counting the upper and lower case characters. Iterate
2113 * over only the significant characters in the string.
2114 */
2115 all_formatted = TRUE;
2116 upper_count = lower_count = 0;
2117 for (index = line->indent;
2118 index < line->buffer.length - line->outdent; index++) {
2119 gagt_attrset_t attribute_set;
2120 unsigned char character;
2121
2122 gagt_unpack_attributes(line->buffer.attributes[index], &attribute_set);
2123 character = line->buffer.data[index];
2124
2125 /*
2126 * If no AGT attribute is set for this character, then not all of the
2127 * line is standout text. In this case, reset the all_formatted flag.
2128 */
2129 if (!(attribute_set.blink
2130 || attribute_set.fixed || attribute_set.emphasis))
2131 all_formatted = FALSE;
2132
2133 /* Count upper and lower case characters. */
2134 if (islower(character))
2135 lower_count++;
2136 else if (isupper(character))
2137 upper_count++;
2138 }
2139
2140 /*
2141 * Consider standout if every character was formatted, or if the string
2142 * is all uppercase.
2143 */
2144 return all_formatted || (upper_count > 0 && lower_count == 0);
2145 }
2146
2147
2148 /*
2149 * gagt_set_font_hint_proportional()
2150 * gagt_set_font_hint_proportional_newline()
2151 * gagt_set_font_hint_fixed_width()
2152 *
2153 * Helpers for assigning font hints. Font hints have strengths, and these
2154 * functions ensure that gagt_assign_paragraph_font_hints() only increases
2155 * strengths, and doesn't need to worry about checking before setting. In
2156 * the case of newline, the function also adds standout to the font hint if
2157 * appropriate.
2158 */
gagt_set_font_hint_proportional(gagt_lineref_t line)2159 static void gagt_set_font_hint_proportional(gagt_lineref_t line) {
2160 /* The only weaker hint than proportional is none. */
2161 if (line->font_hint == HINT_NONE)
2162 line->font_hint = HINT_PROPORTIONAL;
2163 }
2164
gagt_set_font_hint_proportional_newline(gagt_lineref_t line)2165 static void gagt_set_font_hint_proportional_newline(gagt_lineref_t line) {
2166 /*
2167 * Proportional and none are weaker than newline. Because of the way we
2168 * set font hints, this function can't be called with a current line hint
2169 * of proportional newline.
2170 */
2171 if (line->font_hint == HINT_NONE || line->font_hint == HINT_PROPORTIONAL) {
2172 if (gagt_line_is_standout(line))
2173 line->font_hint = HINT_PROPORTIONAL_NEWLINE_STANDOUT;
2174 else
2175 line->font_hint = HINT_PROPORTIONAL_NEWLINE;
2176 }
2177 }
2178
gagt_set_font_hint_fixed_width(gagt_lineref_t line)2179 static void gagt_set_font_hint_fixed_width(gagt_lineref_t line) {
2180 /* Fixed width font is the strongest hint. */
2181 if (line->font_hint == HINT_NONE
2182 || line->font_hint == HINT_PROPORTIONAL
2183 || line->font_hint == HINT_PROPORTIONAL_NEWLINE
2184 || line->font_hint == HINT_PROPORTIONAL_NEWLINE_STANDOUT)
2185 line->font_hint = HINT_FIXED_WIDTH;
2186 }
2187
2188
2189 /*
2190 * gagt_assign_paragraph_font_hints()
2191 *
2192 * For a given paragraph in the page buffer, this function looks at the text
2193 * style used, and assigns a font hint value to each line. Font hints
2194 * indicate whether the line probably requires fixed width font, or may be
2195 * okay in variable width, and for lines that look like they might be okay
2196 * in variable width, whether the newline should probably be rendered at the
2197 * end of the line, or if it might be omitted.
2198 */
gagt_assign_paragraph_font_hints(const gagt_paragraphref_t paragraph)2199 static void gagt_assign_paragraph_font_hints(const gagt_paragraphref_t paragraph) {
2200 static int is_initialized = FALSE;
2201 static int threshold[BYTE_MAX_VAL + 1];
2202
2203 gagt_lineref_t line, first_line;
2204 int is_table, in_list;
2205 assert(paragraph);
2206
2207 /* On first call, set up the table on punctuation run thresholds. */
2208 if (!is_initialized) {
2209 int character;
2210
2211 for (character = 0; character <= BYTE_MAX_VAL; character++) {
2212 /*
2213 * Set the threshold, either a normal value, or a larger one for
2214 * punctuation characters that tend to have consecutive runs in
2215 * non-table text.
2216 */
2217 if (ispunct(character)) {
2218 threshold[character] = strchr(GAGT_COMMON_PUNCTUATION, character)
2219 ? GAGT_COMMON_THRESHOLD : GAGT_THRESHOLD;
2220 }
2221 }
2222
2223 is_initialized = TRUE;
2224 }
2225
2226 /*
2227 * Note the first paragraph line. This value is commonly used, and under
2228 * certain circumstances, it's also modified later on.
2229 */
2230 first_line = gagt_get_first_paragraph_line(paragraph);
2231 assert(first_line);
2232
2233 /*
2234 * Phase 1 -- look for pages that consist of just one paragraph,
2235 * itself consisting of only one line.
2236 *
2237 * There is no point in attempting alignment of text in a one paragraph,
2238 * one line page. This would be, for example, an error message from the
2239 * interpreter parser. In this case, set the line for proportional with
2240 * newline, and return immediately.
2241 */
2242 if (gagt_get_first_paragraph() == paragraph
2243 && !gagt_get_next_paragraph(paragraph)
2244 && !gagt_get_next_paragraph_line(first_line)) {
2245 /*
2246 * Set the first paragraph line for proportional with a newline, and
2247 * return.
2248 */
2249 gagt_set_font_hint_proportional_newline(first_line);
2250 return;
2251 }
2252
2253 /*
2254 * Phase 2 -- try to identify paragraphs that are tables, based on
2255 * looking for runs of punctuation.
2256 *
2257 * Search for any string that has a run of apparent line drawing or other
2258 * formatting characters in it. If we find one, we'll consider the
2259 * paragraph to be a "table", that is, it has some quality that we might
2260 * destroy if we used a proportional font.
2261 */
2262 is_table = FALSE;
2263 for (line = first_line;
2264 line && !is_table; line = gagt_get_next_paragraph_line(line)) {
2265 int index, counts[BYTE_MAX_VAL + 1], total_counts;
2266
2267 /*
2268 * Clear the initial counts. Using memset() here is an order of
2269 * magnitude or two faster than a for-loop. Also there's a total count
2270 * to detect when counts needs to be recleared, or is already clear.
2271 */
2272 memset(counts, 0, sizeof(counts));
2273 total_counts = 0;
2274
2275 /*
2276 * Count consecutive punctuation in the line, excluding the indentation
2277 * and outdent.
2278 */
2279 for (index = line->indent;
2280 index < line->buffer.length - line->outdent && !is_table; index++) {
2281 int character;
2282 character = line->buffer.data[index];
2283
2284 /* Test this character for punctuation. */
2285 if (ispunct(character)) {
2286 /*
2287 * Increment the count for this character, and note that
2288 * counts are no longer empty, then compare against threshold.
2289 */
2290 counts[character]++;
2291 total_counts++;
2292
2293 is_table = (counts[character] >= threshold[character]);
2294 } else {
2295 /*
2296 * Re-clear all counts, again with memset() for speed, but only
2297 * if they need clearing. As they often won't, this optimization
2298 * saves quite a bit of work.
2299 */
2300 if (total_counts > 0) {
2301 memset(counts, 0, sizeof(counts));
2302 total_counts = 0;
2303 }
2304 }
2305 }
2306 }
2307
2308 /*
2309 * Phase 3 -- try again to identify paragraphs that are tables, based
2310 * this time on looking for runs of whitespace.
2311 *
2312 * If no evidence found so far, look again, this time searching for any
2313 * run of four or more spaces on the line (excluding any lead-in or
2314 * trailing spaces).
2315 */
2316 if (!is_table) {
2317 for (line = first_line;
2318 line && !is_table; line = gagt_get_next_paragraph_line(line)) {
2319 int index, count;
2320
2321 /*
2322 * Count consecutive spaces in the line, excluding the indentation
2323 * and outdent.
2324 */
2325 count = 0;
2326 for (index = line->indent;
2327 index < line->buffer.length - line->outdent && !is_table;
2328 index++) {
2329 int character;
2330 character = line->buffer.data[index];
2331
2332 if (isspace(character)) {
2333 count++;
2334 is_table = (count >= GAGT_THRESHOLD);
2335 } else
2336 count = 0;
2337 }
2338 }
2339 }
2340
2341 /*
2342 * If the paragraph appears to be a table, and if it consists of more than
2343 * just a single line, mark all lines as fixed font output and return.
2344 */
2345 if (is_table && gagt_get_next_paragraph_line(first_line)) {
2346 for (line = first_line;
2347 line; line = gagt_get_next_paragraph_line(line)) {
2348 gagt_set_font_hint_fixed_width(line);
2349 }
2350
2351 /* Nothing more to do. */
2352 return;
2353 }
2354
2355 /*
2356 * Phase 4 -- consider separating the first line from the rest of
2357 * the paragraph.
2358 *
2359 * Not a table, so the choice is between proportional rendering with a
2360 * newline, and proportional rendering without...
2361 *
2362 * If the first paragraph line is standout or short, render it pro-
2363 * portionally with a newline, and don't consider it as a further part of
2364 * the paragraph.
2365 */
2366 if (gagt_line_is_standout(first_line)
2367 || first_line->real_length < screen_width / 2) {
2368 /* Set the first paragraph line for a newline. */
2369 gagt_set_font_hint_proportional_newline(first_line);
2370
2371 /*
2372 * Disassociate this line from the rest of the paragraph by moving on
2373 * the value of the first_line variable. If it turns out that there
2374 * is no next paragraph line, then we have a one-line paragraph, and
2375 * there's no more to do.
2376 */
2377 first_line = gagt_get_next_paragraph_line(first_line);
2378 if (!first_line)
2379 return;
2380 }
2381
2382 /*
2383 * Phase 5 -- try to identify lists by a simple initial look at line
2384 * indentations.
2385 *
2386 * Look through the paragraph for apparent lists, and decide for each
2387 * line whether it's appropriate to output a newline, and render
2388 * proportionally, or just render proportionally.
2389 *
2390 * After this loop, each line will have some form of font hint assigned
2391 * to it.
2392 */
2393 in_list = FALSE;
2394 for (line = first_line;
2395 line; line = gagt_get_next_paragraph_line(line)) {
2396 gagt_lineref_t next_line;
2397
2398 next_line = gagt_get_next_paragraph_line(line);
2399
2400 /*
2401 * Special last-iteration processing. The newline is always output at
2402 * the end of a paragraph, so if there isn't a next line, then this
2403 * line is the last paragraph line. Set its font hint appropriately,
2404 * and do no more for the line.
2405 */
2406 if (!next_line) {
2407 gagt_set_font_hint_proportional_newline(line);
2408 continue;
2409 }
2410
2411 /*
2412 * If the next line's indentation is deeper that that of the first
2413 * line, this paragraph looks like it is trying to be some form of a
2414 * list. In this case, make newline significant for the current line,
2415 * and set the in_list flag so we can delay the return to proportional
2416 * by one line. On return to first line indentation, make newline
2417 * significant for the return line.
2418 */
2419 if (next_line->indent > first_line->indent) {
2420 gagt_set_font_hint_proportional_newline(line);
2421 in_list = TRUE;
2422 } else {
2423 if (in_list)
2424 gagt_set_font_hint_proportional_newline(line);
2425 else
2426 gagt_set_font_hint_proportional(line);
2427 in_list = FALSE;
2428 }
2429 }
2430
2431 /*
2432 * Phase 6 -- look again for lines that look like they are supposed
2433 * to stand out from their neighbors.
2434 *
2435 * Now rescan the paragraph, looking this time for lines that stand out
2436 * from their neighbours. Make newline significant for each such line,
2437 * and the line above, if there is one.
2438 *
2439 * Here we split the loop on lines so that we avoid looking at the prior
2440 * line of the current first line -- because of "adjustments", it may not
2441 * be the real paragraph first line.
2442 *
2443 * So, deal with the current first line...
2444 */
2445 if (gagt_line_is_standout(first_line)) {
2446 /* Make newline significant for this line. */
2447 gagt_set_font_hint_proportional_newline(first_line);
2448 }
2449
2450 /* ... then deal with the rest of the lines, looking for standouts. */
2451 for (line = gagt_get_next_paragraph_line(first_line);
2452 line; line = gagt_get_next_paragraph_line(line)) {
2453 if (gagt_line_is_standout(line)) {
2454 gagt_lineref_t prior_line;
2455
2456 /* Make newline significant for this line. */
2457 gagt_set_font_hint_proportional_newline(line);
2458
2459 /*
2460 * Make newline significant for the line above. There will always
2461 * be one because we start the loop past the first line.
2462 */
2463 prior_line = gagt_get_prior_paragraph_line(line);
2464 gagt_set_font_hint_proportional_newline(prior_line);
2465 }
2466 }
2467
2468 /*
2469 * Phase 7 -- special case short lines at the paragraph start.
2470 *
2471 * Make a special case of lines that begin a paragraph, and are short and
2472 * followed by a much longer line. This should catch games which output
2473 * room titles above descriptions without using AGT fonts/bold/whatever.
2474 * Without this trap, room titles and their descriptions are run together.
2475 * This is more programmatic guesswork than heuristics.
2476 */
2477 if (gagt_get_next_paragraph_line(first_line)) {
2478 gagt_lineref_t next_line;
2479
2480 next_line = gagt_get_next_paragraph_line(first_line);
2481
2482 /*
2483 * See if the first line is less than half width, and the second line
2484 * is more than three quarters width. If it is, set newline as
2485 * significant for the first paragraph line.
2486 */
2487 if (first_line->real_length < screen_width / 2
2488 && next_line->real_length > screen_width * 3 / 4) {
2489 gagt_set_font_hint_proportional_newline(first_line);
2490 }
2491 }
2492
2493 /*
2494 * Phase 8 -- special case paragraphs of only short lines.
2495 *
2496 * Make a special case out of paragraphs where all lines are short. This
2497 * catches elements like indented addresses.
2498 */
2499 if (gagt_get_next_paragraph_line(first_line)) {
2500 int all_short;
2501
2502 all_short = TRUE;
2503 for (line = first_line;
2504 line; line = gagt_get_next_paragraph_line(line)) {
2505 /* Clear flag if this line isn't 'short'. */
2506 if (line->real_length >= screen_width / 2) {
2507 all_short = FALSE;
2508 break;
2509 }
2510 }
2511
2512 /*
2513 * If all lines were short, mark the complete paragraph as having
2514 * significant newlines.
2515 */
2516 if (all_short) {
2517 for (line = first_line;
2518 line; line = gagt_get_next_paragraph_line(line)) {
2519 gagt_set_font_hint_proportional_newline(line);
2520 }
2521 }
2522 }
2523 }
2524
2525
2526 /*
2527 * gagt_assign_font_hints()
2528 *
2529 *
2530 * Sets a font hint for each line of each page buffer paragraph that is not
2531 * a special paragraph.
2532 */
gagt_assign_font_hints()2533 static void gagt_assign_font_hints() {
2534 gagt_paragraphref_t paragraph;
2535
2536 for (paragraph = gagt_get_first_paragraph();
2537 paragraph; paragraph = gagt_get_next_paragraph(paragraph)) {
2538 if (!paragraph->special)
2539 gagt_assign_paragraph_font_hints(paragraph);
2540 }
2541 }
2542
2543
2544 /*---------------------------------------------------------------------*/
2545 /* Glk port special paragraph functions and data */
2546 /*---------------------------------------------------------------------*/
2547
2548 /*
2549 * It's helpful to handle some AGiliTy interpreter output specially, to im-
2550 * prove the look of the text where Glk fonts and styles are available. We
2551 * build a table of paragraphs the interpreter can come out with, and the
2552 * replacement text we'll use when we see this paragraph. Note that matches
2553 * are made after factoring out indentation, and replacement lines do not
2554 * automatically print with a newline. All clear, then? Here's the table
2555 * entry definition.
2556 */
2557 enum { GAGT_SPECIAL_MATCH_MAX = 5 };
2558
2559 typedef const struct gagt_special_s {
2560 const int line_count;
2561 const char *const compare[GAGT_SPECIAL_MATCH_MAX + 1];
2562 const char *const replace;
2563 } gagt_special_t;
2564
2565 /*
2566 * Table of special AGiliTy interpreter strings and paragraphs -- where one
2567 * appears in game output, we'll print out its replacement instead. Be
2568 * warned; these strings are VERY specific to AGiliTy 1.1.1.1, and are extre-
2569 * mely likely to change with any future interpreter releases. They also
2570 * omit initializers with abandon, expecting the compiler to default these
2571 * to NULL/zero. Replacement strings embed style encoding as |x, where x is
2572 * E(mphasized), S(ubheader), or N(ormal) for convenience.
2573 */
2574 static gagt_special_t GAGT_SPECIALS[] = {
2575
2576 /* Initial screen AGT game type line. */
2577 {
2578 1,
2579 {"[Created with Malmberg and Welch's Adventure Game Toolkit]"},
2580 "|ECreated with Malmberg and Welch's Adventure Game Toolkit|N\n"
2581 },
2582
2583 /* Normal version of initial interpreter information block. */
2584 {
2585 4,
2586 {
2587 "This game is being executed by",
2588 "AGiliTy: The (Mostly) Universal AGT Interpreter version 1.1.1.1",
2589 "Copyright (C) 1996-99,2001 by Robert Masenten",
2590 "Glk version"
2591 },
2592 "This game is being executed by:\n\n"
2593 " |SAGiliTy, The (Mostly) Universal AGT Interpreter, Version 1.1.1.1|N\n"
2594 " |ECopyright (C) 1996-1999,2001 by Robert Masenten|N\n"
2595 " |EGlk version|N\n"
2596 },
2597
2598 /* AGiliTy "information" screen header block. */
2599 {
2600 5,
2601 {
2602 "AGiliTy",
2603 "The (Mostly) Universal AGT Interpreter, version 1.1.1.1",
2604 "Copyright (C) 1996-1999,2001 by Robert Masenten",
2605 "[Glk version]",
2606 "-----------------------------------------------------------"
2607 },
2608 "|SAGiliTy, The (Mostly) Universal AGT Interpreter, Version 1.1.1.1|N\n"
2609 "|ECopyright (C) 1996-1999,2001 by Robert Masenten|N\n"
2610 "|EGlk version|N\n"
2611 },
2612
2613 /* "HIT ANY KEY" message, usually displayed after a game's introduction. */
2614 {
2615 1,
2616 {"--- HIT ANY KEY ---"},
2617 "|E[Press any key...]|N"
2618 },
2619
2620 /* Alternative, shrunken version of initial interpreter information block. */
2621 {
2622 2,
2623 {
2624 "Being run by AGiliTy version 1.1.1.1, Copyright (C) 1996-99,2001"
2625 " Robert Masenten",
2626 "Glk version"
2627 },
2628 "This game is being executed by:\n\n"
2629 " |SAGiliTy, The (Mostly) Universal AGT Interpreter, Version 1.1.1.1|N\n"
2630 " |ECopyright (C) 1996-1999,2001 by Robert Masenten|N\n"
2631 " |EGlk version|N\n"
2632 },
2633
2634 /* Alternative, minimal version of initial interpreter information block. */
2635 {
2636 1,
2637 {
2638 "Being run by AGiliTy version 1.1.1.1, Copyright (C) 1996-99,2001"
2639 " Robert Masenten"
2640 },
2641 "This game is being executed by:\n\n"
2642 " |SAGiliTy, The (Mostly) Universal AGT Interpreter, Version 1.1.1.1|N\n"
2643 " |ECopyright (C) 1996-1999,2001 by Robert Masenten|N\n"
2644 " |EGlk version|N\n"
2645 },
2646
2647 /* Lengthy version of the "Created with..." message. */
2648 {
2649 2,
2650 {
2651 "This game was created with Malmberg and Welch's Adventure Game Toolkit;"
2652 " it is",
2653 "being executed by"
2654 },
2655 "|ECreated with Malmberg and Welch's Adventure Game Toolkit|N\n"
2656 },
2657
2658 /* Three-line version of initial interpreter information block. */
2659 {
2660 3,
2661 {
2662 "AGiliTy: The (Mostly) Universal AGT Interpreter version 1.1.1.1",
2663 "Copyright (C) 1996-99,2001 by Robert Masenten",
2664 "Glk version"
2665 },
2666 "This game is being executed by:\n\n"
2667 " |SAGiliTy, The (Mostly) Universal AGT Interpreter, Version 1.1.1.1|N\n"
2668 " |ECopyright (C) 1996-1999,2001 by Robert Masenten|N\n"
2669 " |EGlk version|N\n"
2670 },
2671
2672 /*
2673 * Assorted special verb output messages, with the extra icky quality that
2674 * we have to spot messages that wrap because we forced screen_width to 80.
2675 */
2676 {
2677 2,
2678 {
2679 "[Now in BRIEF mode (room descriptions will only be printed"
2680 " when they are entered",
2681 "the first time)]"
2682 },
2683 "|E[Now in BRIEF mode: Room descriptions will only be printed"
2684 " when rooms are entered for the first time.]|N\n"
2685 },
2686
2687 {
2688 2,
2689 {
2690 "[Now in VERBOSE mode (room descriptions will be printed"
2691 " every time you enter a",
2692 "room)]"
2693 },
2694 "|E[Now in VERBOSE mode: Room descriptions will be printed"
2695 " every time you enter a room.]|N\n"
2696 },
2697
2698 {
2699 1,
2700 {"[LISTEXIT mode on: room exits will be listed.]"},
2701 "|E[LISTEXIT mode on: Room exits will be listed.]|N\n"
2702 },
2703
2704 {
2705 1,
2706 {"[LISTEXIT mode off: room exits will not be listed.]"},
2707 "|E[LISTEXIT mode off: Room exits will not be listed.]|N\n"
2708 },
2709
2710 /* End of table sentinel entry. Do not delete. */
2711 {0, {NULL}, NULL}
2712 };
2713
2714
2715 /*
2716 * gagt_compare_special_line()
2717 * gagt_compare_special_paragraph()
2718 *
2719 * Helpers for gagt_find_equivalent_special(). Compare line data case-
2720 * insensitively, taking care to use lengths rather than relying on line
2721 * buffer data being NUL terminated (which it's not); and iterate a complete
2722 * special paragraph comparison.
2723 */
gagt_compare_special_line(const char * compare,const gagt_lineref_t line)2724 static int gagt_compare_special_line(const char *compare, const gagt_lineref_t line) {
2725 /*
2726 * Return true if the lengths match, and the real line data (excluding
2727 * indent and outdent) also matches, ignoring case.
2728 */
2729 return (int)strlen(compare) == line->real_length
2730 && gagt_strncasecmp(compare,
2731 (const char *)line->buffer.data + line->indent,
2732 line->real_length) == 0;
2733 }
2734
gagt_compare_special_paragraph(const gagt_specialref_t special,const gagt_paragraphref_t paragraph)2735 static int gagt_compare_special_paragraph(const gagt_specialref_t special,
2736 const gagt_paragraphref_t paragraph) {
2737 /* If the line counts match, compare line by line. */
2738 if (special->line_count == gagt_get_paragraph_line_count(paragraph)) {
2739 gagt_lineref_t line;
2740 int index, is_match;
2741
2742 is_match = TRUE;
2743 for (index = 0, line = gagt_get_first_paragraph_line(paragraph);
2744 index < special->line_count && line;
2745 index++, line = gagt_get_next_paragraph_line(line)) {
2746 if (!gagt_compare_special_line(special->compare[index], line)) {
2747 is_match = FALSE;
2748 break;
2749 }
2750 }
2751
2752 return is_match;
2753 }
2754
2755 /* Line count mismatch; return FALSE. */
2756 return FALSE;
2757 }
2758
2759
2760 /*
2761 * gagt_find_equivalent_special()
2762 *
2763 * Given a paragraph, see if it matches any of the special ones set up in
2764 * our array. Returns the special, or NULL if no match.
2765 */
gagt_find_equivalent_special(gagt_paragraphref_t paragraph)2766 static gagt_specialref_t gagt_find_equivalent_special(gagt_paragraphref_t paragraph) {
2767 gagt_specialref_t special, match;
2768
2769 /* Check each special paragraph entry for a match against this paragraph. */
2770 match = NULL;
2771 for (special = GAGT_SPECIALS; special->replace; special++) {
2772 if (gagt_compare_special_paragraph(special, paragraph)) {
2773 match = special;
2774 break;
2775 }
2776 }
2777
2778 return match;
2779 }
2780
2781
2782 /*
2783 * gagt_mark_specials()
2784 *
2785 * Search for and mark any lines that match special paragraphs.
2786 */
gagt_mark_specials()2787 static void gagt_mark_specials() {
2788 static int is_verified = FALSE;
2789
2790 /*
2791 * Verify special paragraphs table contents. This checks that each entry
2792 * ends with a NULL comparison, has a replacement, and that the line count
2793 * matches.
2794 */
2795 if (!is_verified) {
2796 gagt_specialref_t special;
2797
2798 for (special = GAGT_SPECIALS; special->replace; special++) {
2799 int line_count, index;
2800
2801 line_count = 0;
2802 for (index = 0; special->compare[index]; index++)
2803 line_count++;
2804
2805 assert(special->line_count == line_count);
2806 assert(special->replace);
2807 assert(!special->compare[GAGT_SPECIAL_MATCH_MAX]);
2808 }
2809
2810 is_verified = TRUE;
2811 }
2812
2813 /*
2814 * Search all paragraphs for special matches, if enabled. When a special
2815 * match is found, mark the paragraph with a pointer to the matching entry.
2816 */
2817 if (g_vm->gagt_replacement_enabled) {
2818 gagt_paragraphref_t paragraph;
2819
2820 for (paragraph = gagt_get_first_paragraph();
2821 paragraph; paragraph = gagt_get_next_paragraph(paragraph)) {
2822 paragraph->special = gagt_find_equivalent_special(paragraph);
2823 }
2824 }
2825 }
2826
2827
2828 /*
2829 * gagt_display_special()
2830 *
2831 * Display the replacement text for the specified special table entry. The
2832 * current Glk style in force is passed in; we return the Glk style in force
2833 * after we've done.
2834 */
gagt_display_special(const gagt_specialref_t special,glui32 current_style)2835 static glui32 gagt_display_special(const gagt_specialref_t special, glui32 current_style) {
2836 glui32 set_style;
2837 int index, marker, length;
2838 const char *string;
2839 assert(special);
2840
2841 /* Extract replacement string and length. */
2842 string = special->replace;
2843 assert(string);
2844 length = strlen(string);
2845
2846 set_style = current_style;
2847
2848 /*
2849 * Iterate each character in replacement string, looking for style escapes,
2850 * and flushing delayed output when one is found.
2851 */
2852 marker = 0;
2853 for (index = 0; index < length; index++) {
2854 if (string[index] == '|') {
2855 glui32 style;
2856
2857 /* Flush delayed output accumulated so far, excluding escape. */
2858 g_vm->glk_put_buffer(string + marker, index - marker);
2859 marker = index + 2;
2860
2861 /* Determine any new text style. */
2862 style = set_style;
2863 switch (string[++index]) {
2864 case 'E':
2865 style = style_Emphasized;
2866 break;
2867
2868 case 'S':
2869 style = style_Subheader;
2870 break;
2871
2872 case 'N':
2873 style = style_Normal;
2874 break;
2875
2876 default:
2877 gagt_fatal("GLK: Invalid replacement style escape");
2878 gagt_exit();
2879 }
2880
2881 /* If style changed, update Glk's style setting. */
2882 if (style != set_style) {
2883 g_vm->glk_set_style(style);
2884 set_style = style;
2885 }
2886 }
2887 }
2888
2889 /* Output any remaining delayed characters. */
2890 if (marker < length)
2891 g_vm->glk_put_buffer(string + marker, length - marker);
2892
2893 return set_style;
2894 }
2895
2896
2897 /*---------------------------------------------------------------------*/
2898 /* Glk port output functions */
2899 /*---------------------------------------------------------------------*/
2900
2901 /*
2902 * Flag for if the user entered "help" as their last input, or if hints have
2903 * been silenced as a result of already using a Glk command.
2904 */
2905 static int gagt_help_requested = FALSE,
2906 gagt_help_hints_silenced = FALSE;
2907
2908 /*
2909 * gagt_display_register_help_request()
2910 * gagt_display_silence_help_hints()
2911 * gagt_display_provide_help_hint()
2912 *
2913 * Register a request for help, and print a note of how to get Glk command
2914 * help from the interpreter unless silenced.
2915 */
gagt_display_register_help_request()2916 static void gagt_display_register_help_request() {
2917 gagt_help_requested = TRUE;
2918 }
2919
gagt_display_silence_help_hints()2920 static void gagt_display_silence_help_hints() {
2921 gagt_help_hints_silenced = TRUE;
2922 }
2923
gagt_display_provide_help_hint(glui32 current_style)2924 static glui32 gagt_display_provide_help_hint(glui32 current_style) {
2925 if (gagt_help_requested && !gagt_help_hints_silenced) {
2926 g_vm->glk_set_style(style_Emphasized);
2927 g_vm->glk_put_string("[Try 'glk help' for help on special interpreter"
2928 " commands]\n");
2929
2930 gagt_help_requested = FALSE;
2931 return style_Emphasized;
2932 }
2933
2934 return current_style;
2935 }
2936
2937
2938 /*
2939 * gagt_display_text_element()
2940 *
2941 * Display an element of a buffer string using matching packed attributes.
2942 * The currently set Glk style is supplied, and the function returns the
2943 * new currently set Glk style.
2944 *
2945 * The function handles a flag to coerce fixed width font.
2946 */
gagt_display_text_element(const char * string,const unsigned char * attributes,int length,glui32 current_style,int fixed_width)2947 static glui32 gagt_display_text_element(const char *string, const unsigned char *attributes,
2948 int length, glui32 current_style, int fixed_width) {
2949 int marker, index;
2950 glui32 set_style;
2951 assert(g_vm->glk_stream_get_current());
2952
2953 set_style = current_style;
2954
2955 /*
2956 * Iterate each character in the line range. We actually delay output
2957 * until we see a change in style; that way, we can send a buffer of
2958 * characters to Glk, rather than sending them just one at a time.
2959 */
2960 marker = 0;
2961 for (index = 0; index < length; index++) {
2962 gagt_attrset_t attribute_set;
2963 glui32 style;
2964 assert(attributes && string);
2965
2966 /*
2967 * Unpack the AGT font attributes for this character, and add fixed
2968 * width font coercion.
2969 */
2970 gagt_unpack_attributes(attributes[index], &attribute_set);
2971 attribute_set.fixed |= fixed_width;
2972
2973 /*
2974 * Decide on any applicable new Glk text styling. If it's different
2975 * to the current style, output the delayed characters, and update
2976 * Glk's style setting.
2977 */
2978 style = gagt_select_style(&attribute_set);
2979 if (style != set_style) {
2980 g_vm->glk_put_buffer(string + marker, index - marker);
2981 marker = index;
2982
2983 g_vm->glk_set_style(style);
2984 set_style = style;
2985 }
2986 }
2987
2988 /* Output any remaining delayed characters. */
2989 if (marker < length)
2990 g_vm->glk_put_buffer(string + marker, length - marker);
2991
2992 return set_style;
2993 }
2994
2995
2996 /*
2997 * gagt_display_line()
2998 *
2999 * Display a page buffer line, starting in the current Glk style, and
3000 * returning the new current Glk style.
3001 *
3002 * The function takes additional flags to force fixed width font, skip over
3003 * indentation and trailing line whitespace, and trim hyphens (if skipping
3004 * trailing whitespace).
3005 */
gagt_display_line(const gagt_lineref_t line,glui32 current_style,int fixed_width,int skip_indent,int skip_outdent,int trim_hyphen)3006 static glui32 gagt_display_line(const gagt_lineref_t line, glui32 current_style,
3007 int fixed_width, int skip_indent, int skip_outdent,
3008 int trim_hyphen) {
3009 int start, length;
3010 glui32 set_style;
3011
3012 /*
3013 * Check the skip indent flag to find the first character to display, and
3014 * the count of characters to display.
3015 */
3016 start = 0;
3017 length = line->buffer.length;
3018 if (skip_indent) {
3019 start += line->indent;
3020 length -= line->indent;
3021 }
3022
3023 /* Adjust length for skipping outdent and trimming hyphens. */
3024 if (skip_outdent) {
3025 length -= line->outdent;
3026 if (trim_hyphen && line->is_hyphenated)
3027 length--;
3028 }
3029
3030 /* Display this line segment. */
3031 set_style = gagt_display_text_element((const char *)line->buffer.data + start,
3032 line->buffer.attributes + start,
3033 length, current_style, fixed_width);
3034
3035 return set_style;
3036 }
3037
3038
3039 /*
3040 * gagt_display_hinted_line()
3041 *
3042 * Display a page buffer line, starting in the current Glk style, and
3043 * returning the new current Glk style. The function uses the font hints
3044 * from the line, and receives the font hint of the prior line.
3045 */
gagt_display_hinted_line(const gagt_lineref_t line,glui32 current_style,gagt_font_hint_t prior_hint)3046 static glui32 gagt_display_hinted_line(const gagt_lineref_t line, glui32 current_style,
3047 gagt_font_hint_t prior_hint) {
3048 glui32 style;
3049
3050 style = current_style;
3051 switch (line->font_hint) {
3052 case HINT_FIXED_WIDTH:
3053 /* Force fixed width font on the line. */
3054 style = gagt_display_line(line, style, TRUE, FALSE, FALSE, FALSE);
3055
3056 g_vm->glk_put_char('\n');
3057 break;
3058
3059 case HINT_PROPORTIONAL:
3060 /*
3061 * Permit proportional font, and suppress outdent. Suppress indent
3062 * too if this line follows a line that suppressed newline, or is the
3063 * first line in the paragraph. For all cases, trim the hyphen from
3064 * hyphenated lines.
3065 */
3066 if (prior_hint == HINT_PROPORTIONAL || prior_hint == HINT_NONE)
3067 style = gagt_display_line(line, style, FALSE, TRUE, TRUE, TRUE);
3068 else
3069 style = gagt_display_line(line, style, FALSE, FALSE, TRUE, TRUE);
3070
3071 /*
3072 * Where the line is not hyphenated, output a space in place of newline.
3073 * This lets paragraph text to flow to the full display width.
3074 */
3075 if (!line->is_hyphenated)
3076 g_vm->glk_put_char(' ');
3077 break;
3078
3079 case HINT_PROPORTIONAL_NEWLINE:
3080 case HINT_PROPORTIONAL_NEWLINE_STANDOUT:
3081 /*
3082 * As above, permit proportional font, suppress outdent, and suppress
3083 * indent too under certain conditions; in this case, only when the
3084 * prior line suppressed newline.
3085 */
3086 if (prior_hint == HINT_PROPORTIONAL)
3087 style = gagt_display_line(line, style, FALSE, TRUE, TRUE, FALSE);
3088 else
3089 style = gagt_display_line(line, style, FALSE, FALSE, TRUE, FALSE);
3090
3091 g_vm->glk_put_char('\n');
3092 break;
3093
3094 case HINT_NONE:
3095 gagt_fatal("GLK: Page buffer line with no font hint");
3096 gagt_exit();
3097 break;
3098
3099 default:
3100 gagt_fatal("GLK: Invalid font hint encountered");
3101 gagt_exit();
3102 break;
3103 }
3104
3105 return style;
3106 }
3107
3108
3109 /*
3110 * gagt_display_auto()
3111 *
3112 * Display buffered output text to the Glk main window using a bunch of
3113 * occasionally rather dodgy heuristics to try to automatically set a suitable
3114 * font for the way the text is structured, while replacing special paragraphs
3115 * with altered text.
3116 */
gagt_display_auto()3117 static void gagt_display_auto() {
3118 gagt_paragraphref_t paragraph;
3119 glui32 style;
3120
3121 style = style_Normal;
3122 g_vm->glk_set_style(style);
3123
3124 /* Handle each paragraph. */
3125 for (paragraph = gagt_get_first_paragraph();
3126 paragraph; paragraph = gagt_get_next_paragraph(paragraph)) {
3127 /* If a special paragraph, output replacement text instead. */
3128 if (paragraph->special) {
3129 style = gagt_display_special(paragraph->special, style);
3130 g_vm->glk_put_char('\n');
3131 } else {
3132 gagt_lineref_t line;
3133 gagt_font_hint_t prior_hint;
3134
3135 /* Get the first line of the paragraph. */
3136 line = gagt_get_first_paragraph_line(paragraph);
3137
3138 /*
3139 * Output a blank line where the first line of the first paragraph
3140 * is standout; this sets it apart from the prompt.
3141 */
3142 if (paragraph == gagt_get_first_paragraph()
3143 && line == gagt_get_first_paragraph_line(paragraph)) {
3144 if (line->font_hint == HINT_PROPORTIONAL_NEWLINE_STANDOUT)
3145 g_vm->glk_put_char('\n');
3146 }
3147
3148 /* Handle each line of the paragraph. */
3149 prior_hint = HINT_NONE;
3150 for (; line; line = gagt_get_next_paragraph_line(line)) {
3151 /*
3152 * Print this line according to its font hint, noting any change
3153 * of style and the line's font hint for use next iteration as
3154 * the prior hint.
3155 */
3156 style = gagt_display_hinted_line(line, style, prior_hint);
3157 prior_hint = line->font_hint;
3158 }
3159
3160 /* Output the newline for the end of the paragraph. */
3161 g_vm->glk_put_char('\n');
3162 }
3163 }
3164
3165 /* If no paragraphs at all, but a current buffer, output a newline. */
3166 if (!gagt_get_first_paragraph() && gagt_current_buffer.length > 0)
3167 g_vm->glk_put_char('\n');
3168
3169 /* Output any help hint and unterminated line from the line buffer. */
3170 style = gagt_display_provide_help_hint(style);
3171 style = gagt_display_text_element((const char *)gagt_current_buffer.data,
3172 gagt_current_buffer.attributes,
3173 gagt_current_buffer.length, style, FALSE);
3174 }
3175
3176
3177 /*
3178 * gagt_display_manual()
3179 *
3180 * Display buffered output text in the Glk main window, with either a fixed
3181 * width or a proportional font.
3182 */
gagt_display_manual(int fixed_width)3183 static void gagt_display_manual(int fixed_width) {
3184 gagt_lineref_t line;
3185 glui32 style;
3186
3187 style = style_Normal;
3188 g_vm->glk_set_style(style);
3189
3190 for (line = gagt_get_first_page_line();
3191 line; line = gagt_get_next_page_line(line)) {
3192 gagt_paragraphref_t paragraph;
3193
3194 paragraph = line->paragraph;
3195
3196 /*
3197 * If this is a special paragraph, display the replacement text on
3198 * its first line and ignore remaining special lines. Otherwise,
3199 * display the page buffer line using either fixed or proportional
3200 * font, as requested.
3201 */
3202 if (paragraph && paragraph->special) {
3203 if (gagt_get_first_paragraph_line(paragraph) == line)
3204 style = gagt_display_special(paragraph->special, style);
3205 } else {
3206 style = gagt_display_line(line, style, fixed_width,
3207 FALSE, FALSE, FALSE);
3208 g_vm->glk_put_char('\n');
3209 }
3210 }
3211
3212 /* Output any help hint and unterminated line from the line buffer. */
3213 style = gagt_display_provide_help_hint(style);
3214 style = gagt_display_text_element((const char *)gagt_current_buffer.data,
3215 gagt_current_buffer.attributes,
3216 gagt_current_buffer.length,
3217 style, fixed_width);
3218 }
3219
3220
3221 /*
3222 * gagt_display_debug()
3223 *
3224 * Display the analyzed page buffer in a form that shows all of its gory
3225 * detail.
3226 */
gagt_display_debug()3227 static void gagt_display_debug() {
3228 gagt_lineref_t line;
3229 char buffer[256];
3230
3231 g_vm->glk_set_style(style_Preformatted);
3232 for (line = gagt_get_first_page_line();
3233 line; line = gagt_get_next_page_line(line)) {
3234 gagt_paragraphref_t paragraph;
3235
3236 paragraph = line->paragraph;
3237 sprintf(buffer,
3238 "%2d:%2d->%2ld A=%-3d L=%-2d I=%-2d O=%-2d R=%-2d %c%c| ",
3239 paragraph ? paragraph->id + 1 : 0,
3240 paragraph ? paragraph->line_count : 0,
3241 paragraph && paragraph->special
3242 ? paragraph->special - GAGT_SPECIALS + 1 : 0,
3243 line->buffer.allocation, line->buffer.length,
3244 line->indent, line->outdent,
3245 line->real_length,
3246 line->is_hyphenated ? 'h' : '_',
3247 line->is_blank ? 'b' :
3248 line->font_hint == HINT_PROPORTIONAL ? 'P' :
3249 line->font_hint == HINT_PROPORTIONAL_NEWLINE ? 'N' :
3250 line->font_hint == HINT_PROPORTIONAL_NEWLINE_STANDOUT ? 'S' :
3251 line->font_hint == HINT_FIXED_WIDTH ? 'F' : '_');
3252 g_vm->glk_put_string(buffer);
3253
3254 g_vm->glk_put_buffer((const char *)line->buffer.data, line->buffer.length);
3255 g_vm->glk_put_char('\n');
3256 }
3257
3258 if (gagt_current_buffer.length > 0) {
3259 sprintf(buffer,
3260 "__,__->__ A=%-3d L=%-2d I=__ O=__ R=__ %s| ",
3261 gagt_current_buffer.allocation, gagt_current_buffer.length,
3262 gagt_help_requested ? "HR" : "__");
3263 g_vm->glk_put_string(buffer);
3264
3265 g_vm->glk_put_buffer((const char *)gagt_current_buffer.data, gagt_current_buffer.length);
3266 }
3267
3268 gagt_help_requested = FALSE;
3269 }
3270
3271
3272 /*
3273 * gagt_output_flush()
3274 *
3275 * Flush any buffered output text to the Glk main window, and clear the
3276 * buffer ready for new output text. The function concerns itself with
3277 * both the page buffer and any unterminated line in the line buffer.
3278 */
gagt_output_flush()3279 static void gagt_output_flush() {
3280 /*
3281 * Run the analysis of page buffer contents. This will fill in the
3282 * paragraph and font hints fields, any any applicable special pointer,
3283 * for every line held in the buffer.
3284 */
3285 gagt_paragraph_page();
3286 gagt_mark_specials();
3287 gagt_assign_font_hints();
3288
3289 /*
3290 * Select the appropriate display routine to use, and call it. The display
3291 * routines present somewhat different output, and are responsible for
3292 * displaying both the page buffer _and_ any buffered current line text.
3293 */
3294 switch (g_vm->gagt_font_mode) {
3295 case FONT_AUTOMATIC:
3296 gagt_display_auto();
3297 break;
3298
3299 case FONT_FIXED_WIDTH:
3300 gagt_display_manual(TRUE);
3301 break;
3302
3303 case FONT_PROPORTIONAL:
3304 gagt_display_manual(FALSE);
3305 break;
3306
3307 case FONT_DEBUG:
3308 gagt_display_debug();
3309 break;
3310
3311 default:
3312 gagt_fatal("GLK: Invalid font mode encountered");
3313 gagt_exit();
3314 }
3315
3316 /* Empty the buffer, ready for new game strings. */
3317 gagt_paragraphs_delete();
3318 gagt_output_delete();
3319 }
3320
3321
3322 /*
3323 * agt_clrscr()
3324 *
3325 * Clear the main playing area window. Although there may be little point
3326 * in flushing (rather than emptying) the buffers, nevertheless that is
3327 * what we do.
3328 */
agt_clrscr()3329 void agt_clrscr() {
3330 if (!BATCH_MODE) {
3331 /* Update the apparent (virtual) window x position. */
3332 curr_x = 0;
3333
3334 /* Flush any pending buffered output, and clear the main window. */
3335 gagt_output_flush();
3336 g_vm->glk_window_clear(g_vm->gagt_main_window);
3337
3338 /* Add a series of newlines to any script file. */
3339 if (script_on)
3340 textputs(scriptfile, "\n\n\n\n");
3341
3342 gagt_debug("agt_clrscr", "");
3343 }
3344 }
3345
3346
3347 /*
3348 * gagt_styled_string()
3349 * gagt_styled_char()
3350 * gagt_standout_string()
3351 * gagt_standout_char()
3352 * gagt_normal_string()
3353 * gagt_normal_char()
3354 * gagt_header_string()
3355 *
3356 * Convenience functions to print strings in assorted styles. A standout
3357 * string is one that hints that it's from the interpreter, not the game.
3358 */
gagt_styled_string(glui32 style,const char * message)3359 static void gagt_styled_string(glui32 style, const char *message) {
3360 assert(message);
3361
3362 g_vm->glk_set_style(style);
3363 g_vm->glk_put_string(message);
3364 g_vm->glk_set_style(style_Normal);
3365 }
3366
gagt_styled_char(glui32 style,char c)3367 static void gagt_styled_char(glui32 style, char c) {
3368 char buffer[2];
3369
3370 buffer[0] = c;
3371 buffer[1] = '\0';
3372 gagt_styled_string(style, buffer);
3373 }
3374
gagt_standout_string(const char * message)3375 static void gagt_standout_string(const char *message) {
3376 gagt_styled_string(style_Emphasized, message);
3377 }
3378
gagt_standout_char(char c)3379 static void gagt_standout_char(char c) {
3380 gagt_styled_char(style_Emphasized, c);
3381 }
3382
gagt_normal_string(const char * message)3383 static void gagt_normal_string(const char *message) {
3384 gagt_styled_string(style_Normal, message);
3385 }
3386
gagt_normal_char(char c)3387 static void gagt_normal_char(char c) {
3388 gagt_styled_char(style_Normal, c);
3389 }
3390
gagt_header_string(const char * message)3391 static void gagt_header_string(const char *message) {
3392 gagt_styled_string(style_Header, message);
3393 }
3394
3395
3396 /*---------------------------------------------------------------------*/
3397 /* Glk port delay functions */
3398 /*---------------------------------------------------------------------*/
3399
3400 /* Number of milliseconds in a second (traditionally, 1000). */
3401 static const int GAGT_MS_PER_SEC = 1000;
3402
3403 /*
3404 * Number of milliseconds to timeout. Because of jitter in the way Glk
3405 * generates timeouts, it's worthwhile implementing a delay using a number
3406 * of shorter timeouts. This minimizes inaccuracies in the actual delay.
3407 */
3408 static const glui32 GAGT_DELAY_TIMEOUT = 50;
3409
3410 /* The character key that can be pressed to cancel, and suspend, delays. */
3411 static const char GAGT_DELAY_SUSPEND = ' ';
3412
3413 /*
3414 * Flag to temporarily turn off all delays. This is set when the user
3415 * cancels a delay with a keypress, and remains set until the next time
3416 * that AGiliTy requests user input. This way, games that call agt_delay()
3417 * sequentially don't require multiple keypresses to jump out of delay
3418 * sections.
3419 */
3420 static int gagt_delays_suspended = FALSE;
3421
3422
3423 /*
3424 * agt_delay()
3425 *
3426 * Delay for the specified number of seconds. The delay can be canceled
3427 * by a user keypress.
3428 */
agt_delay(int seconds)3429 void agt_delay(int seconds) {
3430 glui32 milliseconds, delayed;
3431 int delay_completed;
3432
3433 /* Suppress delay if in fast replay or batch mode. */
3434 if (fast_replay || BATCH_MODE)
3435 return;
3436
3437 /*
3438 * Do nothing if Glk doesn't have timers, if the delay state is set to
3439 * ignore delays, if a zero or negative delay was specified, or if delays
3440 * are currently temporarily suspended.
3441 */
3442 if (!g_vm->glk_gestalt(gestalt_Timer, 0)
3443 || g_vm->gagt_delay_mode == DELAY_OFF
3444 || seconds <= 0 || gagt_delays_suspended)
3445 return;
3446
3447 /* Flush any pending buffered output, and refresh status to show waiting. */
3448 gagt_output_flush();
3449 gagt_status_in_delay(TRUE);
3450
3451 /* Calculate the number of milliseconds to delay. */
3452 milliseconds = (seconds * GAGT_MS_PER_SEC)
3453 / (g_vm->gagt_delay_mode == DELAY_SHORT ? 2 : 1);
3454
3455 /* Request timer events, and let a keypress cancel the delay. */
3456 g_vm->glk_request_char_event(g_vm->gagt_main_window);
3457 g_vm->glk_request_timer_events(GAGT_DELAY_TIMEOUT);
3458
3459 /*
3460 * Implement the delay using a sequence of shorter Glk timeouts, with an
3461 * option to cancel the delay with a keypress.
3462 */
3463 delay_completed = TRUE;
3464 for (delayed = 0; delayed < milliseconds; delayed += GAGT_DELAY_TIMEOUT) {
3465 event_t event;
3466
3467 /* Wait for the next timeout, or a character. */
3468 gagt_event_wait_2(evtype_CharInput, evtype_Timer, &event);
3469 if (event.type == evtype_CharInput) {
3470 /*
3471 * If suspend requested, stop the delay, and set the delay
3472 * suspension flag, and a note that the delay loop didn't complete.
3473 * Otherwise, reissue the character input request.
3474 */
3475 if (event.val1 == GAGT_DELAY_SUSPEND) {
3476 gagt_delays_suspended = TRUE;
3477 delay_completed = FALSE;
3478 break;
3479 } else
3480 g_vm->glk_request_char_event(g_vm->gagt_main_window);
3481 }
3482 }
3483
3484 /* Cancel any pending character input, and timer events. */
3485 if (delay_completed)
3486 g_vm->glk_cancel_char_event(g_vm->gagt_main_window);
3487 g_vm->glk_request_timer_events(0);
3488
3489 /* Clear the waiting indicator. */
3490 gagt_status_in_delay(FALSE);
3491
3492 gagt_debug("agt_delay", "seconds=%d [%lu mS] -> %s", seconds, milliseconds,
3493 delay_completed ? "completed" : "canceled");
3494 }
3495
3496
3497 /*
3498 * gagt_delay_resume()
3499 *
3500 * Unsuspend delays. This function should be called by agt_input() and
3501 * agt_getkey(), to re-enable delays when the interpreter next requests
3502 * user input.
3503 */
gagt_delay_resume()3504 static void gagt_delay_resume() {
3505 gagt_delays_suspended = FALSE;
3506 }
3507
3508
3509 /*---------------------------------------------------------------------*/
3510 /* Glk port box drawing functions */
3511 /*---------------------------------------------------------------------*/
3512
3513 /* Saved details of any current box dimensions and flags. */
3514 static unsigned long gagt_box_flags = 0;
3515 static int gagt_box_busy = FALSE,
3516 gagt_box_width = 0,
3517 gagt_box_height = 0,
3518 gagt_box_startx = 0;
3519
3520
3521 /*
3522 * gagt_box_rule()
3523 * gagt_box_position()
3524 *
3525 * Draw a line at the top or bottom of a box, and position the cursor
3526 * with a box indent.
3527 */
gagt_box_rule(int width)3528 static void gagt_box_rule(int width) {
3529 char *ruler;
3530
3531 /* Write a +--...--+ ruler to delimit a box. */
3532 ruler = (char *)gagt_malloc(width + 2 + 1);
3533 memset(ruler + 1, '-', width);
3534 ruler[0] = ruler[width + 1] = '+';
3535 ruler[width + 2] = '\0';
3536 agt_puts(ruler);
3537 free(ruler);
3538 }
3539
gagt_box_position(int indent)3540 static void gagt_box_position(int indent) {
3541 char *spaces;
3542
3543 /* Write a newline before the indent. */
3544 agt_newline();
3545
3546 /* Write the indent to the start of box text. */
3547 spaces = (char *)gagt_malloc(indent + 1);
3548 memset(spaces, ' ', indent);
3549 spaces[indent] = '\0';
3550 agt_puts(spaces);
3551 free(spaces);
3552 }
3553
3554
3555 /*
3556 * agt_makebox()
3557 * agt_qnewline()
3558 * agt_endbox()
3559 *
3560 * Start a box of given width, height, and with given flags. Write a new
3561 * line in the box. And end the box.
3562 */
agt_makebox(int width,int height,unsigned long flags)3563 void agt_makebox(int width, int height, unsigned long flags) {
3564 assert(!gagt_box_busy);
3565
3566 gagt_box_busy = TRUE;
3567 gagt_box_flags = flags;
3568 gagt_box_width = width;
3569 gagt_box_height = height;
3570
3571 /* If no centering requested, set the indent to zero. */
3572 if (gagt_box_flags & TB_NOCENT)
3573 gagt_box_startx = 0;
3574 else {
3575 int centering_width;
3576
3577 /*
3578 * Calculate the indent for centering, adding 4 characters for borders.
3579 * Here, since screen_width is artificial, we'll center off status_width
3580 * if it is less than screen width, otherwise we'll center by using
3581 * screen_width. The reason for shrinking to screen_width is that if
3582 * we don't, we could drive curr_x to beyond screen_width with our box
3583 * indentations, and that confuses AGiliTy.
3584 */
3585 if (status_width < screen_width)
3586 centering_width = status_width;
3587 else
3588 centering_width = screen_width;
3589 if (gagt_box_flags & TB_BORDER)
3590 gagt_box_startx = (centering_width - gagt_box_width - 4) / 2;
3591 else
3592 gagt_box_startx = (centering_width - gagt_box_width) / 2;
3593
3594 /* If the box turns out wider than the window, abandon centering. */
3595 if (gagt_box_startx < 0)
3596 gagt_box_startx = 0;
3597 }
3598
3599 /*
3600 * When in a box, we'll coerce fixed width font by setting it in the AGT
3601 * font attributes. This ensures that the box displays as accurately as
3602 * we're able to achieve.
3603 */
3604 gagt_coerce_fixed_font(TRUE);
3605
3606 /* Position the cursor for the box, and if bordered, write the rule. */
3607 gagt_box_position(gagt_box_startx);
3608 if (gagt_box_flags & TB_BORDER) {
3609 gagt_box_rule(gagt_box_width + 2);
3610 gagt_box_position(gagt_box_startx);
3611 agt_puts("| ");
3612 }
3613
3614 gagt_debug("agt_makebox", "width=%d, height=%d, flags=0x%lx",
3615 width, height, flags);
3616 }
3617
agt_qnewline()3618 void agt_qnewline() {
3619 assert(gagt_box_busy);
3620
3621 /* Write box characters for the current and next line. */
3622 if (gagt_box_flags & TB_BORDER) {
3623 agt_puts(" |");
3624 gagt_box_position(gagt_box_startx);
3625 agt_puts("| ");
3626 } else
3627 gagt_box_position(gagt_box_startx);
3628
3629 gagt_debug("agt_qnewline", "");
3630 }
3631
agt_endbox()3632 void agt_endbox() {
3633 assert(gagt_box_busy);
3634
3635 /* Finish off the current box. */
3636 if (gagt_box_flags & TB_BORDER) {
3637 agt_puts(" |");
3638 gagt_box_position(gagt_box_startx);
3639 gagt_box_rule(gagt_box_width + 2);
3640 }
3641 agt_newline();
3642
3643 /* An extra newline here improves the appearance. */
3644 agt_newline();
3645
3646 /* Back to allowing proportional font output again. */
3647 gagt_coerce_fixed_font(FALSE);
3648
3649 gagt_box_busy = FALSE;
3650 gagt_box_flags = gagt_box_width = gagt_box_startx = 0;
3651
3652 gagt_debug("agt_endbox", "");
3653 }
3654
3655
3656 /*---------------------------------------------------------------------*/
3657 /* Glk command escape functions */
3658 /*---------------------------------------------------------------------*/
3659
3660 /*
3661 * gagt_command_script()
3662 *
3663 * Turn game output scripting (logging) on and off.
3664 */
gagt_command_script(const char * argument)3665 static void gagt_command_script(const char *argument) {
3666 assert(argument);
3667
3668 if (gagt_strcasecmp(argument, "on") == 0) {
3669 frefid_t fileref;
3670
3671 if (g_vm->gagt_transcript_stream) {
3672 gagt_normal_string("Glk transcript is already on.\n");
3673 return;
3674 }
3675
3676 fileref = g_vm->glk_fileref_create_by_prompt(fileusage_Transcript
3677 | fileusage_TextMode,
3678 filemode_WriteAppend, 0);
3679 if (!fileref) {
3680 gagt_standout_string("Glk transcript failed.\n");
3681 return;
3682 }
3683
3684 g_vm->gagt_transcript_stream = g_vm->glk_stream_open_file(fileref,
3685 filemode_WriteAppend, 0);
3686 g_vm->glk_fileref_destroy(fileref);
3687 if (!g_vm->gagt_transcript_stream) {
3688 gagt_standout_string("Glk transcript failed.\n");
3689 return;
3690 }
3691
3692 g_vm->glk_window_set_echo_stream(g_vm->gagt_main_window, g_vm->gagt_transcript_stream);
3693
3694 gagt_normal_string("Glk transcript is now on.\n");
3695 }
3696
3697 else if (gagt_strcasecmp(argument, "off") == 0) {
3698 if (!g_vm->gagt_transcript_stream) {
3699 gagt_normal_string("Glk transcript is already off.\n");
3700 return;
3701 }
3702
3703 g_vm->glk_stream_close(g_vm->gagt_transcript_stream, NULL);
3704 g_vm->gagt_transcript_stream = NULL;
3705
3706 g_vm->glk_window_set_echo_stream(g_vm->gagt_main_window, NULL);
3707
3708 gagt_normal_string("Glk transcript is now off.\n");
3709 }
3710
3711 else if (strlen(argument) == 0) {
3712 gagt_normal_string("Glk transcript is ");
3713 gagt_normal_string(g_vm->gagt_transcript_stream ? "on" : "off");
3714 gagt_normal_string(".\n");
3715 }
3716
3717 else {
3718 gagt_normal_string("Glk transcript can be ");
3719 gagt_standout_string("on");
3720 gagt_normal_string(", or ");
3721 gagt_standout_string("off");
3722 gagt_normal_string(".\n");
3723 }
3724 }
3725
3726
3727 /*
3728 * gagt_command_inputlog()
3729 *
3730 * Turn game input logging on and off.
3731 */
gagt_command_inputlog(const char * argument)3732 static void gagt_command_inputlog(const char *argument) {
3733 assert(argument);
3734
3735 if (gagt_strcasecmp(argument, "on") == 0) {
3736 frefid_t fileref;
3737
3738 if (g_vm->gagt_inputlog_stream) {
3739 gagt_normal_string("Glk input logging is already on.\n");
3740 return;
3741 }
3742
3743 fileref = g_vm->glk_fileref_create_by_prompt(fileusage_InputRecord
3744 | fileusage_BinaryMode,
3745 filemode_WriteAppend, 0);
3746 if (!fileref) {
3747 gagt_standout_string("Glk input logging failed.\n");
3748 return;
3749 }
3750
3751 g_vm->gagt_inputlog_stream = g_vm->glk_stream_open_file(fileref,
3752 filemode_WriteAppend, 0);
3753 g_vm->glk_fileref_destroy(fileref);
3754 if (!g_vm->gagt_inputlog_stream) {
3755 gagt_standout_string("Glk input logging failed.\n");
3756 return;
3757 }
3758
3759 gagt_normal_string("Glk input logging is now on.\n");
3760 }
3761
3762 else if (gagt_strcasecmp(argument, "off") == 0) {
3763 if (!g_vm->gagt_inputlog_stream) {
3764 gagt_normal_string("Glk input logging is already off.\n");
3765 return;
3766 }
3767
3768 g_vm->glk_stream_close(g_vm->gagt_inputlog_stream, NULL);
3769 g_vm->gagt_inputlog_stream = NULL;
3770
3771 gagt_normal_string("Glk input log is now off.\n");
3772 }
3773
3774 else if (strlen(argument) == 0) {
3775 gagt_normal_string("Glk input logging is ");
3776 gagt_normal_string(g_vm->gagt_inputlog_stream ? "on" : "off");
3777 gagt_normal_string(".\n");
3778 }
3779
3780 else {
3781 gagt_normal_string("Glk input logging can be ");
3782 gagt_standout_string("on");
3783 gagt_normal_string(", or ");
3784 gagt_standout_string("off");
3785 gagt_normal_string(".\n");
3786 }
3787 }
3788
3789
3790 /*
3791 * gagt_command_readlog()
3792 *
3793 * Set the game input log, to read input from a file.
3794 */
gagt_command_readlog(const char * argument)3795 static void gagt_command_readlog(const char *argument) {
3796 assert(argument);
3797
3798 if (gagt_strcasecmp(argument, "on") == 0) {
3799 frefid_t fileref;
3800
3801 if (g_vm->gagt_readlog_stream) {
3802 gagt_normal_string("Glk read log is already on.\n");
3803 return;
3804 }
3805
3806 fileref = g_vm->glk_fileref_create_by_prompt(fileusage_InputRecord
3807 | fileusage_BinaryMode,
3808 filemode_Read, 0);
3809 if (!fileref) {
3810 gagt_standout_string("Glk read log failed.\n");
3811 return;
3812 }
3813
3814 if (!g_vm->glk_fileref_does_file_exist(fileref)) {
3815 g_vm->glk_fileref_destroy(fileref);
3816 gagt_standout_string("Glk read log failed.\n");
3817 return;
3818 }
3819
3820 g_vm->gagt_readlog_stream = g_vm->glk_stream_open_file(fileref, filemode_Read, 0);
3821 g_vm->glk_fileref_destroy(fileref);
3822 if (!g_vm->gagt_readlog_stream) {
3823 gagt_standout_string("Glk read log failed.\n");
3824 return;
3825 }
3826
3827 gagt_normal_string("Glk read log is now on.\n");
3828 }
3829
3830 else if (gagt_strcasecmp(argument, "off") == 0) {
3831 if (!g_vm->gagt_readlog_stream) {
3832 gagt_normal_string("Glk read log is already off.\n");
3833 return;
3834 }
3835
3836 g_vm->glk_stream_close(g_vm->gagt_readlog_stream, NULL);
3837 g_vm->gagt_readlog_stream = NULL;
3838
3839 gagt_normal_string("Glk read log is now off.\n");
3840 }
3841
3842 else if (strlen(argument) == 0) {
3843 gagt_normal_string("Glk read log is ");
3844 gagt_normal_string(g_vm->gagt_readlog_stream ? "on" : "off");
3845 gagt_normal_string(".\n");
3846 }
3847
3848 else {
3849 gagt_normal_string("Glk read log can be ");
3850 gagt_standout_string("on");
3851 gagt_normal_string(", or ");
3852 gagt_standout_string("off");
3853 gagt_normal_string(".\n");
3854 }
3855 }
3856
3857
3858 /*
3859 * gagt_command_abbreviations()
3860 *
3861 * Turn abbreviation expansions on and off.
3862 */
gagt_command_abbreviations(const char * argument)3863 static void gagt_command_abbreviations(const char *argument) {
3864 assert(argument);
3865
3866 if (gagt_strcasecmp(argument, "on") == 0) {
3867 if (g_vm->gagt_abbreviations_enabled) {
3868 gagt_normal_string("Glk abbreviation expansions are already on.\n");
3869 return;
3870 }
3871
3872 g_vm->gagt_abbreviations_enabled = TRUE;
3873 gagt_normal_string("Glk abbreviation expansions are now on.\n");
3874 }
3875
3876 else if (gagt_strcasecmp(argument, "off") == 0) {
3877 if (!g_vm->gagt_abbreviations_enabled) {
3878 gagt_normal_string("Glk abbreviation expansions are already off.\n");
3879 return;
3880 }
3881
3882 g_vm->gagt_abbreviations_enabled = FALSE;
3883 gagt_normal_string("Glk abbreviation expansions are now off.\n");
3884 }
3885
3886 else if (strlen(argument) == 0) {
3887 gagt_normal_string("Glk abbreviation expansions are ");
3888 gagt_normal_string(g_vm->gagt_abbreviations_enabled ? "on" : "off");
3889 gagt_normal_string(".\n");
3890 }
3891
3892 else {
3893 gagt_normal_string("Glk abbreviation expansions can be ");
3894 gagt_standout_string("on");
3895 gagt_normal_string(", or ");
3896 gagt_standout_string("off");
3897 gagt_normal_string(".\n");
3898 }
3899 }
3900
3901
3902 /*
3903 * gagt_command_fonts()
3904 *
3905 * Set the value for g_vm->gagt_font_mode depending on the argument from the
3906 * user's command escape.
3907 *
3908 * Despite our best efforts, font control may still be wrong in some games.
3909 * This command gives us a chance to correct that.
3910 */
gagt_command_fonts(const char * argument)3911 static void gagt_command_fonts(const char *argument) {
3912 assert(argument);
3913
3914 if (gagt_strcasecmp(argument, "fixed") == 0) {
3915 if (g_vm->gagt_font_mode == FONT_FIXED_WIDTH) {
3916 gagt_normal_string("Glk font control is already 'fixed'.\n");
3917 return;
3918 }
3919
3920 g_vm->gagt_font_mode = FONT_FIXED_WIDTH;
3921 gagt_normal_string("Glk font control is now 'fixed'.\n");
3922 }
3923
3924 else if (gagt_strcasecmp(argument, "variable") == 0
3925 || gagt_strcasecmp(argument, "proportional") == 0) {
3926 if (g_vm->gagt_font_mode == FONT_PROPORTIONAL) {
3927 gagt_normal_string("Glk font control is already 'proportional'.\n");
3928 return;
3929 }
3930
3931 g_vm->gagt_font_mode = FONT_PROPORTIONAL;
3932 gagt_normal_string("Glk font control is now 'proportional'.\n");
3933 }
3934
3935 else if (gagt_strcasecmp(argument, "auto") == 0
3936 || gagt_strcasecmp(argument, "automatic") == 0) {
3937 if (g_vm->gagt_font_mode == FONT_AUTOMATIC) {
3938 gagt_normal_string("Glk font control is already 'automatic'.\n");
3939 return;
3940 }
3941
3942 g_vm->gagt_font_mode = FONT_AUTOMATIC;
3943 gagt_normal_string("Glk font control is now 'automatic'.\n");
3944 }
3945
3946 else if (gagt_strcasecmp(argument, "debug") == 0) {
3947 if (g_vm->gagt_font_mode == FONT_DEBUG) {
3948 gagt_normal_string("Glk font control is already 'debug'.\n");
3949 return;
3950 }
3951
3952 g_vm->gagt_font_mode = FONT_DEBUG;
3953 gagt_normal_string("Glk font control is now 'debug'.\n");
3954 }
3955
3956 else if (strlen(argument) == 0) {
3957 gagt_normal_string("Glk font control is set to '");
3958 switch (g_vm->gagt_font_mode) {
3959 case FONT_AUTOMATIC:
3960 gagt_normal_string("automatic");
3961 break;
3962
3963 case FONT_FIXED_WIDTH:
3964 gagt_normal_string("fixed");
3965 break;
3966
3967 case FONT_PROPORTIONAL:
3968 gagt_normal_string("proportional");
3969 break;
3970
3971 case FONT_DEBUG:
3972 gagt_normal_string("debug");
3973 break;
3974
3975 default:
3976 gagt_fatal("GLK: Invalid font mode encountered");
3977 gagt_exit();
3978 }
3979 gagt_normal_string("'.\n");
3980 }
3981
3982 else {
3983 /* Avoid mentioning the debug setting. */
3984 gagt_normal_string("Glk font control can be ");
3985 gagt_standout_string("fixed");
3986 gagt_normal_string(", ");
3987 gagt_standout_string("proportional");
3988 gagt_normal_string(", or ");
3989 gagt_standout_string("automatic");
3990 gagt_normal_string(".\n");
3991 }
3992 }
3993
3994
3995 /*
3996 * gagt_command_delays()
3997 *
3998 * Set a value for g_vm->gagt_delay_mode depending on the argument from
3999 * the user's command escape.
4000 */
gagt_command_delays(const char * argument)4001 static void gagt_command_delays(const char *argument) {
4002 assert(argument);
4003
4004 if (!g_vm->glk_gestalt(gestalt_Timer, 0)) {
4005 gagt_normal_string("Glk delays are not available.\n");
4006 return;
4007 }
4008
4009 if (gagt_strcasecmp(argument, "full") == 0
4010 || gagt_strcasecmp(argument, "on") == 0) {
4011 if (g_vm->gagt_delay_mode == DELAY_FULL) {
4012 gagt_normal_string("Glk delay mode is already 'full'.\n");
4013 return;
4014 }
4015
4016 g_vm->gagt_delay_mode = DELAY_FULL;
4017 gagt_normal_string("Glk delay mode is now 'full'.\n");
4018 }
4019
4020 else if (gagt_strcasecmp(argument, "short") == 0
4021 || gagt_strcasecmp(argument, "half") == 0) {
4022 if (g_vm->gagt_delay_mode == DELAY_SHORT) {
4023 gagt_normal_string("Glk delay mode is already 'short'.\n");
4024 return;
4025 }
4026
4027 g_vm->gagt_delay_mode = DELAY_SHORT;
4028 gagt_normal_string("Glk delay mode is now 'short'.\n");
4029 }
4030
4031 else if (gagt_strcasecmp(argument, "none") == 0
4032 || gagt_strcasecmp(argument, "off") == 0) {
4033 if (g_vm->gagt_delay_mode == DELAY_OFF) {
4034 gagt_normal_string("Glk delay mode is already 'none'.\n");
4035 return;
4036 }
4037
4038 g_vm->gagt_delay_mode = DELAY_OFF;
4039 gagt_normal_string("Glk delay mode is now 'none'.\n");
4040 }
4041
4042 else if (strlen(argument) == 0) {
4043 gagt_normal_string("Glk delay mode is set to '");
4044 switch (g_vm->gagt_delay_mode) {
4045 case DELAY_FULL:
4046 gagt_normal_string("full");
4047 break;
4048
4049 case DELAY_SHORT:
4050 gagt_normal_string("short");
4051 break;
4052
4053 case DELAY_OFF:
4054 gagt_normal_string("none");
4055 break;
4056
4057 default:
4058 gagt_fatal("GLK: Invalid delay mode encountered");
4059 gagt_exit();
4060 }
4061 gagt_normal_string("'.\n");
4062 }
4063
4064 else {
4065 gagt_normal_string("Glk delay mode can be ");
4066 gagt_standout_string("full");
4067 gagt_normal_string(", ");
4068 gagt_standout_string("short");
4069 gagt_normal_string(", or ");
4070 gagt_standout_string("none");
4071 gagt_normal_string(".\n");
4072 }
4073 }
4074
4075
4076 /*
4077 * gagt_command_width()
4078 *
4079 * Print out the (approximate) display width, from status_width. It's
4080 * approximate because the main window might include a scrollbar that
4081 * the status window doesn't have, may use a different size font, and so
4082 * on. But the main window won't tell us a width at all - it always
4083 * returns zero. If we don't happen to have a status window available
4084 * to us, there's not much we can say.
4085 *
4086 * Note that this function uses the interpreter variable status_width,
4087 * so it's important to keep this updated with the current window size at
4088 * all times.
4089 */
gagt_command_width(const char * argument)4090 static void gagt_command_width(const char *argument) {
4091 char buffer[16];
4092 assert(argument);
4093
4094 if (!g_vm->gagt_status_window) {
4095 gagt_normal_string("Glk's current display width is unknown.\n");
4096 return;
4097 }
4098
4099 gagt_normal_string("Glk's current display width is approximately ");
4100 sprintf(buffer, "%d", status_width);
4101 gagt_normal_string(buffer);
4102 gagt_normal_string(status_width == 1 ? " character" : " characters");
4103 gagt_normal_string(".\n");
4104 }
4105
4106
4107 /*
4108 * gagt_command_replacements()
4109 *
4110 * Turn Glk special paragraph replacement on and off.
4111 */
gagt_command_replacements(const char * argument)4112 static void gagt_command_replacements(const char *argument) {
4113 assert(argument);
4114
4115 if (gagt_strcasecmp(argument, "on") == 0) {
4116 if (g_vm->gagt_replacement_enabled) {
4117 gagt_normal_string("Glk replacements are already on.\n");
4118 return;
4119 }
4120
4121 g_vm->gagt_replacement_enabled = TRUE;
4122 gagt_normal_string("Glk replacements are now on.\n");
4123 }
4124
4125 else if (gagt_strcasecmp(argument, "off") == 0) {
4126 if (!g_vm->gagt_replacement_enabled) {
4127 gagt_normal_string("Glk replacements are already off.\n");
4128 return;
4129 }
4130
4131 g_vm->gagt_replacement_enabled = FALSE;
4132 gagt_normal_string("Glk replacements are now off.\n");
4133 }
4134
4135 else if (strlen(argument) == 0) {
4136 gagt_normal_string("Glk replacements are ");
4137 gagt_normal_string(g_vm->gagt_replacement_enabled ? "on" : "off");
4138 gagt_normal_string(".\n");
4139 }
4140
4141 else {
4142 gagt_normal_string("Glk replacements can be ");
4143 gagt_standout_string("on");
4144 gagt_normal_string(", or ");
4145 gagt_standout_string("off");
4146 gagt_normal_string(".\n");
4147 }
4148 }
4149
4150
4151 /*
4152 * gagt_command_statusline()
4153 *
4154 * Turn the extended status line on and off.
4155 */
gagt_command_statusline(const char * argument)4156 static void gagt_command_statusline(const char *argument) {
4157 assert(argument);
4158
4159 if (!g_vm->gagt_status_window) {
4160 gagt_normal_string("Glk status window is not available.\n");
4161 return;
4162 }
4163
4164 if (gagt_strcasecmp(argument, "extended") == 0
4165 || gagt_strcasecmp(argument, "full") == 0) {
4166 if (g_vm->gagt_extended_status_enabled) {
4167 gagt_normal_string("Glk status line mode is already 'extended'.\n");
4168 return;
4169 }
4170
4171 /* Expand the status window down to a second line. */
4172 g_vm->glk_window_set_arrangement(g_vm->glk_window_get_parent(g_vm->gagt_status_window),
4173 winmethod_Above | winmethod_Fixed, 2, NULL);
4174 g_vm->gagt_extended_status_enabled = TRUE;
4175
4176 gagt_normal_string("Glk status line mode is now 'extended'.\n");
4177 }
4178
4179 else if (gagt_strcasecmp(argument, "short") == 0
4180 || gagt_strcasecmp(argument, "normal") == 0) {
4181 if (!g_vm->gagt_extended_status_enabled) {
4182 gagt_normal_string("Glk status line mode is already 'short'.\n");
4183 return;
4184 }
4185
4186 /* Shrink the status window down to one line. */
4187 g_vm->glk_window_set_arrangement(g_vm->glk_window_get_parent(g_vm->gagt_status_window),
4188 winmethod_Above | winmethod_Fixed, 1, NULL);
4189 g_vm->gagt_extended_status_enabled = FALSE;
4190
4191 gagt_normal_string("Glk status line mode is now 'short'.\n");
4192 }
4193
4194 else if (strlen(argument) == 0) {
4195 gagt_normal_string("Glk status line mode is set to '");
4196 gagt_normal_string(g_vm->gagt_extended_status_enabled ? "extended" : "short");
4197 gagt_normal_string("'.\n");
4198 }
4199
4200 else {
4201 gagt_normal_string("Glk status line can be ");
4202 gagt_standout_string("extended");
4203 gagt_normal_string(", or ");
4204 gagt_standout_string("short");
4205 gagt_normal_string(".\n");
4206 }
4207 }
4208
4209
4210 /*
4211 * gagt_command_print_version_number()
4212 * gagt_command_version()
4213 *
4214 * Print out the Glk library version number.
4215 */
gagt_command_print_version_number(glui32 version)4216 static void gagt_command_print_version_number(glui32 version) {
4217 char buffer[64];
4218
4219 sprintf(buffer, "%u.%u.%u",
4220 version >> 16, (version >> 8) & 0xff, version & 0xff);
4221 gagt_normal_string(buffer);
4222 }
4223
gagt_command_version(const char * argument)4224 static void gagt_command_version(const char *argument) {
4225 glui32 version;
4226 assert(argument);
4227
4228 gagt_normal_string("This is version ");
4229 gagt_command_print_version_number(GAGT_PORT_VERSION);
4230 gagt_normal_string(" of the Glk AGiliTy port.\n");
4231
4232 version = g_vm->glk_gestalt(gestalt_Version, 0);
4233 gagt_normal_string("The Glk library version is ");
4234 gagt_command_print_version_number(version);
4235 gagt_normal_string(".\n");
4236 }
4237
4238
4239 /*
4240 * gagt_command_commands()
4241 *
4242 * Turn command escapes off. Once off, there's no way to turn them back on.
4243 * Commands must be on already to enter this function.
4244 */
gagt_command_commands(const char * argument)4245 static void gagt_command_commands(const char *argument) {
4246 assert(argument);
4247
4248 if (gagt_strcasecmp(argument, "on") == 0) {
4249 gagt_normal_string("Glk commands are already on.\n");
4250 }
4251
4252 else if (gagt_strcasecmp(argument, "off") == 0) {
4253 g_vm->gagt_commands_enabled = FALSE;
4254 gagt_normal_string("Glk commands are now off.\n");
4255 }
4256
4257 else if (strlen(argument) == 0) {
4258 gagt_normal_string("Glk commands are ");
4259 gagt_normal_string(g_vm->gagt_commands_enabled ? "on" : "off");
4260 gagt_normal_string(".\n");
4261 }
4262
4263 else {
4264 gagt_normal_string("Glk commands can be ");
4265 gagt_standout_string("on");
4266 gagt_normal_string(", or ");
4267 gagt_standout_string("off");
4268 gagt_normal_string(".\n");
4269 }
4270 }
4271
4272 /* Glk subcommands and handler functions. */
4273 struct gagt_command_t {
4274 const char *const command; /* Glk subcommand. */
4275 void (* const handler)(const char *argument); /* Subcommand handler. */
4276 const int takes_argument; /* Argument flag. */
4277 } ;
4278 typedef const gagt_command_t *gagt_commandref_t;
4279
4280 static void gagt_command_summary(const char *argument);
4281 static void gagt_command_help(const char *argument);
4282
4283 static gagt_command_t GAGT_COMMAND_TABLE[] = {
4284 {"summary", gagt_command_summary, FALSE},
4285 {"script", gagt_command_script, TRUE},
4286 {"inputlog", gagt_command_inputlog, TRUE},
4287 {"readlog", gagt_command_readlog, TRUE},
4288 {"abbreviations", gagt_command_abbreviations, TRUE},
4289 {"fonts", gagt_command_fonts, TRUE},
4290 {"delays", gagt_command_delays, TRUE},
4291 {"width", gagt_command_width, FALSE},
4292 {"replacements", gagt_command_replacements, TRUE},
4293 {"statusline", gagt_command_statusline, TRUE},
4294 {"version", gagt_command_version, FALSE},
4295 {"commands", gagt_command_commands, TRUE},
4296 {"help", gagt_command_help, TRUE},
4297 {NULL, NULL, FALSE}
4298 };
4299
4300
4301 /*
4302 * gagt_command_summary()
4303 *
4304 * Report all current Glk settings.
4305 */
gagt_command_summary(const char * argument)4306 static void gagt_command_summary(const char *argument) {
4307 gagt_commandref_t entry;
4308 assert(argument);
4309
4310 /*
4311 * Call handlers that have status to report with an empty argument,
4312 * prompting each to print its current setting.
4313 */
4314 for (entry = GAGT_COMMAND_TABLE; entry->command; entry++) {
4315 if (entry->handler == gagt_command_summary
4316 || entry->handler == gagt_command_help)
4317 continue;
4318
4319 entry->handler("");
4320 }
4321 }
4322
4323
4324 /*
4325 * gagt_command_help()
4326 *
4327 * Document the available Glk cmds.
4328 */
gagt_command_help(const char * cmd)4329 static void gagt_command_help(const char *cmd) {
4330 gagt_commandref_t entry, matched;
4331 assert(cmd);
4332
4333 if (strlen(cmd) == 0) {
4334 gagt_normal_string("Glk cmds are");
4335 for (entry = GAGT_COMMAND_TABLE; entry->command; entry++) {
4336 gagt_commandref_t next;
4337
4338 next = entry + 1;
4339 gagt_normal_string(next->command ? " " : " and ");
4340 gagt_standout_string(entry->command);
4341 gagt_normal_string(next->command ? "," : ".\n\n");
4342 }
4343
4344 gagt_normal_string("Glk cmds may be abbreviated, as long as"
4345 " the abbreviation is unambiguous. Use ");
4346 gagt_standout_string("glk help");
4347 gagt_normal_string(" followed by a Glk cmd name for help on that"
4348 " cmd.\n");
4349 return;
4350 }
4351
4352 matched = NULL;
4353 for (entry = GAGT_COMMAND_TABLE; entry->command; entry++) {
4354 if (gagt_strncasecmp(cmd, entry->command, strlen(cmd)) == 0) {
4355 if (matched) {
4356 gagt_normal_string("The Glk cmd ");
4357 gagt_standout_string(cmd);
4358 gagt_normal_string(" is ambiguous. Try ");
4359 gagt_standout_string("glk help");
4360 gagt_normal_string(" for more information.\n");
4361 return;
4362 }
4363 matched = entry;
4364 }
4365 }
4366 if (!matched) {
4367 gagt_normal_string("The Glk cmd ");
4368 gagt_standout_string(cmd);
4369 gagt_normal_string(" is not valid. Try ");
4370 gagt_standout_string("glk help");
4371 gagt_normal_string(" for more information.\n");
4372 return;
4373 }
4374
4375 if (matched->handler == gagt_command_summary) {
4376 gagt_normal_string("Prints a summary of all the current Glk AGiliTy"
4377 " settings.\n");
4378 }
4379
4380 else if (matched->handler == gagt_command_script) {
4381 gagt_normal_string("Logs the game's output to a file.\n\nUse ");
4382 gagt_standout_string("glk script on");
4383 gagt_normal_string(" to begin logging game output, and ");
4384 gagt_standout_string("glk script off");
4385 gagt_normal_string(" to end it. Glk AGiliTy will ask you for a file"
4386 " when you turn scripts on.\n");
4387 }
4388
4389 else if (matched->handler == gagt_command_inputlog) {
4390 gagt_normal_string("Records the cmds you type into a game.\n\nUse ");
4391 gagt_standout_string("glk inputlog on");
4392 gagt_normal_string(", to begin recording your cmds, and ");
4393 gagt_standout_string("glk inputlog off");
4394 gagt_normal_string(" to turn off input logs. You can play back"
4395 " recorded cmds into a game with the ");
4396 gagt_standout_string("glk readlog");
4397 gagt_normal_string(" cmd.\n");
4398 }
4399
4400 else if (matched->handler == gagt_command_readlog) {
4401 gagt_normal_string("Plays back cmds recorded with ");
4402 gagt_standout_string("glk inputlog on");
4403 gagt_normal_string(".\n\nUse ");
4404 gagt_standout_string("glk readlog on");
4405 gagt_normal_string(". cmd play back stops at the end of the"
4406 " file. You can also play back cmds from a"
4407 " text file created using any standard editor.\n");
4408 }
4409
4410 else if (matched->handler == gagt_command_abbreviations) {
4411 gagt_normal_string("Controls abbreviation expansion.\n\nGlk AGiliTy"
4412 " automatically expands several standard single"
4413 " letter abbreviations for you; for example, \"x\""
4414 " becomes \"examine\". Use ");
4415 gagt_standout_string("glk abbreviations on");
4416 gagt_normal_string(" to turn this feature on, and ");
4417 gagt_standout_string("glk abbreviations off");
4418 gagt_normal_string(" to turn it off. While the feature is on, you"
4419 " can bypass abbreviation expansion for an"
4420 " individual game cmd by prefixing it with a"
4421 " single quote.\n");
4422 }
4423
4424 else if (matched->handler == gagt_command_fonts) {
4425 gagt_normal_string("Controls the way Glk AGiliTy uses fonts.\n\n"
4426 "AGT games normally assume 80x25 monospaced font"
4427 " displays. Glk can often use proportional fonts."
4428 " To try to improve text display, Glk AGiliTy will"
4429 " attempt to automatically detect when game text"
4430 " can be displayed safely in a proportional font,"
4431 " and when fixed width fonts are required. For"
4432 " some games, however, you may need to override"
4433 " it. Use ");
4434 gagt_standout_string("glk fonts automatic");
4435 gagt_normal_string(", ");
4436 gagt_standout_string("glk fonts proportional");
4437 gagt_normal_string(", and ");
4438 gagt_standout_string("glk fonts fixed");
4439 gagt_normal_string(" to switch between Glk AGiliTy font modes.\n");
4440 }
4441
4442 else if (matched->handler == gagt_command_delays) {
4443 gagt_normal_string("Shortens, or eliminates, AGT game delays.\n\nUse ");
4444 gagt_standout_string("glk delays full");
4445 gagt_normal_string(", ");
4446 gagt_standout_string("glk delays short");
4447 gagt_normal_string(", or ");
4448 gagt_standout_string("glk delays none");
4449 gagt_normal_string(". In Glk AGiliTy, you can also end an AGT game's"
4450 " delay early, by pressing Space while the game is"
4451 " delaying.\n");
4452 }
4453
4454 else if (matched->handler == gagt_command_width) {
4455 gagt_normal_string("Prints the screen width available for fixed font"
4456 " display.\n\nEven though Glk AGiliTy tries to handle"
4457 " issues surrounding proportional font displays for"
4458 " you automatically, some game elements may still"
4459 " need to display in fixed width fonts. These"
4460 " elements will be happiest if the available screen"
4461 " width is at least 80 columns.\n");
4462 }
4463
4464 else if (matched->handler == gagt_command_replacements) {
4465 gagt_normal_string("Controls game text scanning and replacement.\n\n"
4466 "Glk AGiliTy can monitor the game's output, and"
4467 " replace a few selected standard messages with"
4468 " equivalents, printed using a style that stands"
4469 " out better in Glk displays. Use ");
4470 gagt_standout_string("glk replacements on");
4471 gagt_normal_string(" to turn this feature on, and ");
4472 gagt_standout_string("glk replacements off");
4473 gagt_normal_string(" to turn it off.\n");
4474 }
4475
4476 else if (matched->handler == gagt_command_statusline) {
4477 gagt_normal_string("Controls the Glk AGiliTy status line display.\n\n"
4478 "Use ");
4479 gagt_standout_string("glk statusline extended");
4480 gagt_normal_string(" to display a full, two line status display, and ");
4481 gagt_standout_string("glk statusline short");
4482 gagt_normal_string(" for a single line status display.\n");
4483 }
4484
4485 else if (matched->handler == gagt_command_version) {
4486 gagt_normal_string("Prints the version numbers of the Glk library"
4487 " and the Glk AGiliTy port.\n");
4488 }
4489
4490 else if (matched->handler == gagt_command_commands) {
4491 gagt_normal_string("Turn off Glk cmds.\n\nUse ");
4492 gagt_standout_string("glk cmds off");
4493 gagt_normal_string(" to disable all Glk cmds, including this one."
4494 " Once turned off, there is no way to turn Glk"
4495 " cmds back on while inside the game.\n");
4496 }
4497
4498 else if (matched->handler == gagt_command_help)
4499 gagt_command_help("");
4500
4501 else
4502 gagt_normal_string("There is no help available on that Glk cmd."
4503 " Sorry.\n");
4504 }
4505
4506
4507 /*
4508 * gagt_command_escape()
4509 *
4510 * This function is handed each input line. If the line contains a specific
4511 * Glk port command, handle it and return TRUE, otherwise return FALSE.
4512 */
gagt_command_escape(const char * string)4513 static int gagt_command_escape(const char *string) {
4514 int posn;
4515 char *string_copy, *cmd, *argument;
4516 assert(string);
4517
4518 /*
4519 * Return FALSE if the string doesn't begin with the Glk command escape
4520 * introducer.
4521 */
4522 posn = strspn(string, "\t ");
4523 if (gagt_strncasecmp(string + posn, "glk", strlen("glk")) != 0)
4524 return FALSE;
4525
4526 /* Take a copy of the string, without any leading space or introducer. */
4527 string_copy = (char *)gagt_malloc(strlen(string + posn) + 1 - strlen("glk"));
4528 strcpy(string_copy, string + posn + strlen("glk"));
4529
4530 /*
4531 * Find the subcommand; the first word in the string copy. Find its end,
4532 * and ensure it terminates with a NUL.
4533 */
4534 posn = strspn(string_copy, "\t ");
4535 cmd = string_copy + posn;
4536 posn += strcspn(string_copy + posn, "\t ");
4537 if (string_copy[posn] != '\0')
4538 string_copy[posn++] = '\0';
4539
4540 /*
4541 * Now find any argument data for the command, ensuring it too terminates
4542 * with a NUL.
4543 */
4544 posn += strspn(string_copy + posn, "\t ");
4545 argument = string_copy + posn;
4546 posn += strcspn(string_copy + posn, "\t ");
4547 string_copy[posn] = '\0';
4548
4549 /*
4550 * Try to handle the command and argument as a Glk subcommand. If it
4551 * doesn't run unambiguously, print command usage. Treat an empty command
4552 * as "help".
4553 */
4554 if (strlen(cmd) > 0) {
4555 gagt_commandref_t entry, matched;
4556 int matches;
4557
4558 /*
4559 * Search for the first unambiguous table cmd string matching
4560 * the cmd passed in.
4561 */
4562 matches = 0;
4563 matched = NULL;
4564 for (entry = GAGT_COMMAND_TABLE; entry->command; entry++) {
4565 if (gagt_strncasecmp(cmd, entry->command, strlen(cmd)) == 0) {
4566 matches++;
4567 matched = entry;
4568 }
4569 }
4570
4571 /* If the match was unambiguous, call the command handler. */
4572 if (matches == 1) {
4573 gagt_normal_char('\n');
4574 matched->handler(argument);
4575
4576 if (!matched->takes_argument && strlen(argument) > 0) {
4577 gagt_normal_string("[The ");
4578 gagt_standout_string(matched->command);
4579 gagt_normal_string(" cmd ignores arguments.]\n");
4580 }
4581 }
4582
4583 /* No match, or the cmd was ambiguous. */
4584 else {
4585 gagt_normal_string("\nThe Glk cmd ");
4586 gagt_standout_string(cmd);
4587 gagt_normal_string(" is ");
4588 gagt_normal_string(matches == 0 ? "not valid" : "ambiguous");
4589 gagt_normal_string(". Try ");
4590 gagt_standout_string("glk help");
4591 gagt_normal_string(" for more information.\n");
4592 }
4593 } else {
4594 gagt_normal_char('\n');
4595 gagt_command_help("");
4596 }
4597
4598 /* The string contained a Glk cmd; return TRUE. */
4599 free(string_copy);
4600 return TRUE;
4601 }
4602
4603
4604 /*---------------------------------------------------------------------*/
4605 /* Glk port input functions */
4606 /*---------------------------------------------------------------------*/
4607
4608 /* Longest line we're going to buffer for input. */
4609 enum { GAGT_INPUTBUFFER_LENGTH = 256 };
4610
4611 /* Table of single-character command abbreviations. */
4612 typedef const struct {
4613 const char abbreviation; /* Abbreviation character. */
4614 const char *const expansion; /* Expansion string. */
4615 } gagt_abbreviation_t;
4616 typedef gagt_abbreviation_t *gagt_abbreviationref_t;
4617
4618 static gagt_abbreviation_t GAGT_ABBREVIATIONS[] = {
4619 {'c', "close"}, {'g', "again"}, {'i', "inventory"},
4620 {'k', "attack"}, {'l', "look"}, {'p', "open"},
4621 {'q', "quit"}, {'r', "drop"}, {'t', "take"},
4622 {'x', "examine"}, {'y', "yes"}, {'z', "wait"},
4623 {'\0', NULL}
4624 };
4625
4626
4627 /*
4628 * gagt_expand_abbreviations()
4629 *
4630 * Expand a few common one-character abbreviations commonly found in other
4631 * game systems, but not always normal in AGT games.
4632 */
gagt_expand_abbreviations(char * buffer,int size)4633 static void gagt_expand_abbreviations(char *buffer, int size) {
4634 char *command_, abbreviation;
4635 const char *expansion;
4636 gagt_abbreviationref_t entry;
4637 assert(buffer);
4638
4639 /* Ignore anything that isn't a single letter command_. */
4640 command_ = buffer + strspn(buffer, "\t ");
4641 if (!(strlen(command_) == 1
4642 || (strlen(command_) > 1 && isspace(command_[1]))))
4643 return;
4644
4645 /* Scan the abbreviations table for a match. */
4646 abbreviation = g_vm->glk_char_to_lower((unsigned char) command_[0]);
4647 expansion = NULL;
4648 for (entry = GAGT_ABBREVIATIONS; entry->expansion; entry++) {
4649 if (entry->abbreviation == abbreviation) {
4650 expansion = entry->expansion;
4651 break;
4652 }
4653 }
4654
4655 /*
4656 * If a match found, check for a fit, then replace the character with the
4657 * expansion string.
4658 */
4659 if (expansion) {
4660 if ((int)strlen(buffer) + (int)strlen(expansion) - 1 >= size)
4661 return;
4662
4663 memmove(command_ + strlen(expansion) - 1, command_, strlen(command_) + 1);
4664 memcpy(command_, expansion, strlen(expansion));
4665
4666 gagt_standout_string("[");
4667 gagt_standout_char(abbreviation);
4668 gagt_standout_string(" -> ");
4669 gagt_standout_string(expansion);
4670 gagt_standout_string("]\n");
4671 }
4672 }
4673
4674
4675 /*
4676 * agt_input()
4677 *
4678 * Read a line from the keyboard, allocating space for it using malloc.
4679 * AGiliTy defines the following for the in_type argument:
4680 *
4681 * in_type: 0=command, 1=number, 2=question, 3=userstr, 4=filename,
4682 * 5=RESTART,RESTORE,UNDO,QUIT
4683 * Negative values are for internal use by the interface (i.e. this module)
4684 * and so are free to be defined by the porter.
4685 *
4686 * Since it's unclear what use we can make of this information in Glk,
4687 * for the moment the argument is ignored. It seems that no-one else
4688 * uses it, either.
4689 */
agt_input(int in_type)4690 char *agt_input(int in_type) {
4691 event_t event;
4692 int length;
4693 char *buffer;
4694
4695 /*
4696 * Update the current status line display, and flush any pending buffered
4697 * output. Release any suspension of delays.
4698 */
4699 gagt_status_notify();
4700 gagt_output_flush();
4701 gagt_delay_resume();
4702
4703 /* Reset current x, as line input implies a newline. */
4704 curr_x = 0;
4705
4706 /* Allocate a line input buffer, allowing 256 characters and a NUL. */
4707 length = GAGT_INPUTBUFFER_LENGTH + 1;
4708 buffer = (char *)gagt_malloc(length);
4709
4710 /*
4711 * If we have an input log to read from, use that until it is exhausted.
4712 * On end of file, close the stream and resume input from line requests.
4713 */
4714 if (g_vm->gagt_readlog_stream) {
4715 glui32 chars;
4716
4717 /* Get the next line from the log stream. */
4718 chars = g_vm->glk_get_line_stream(g_vm->gagt_readlog_stream, buffer, length);
4719 if (chars > 0) {
4720 /* Echo the line just read in input style. */
4721 g_vm->glk_set_style(style_Input);
4722 g_vm->glk_put_buffer(buffer, chars);
4723 g_vm->glk_set_style(style_Normal);
4724
4725 /*
4726 * Convert the string from Glk's ISO 8859 Latin-1 to IBM cp 437,
4727 * add to any script, and return it.
4728 */
4729 gagt_iso_to_cp((const uchar *)buffer, (uchar *)buffer);
4730 if (script_on)
4731 textputs(scriptfile, buffer);
4732 return buffer;
4733 }
4734
4735 /*
4736 * We're at the end of the log stream. Close it, and then continue
4737 * on to request a line from Glk.
4738 */
4739 g_vm->glk_stream_close(g_vm->gagt_readlog_stream, NULL);
4740 g_vm->gagt_readlog_stream = NULL;
4741 }
4742
4743 /* Set this up as a read buffer for the main window, and wait. */
4744 g_vm->glk_request_line_event(g_vm->gagt_main_window, buffer, length - 1, 0);
4745 gagt_event_wait(evtype_LineInput, &event);
4746 if (g_vm->shouldQuit()) {
4747 g_vm->glk_cancel_line_event(g_vm->gagt_main_window, &event);
4748 return nullptr;
4749 }
4750
4751 /* Terminate the input line with a NUL. */
4752 assert((int)event.val1 < length);
4753 buffer[event.val1] = '\0';
4754
4755 /*
4756 * If neither abbreviations nor local commands are enabled, use the data
4757 * read above without further massaging.
4758 */
4759 if (g_vm->gagt_abbreviations_enabled || g_vm->gagt_commands_enabled) {
4760 char *cmd;
4761
4762 /*
4763 * If the first non-space input character is a quote, bypass all
4764 * abbreviation expansion and local command recognition, and use the
4765 * unadulterated input, less introductory quote.
4766 */
4767 cmd = buffer + strspn(buffer, "\t ");
4768 if (cmd[0] == '\'') {
4769 /* Delete the quote with memmove(). */
4770 memmove(cmd, cmd + 1, strlen(cmd));
4771 } else {
4772 /* Check for, and expand, any abbreviated commands. */
4773 if (g_vm->gagt_abbreviations_enabled)
4774 gagt_expand_abbreviations(buffer, length);
4775
4776 /*
4777 * Check for standalone "help", then for Glk port special commands;
4778 * suppress the interpreter's use of this input for Glk commands.
4779 */
4780 if (g_vm->gagt_commands_enabled) {
4781 int posn;
4782
4783 posn = strspn(buffer, "\t ");
4784 if (gagt_strncasecmp(buffer + posn, "help", strlen("help")) == 0) {
4785 if (strspn(buffer + posn + strlen("help"), "\t ")
4786 == strlen(buffer + posn + strlen("help"))) {
4787 gagt_display_register_help_request();
4788 }
4789 }
4790
4791 if (gagt_command_escape(buffer)) {
4792 gagt_display_silence_help_hints();
4793 buffer[0] = '\0';
4794 return buffer;
4795 }
4796 }
4797 }
4798 }
4799
4800 /*
4801 * If there is an input log active, log this input string to it. Note that
4802 * by logging here we get any abbreviation expansions but we won't log glk
4803 * special commands, nor any input read from a current open input log.
4804 */
4805 if (g_vm->gagt_inputlog_stream) {
4806 g_vm->glk_put_string_stream(g_vm->gagt_inputlog_stream, buffer);
4807 g_vm->glk_put_char_stream(g_vm->gagt_inputlog_stream, '\n');
4808 }
4809
4810 /*
4811 * Convert from Glk's ISO 8859 Latin-1 to IBM cp 437, and add to any script.
4812 */
4813 gagt_iso_to_cp((const uchar *)buffer, (uchar *)buffer);
4814 if (script_on)
4815 textputs(scriptfile, buffer);
4816
4817 gagt_debug("agt_input", "in_type=%d -> '%s'", in_type, buffer);
4818 return buffer;
4819 }
4820
4821
4822 /*
4823 * agt_getkey()
4824 *
4825 * Read a single character and return it. AGiliTy defines the echo_char
4826 * argument as:
4827 *
4828 * If echo_char=1, echo character. If 0, then the character is not
4829 * required to be echoed (and ideally shouldn't be).
4830 *
4831 * However, I've found that not all other ports really do this, and in
4832 * practice it doesn't always look right. So for Glk, the character is
4833 * always echoed to the main window.
4834 */
agt_getkey(rbool echo_char)4835 char agt_getkey(rbool echo_char) {
4836 event_t event;
4837 char buffer[3];
4838 assert(g_vm->glk_stream_get_current());
4839
4840 /*
4841 * Update the current status line display, and flush any pending buffered
4842 * output. Release any suspension of delays.
4843 */
4844 gagt_status_notify();
4845 gagt_output_flush();
4846 gagt_delay_resume();
4847
4848 /* Reset current x, as echoed character input implies a newline. */
4849 curr_x = 0;
4850
4851 /*
4852 * If we have an input log to read from, use that as above until it is
4853 * exhausted. We take just the first character of a given line.
4854 */
4855 if (g_vm->gagt_readlog_stream) {
4856 glui32 chars;
4857 char logbuffer[GAGT_INPUTBUFFER_LENGTH + 1];
4858
4859 /* Get the next line from the log stream. */
4860 chars = g_vm->glk_get_line_stream(g_vm->gagt_readlog_stream,
4861 logbuffer, sizeof(logbuffer));
4862 if (chars > 0) {
4863 /* Take just the first character, adding a newline if necessary. */
4864 buffer[0] = logbuffer[0];
4865 buffer[1] = buffer[0] == '\n' ? '\0' : '\n';
4866 buffer[2] = '\0';
4867
4868 /* Echo the character just read in input style. */
4869 g_vm->glk_set_style(style_Input);
4870 g_vm->glk_put_string(buffer);
4871 g_vm->glk_set_style(style_Normal);
4872
4873 /*
4874 * Convert from Glk's ISO 8859 Latin-1 to IBM cp 437, add to any
4875 * script, and return the character.
4876 */
4877 gagt_iso_to_cp((const uchar *)buffer, (uchar *)buffer);
4878 if (script_on)
4879 textputs(scriptfile, buffer);
4880 return buffer[0];
4881 }
4882
4883 /*
4884 * We're at the end of the log stream. Close it, and then continue
4885 * on to request a character from Glk.
4886 */
4887 g_vm->glk_stream_close(g_vm->gagt_readlog_stream, NULL);
4888 g_vm->gagt_readlog_stream = NULL;
4889 }
4890
4891 /*
4892 * Request a single character from main window, and wait. Ignore non-
4893 * ASCII codes that Glk returns for special function keys; we just want
4894 * one ASCII return value. (Glk does treat Return as a special key,
4895 * though, and we want to pass that back as ASCII return.)
4896 */
4897 do {
4898 g_vm->glk_request_char_event(g_vm->gagt_main_window);
4899 gagt_event_wait(evtype_CharInput, &event);
4900 } while (event.val1 > BYTE_MAX_VAL && event.val1 != keycode_Return);
4901
4902 /*
4903 * Save the character into a short string buffer, converting Return
4904 * to newline, and adding a newline if not Return.
4905 */
4906 buffer[0] = event.val1 == keycode_Return ? '\n' : event.val1;
4907 buffer[1] = buffer[0] == '\n' ? '\0' : '\n';
4908 buffer[2] = '\0';
4909
4910 /* If there is an input log active, log this input string to it. */
4911 if (g_vm->gagt_inputlog_stream)
4912 g_vm->glk_put_string_stream(g_vm->gagt_inputlog_stream, buffer);
4913
4914 /*
4915 * No matter what echo_char says, as it happens, the output doesn't look
4916 * great if we don't write out the character, and also a newline (c.f.
4917 * the "Yes/No" confirmation of the QUIT command)...
4918 */
4919 g_vm->glk_set_style(style_Input);
4920 g_vm->glk_put_string(buffer);
4921 g_vm->glk_set_style(style_Normal);
4922
4923 /*
4924 * Convert from Glk's ISO 8859 Latin-1 to IBM cp 437, and add to any
4925 * script.
4926 */
4927 gagt_iso_to_cp((const uchar *)buffer, (uchar *)buffer);
4928 if (script_on)
4929 textputs(scriptfile, buffer);
4930
4931 gagt_debug("agt_getkey", "echo_char=%d -> '%c'",
4932 echo_char, buffer[0] == '\n' ? '$' : buffer[0]);
4933 return buffer[0];
4934 }
4935
4936
4937 /*---------------------------------------------------------------------*/
4938 /* Glk port event functions */
4939 /*---------------------------------------------------------------------*/
4940
4941 /*
4942 * We have some clever atexit() finalizer handling for exit() calls that
4943 * come from the core interpreter. However, an exit() call could also come
4944 * from Glk; Xkill for example. To tell the difference, we'll have the
4945 * event wait functions set a flag to indicate when g_vm->glk_select() is active.
4946 */
4947 static int gagt_in_glk_select = FALSE;
4948
4949 /*
4950 * gagt_event_wait_2()
4951 * gagt_event_wait()
4952 *
4953 * Process Glk events until one of the expected type, or types, arrives.
4954 * Return the event of that type.
4955 */
gagt_event_wait_2(glui32 wait_type_1,glui32 wait_type_2,event_t * event)4956 static void gagt_event_wait_2(glui32 wait_type_1, glui32 wait_type_2, event_t *event) {
4957 assert(event);
4958
4959 do {
4960 gagt_in_glk_select = TRUE;
4961 g_vm->glk_select(event);
4962 gagt_in_glk_select = FALSE;
4963
4964 switch (event->type) {
4965 case evtype_Arrange:
4966 case evtype_Redraw:
4967 gagt_status_redraw();
4968 break;
4969 case evtype_Quit:
4970 return;
4971 default:
4972 break;
4973 }
4974 } while (!(event->type == (EvType)wait_type_1 || event->type == (EvType)wait_type_2));
4975 }
4976
gagt_event_wait(glui32 wait_type,event_t * event)4977 static void gagt_event_wait(glui32 wait_type, event_t *event) {
4978 assert(event);
4979 gagt_event_wait_2(wait_type, evtype_None, event);
4980 }
4981
4982
4983 /*
4984 * gagt_event_in_glk_select()
4985 *
4986 * Return TRUE if we're currently awaiting an event in g_vm->glk_select(). Used
4987 * by the finalizer to distinguish interpreter and glk exit() calls.
4988 */
gagt_event_in_glk_select()4989 static int gagt_event_in_glk_select() {
4990 return gagt_in_glk_select;
4991 }
4992
4993
4994 /*---------------------------------------------------------------------*/
4995 /* Miscellaneous Glk port startup and options functions */
4996 /*---------------------------------------------------------------------*/
4997
4998 /*
4999 * Default screen height and width, and also a default status width for
5000 * use with Glk libraries that don't support separate windows.
5001 */
5002 static const int GAGT_DEFAULT_SCREEN_WIDTH = 80,
5003 GAGT_DEFAULT_SCREEN_HEIGHT = 25,
5004 GAGT_DEFAULT_STATUS_WIDTH = 76;
5005
5006
5007 /*
5008 * agt_option()
5009 *
5010 * Platform-specific setup and options handling. AGiliTy defines the
5011 * arguments and options as:
5012 *
5013 * If setflag is 0, then the option was prefixed with NO_. Return 1 if
5014 * the option is recognized.
5015 *
5016 * The Glk port has no options file handling, so none of this is
5017 * implemented here.
5018 */
agt_option(int optnum,char * optstr[],rbool setflag)5019 rbool agt_option(int optnum, char *optstr[], rbool setflag) {
5020 gagt_debug("agt_option", "optnum=%d, optstr=%s, setflag=%d",
5021 optnum, optstr[0], setflag);
5022 return 0;
5023 }
5024
5025
5026 /*
5027 * agt_globalfile()
5028 *
5029 * Global options file handle handling. For now, this is a stub, since
5030 * there is no .agilrc for this port.
5031 */
agt_globalfile(int fid)5032 genfile agt_globalfile(int fid) {
5033 gagt_debug("agt_globalfile", "fid=%d", fid);
5034 return badfile(fCFG);
5035 }
5036
5037
5038 /*
5039 * init_interface()
5040 *
5041 * General initialization for the module; sets some variables, and creates
5042 * the Glk windows to work in. Called from the AGiliTy main().
5043 */
init_interface()5044 void init_interface() {
5045 glui32 status_height;
5046
5047 /*
5048 * Begin with some default values for global variables that this module
5049 * is somehow responsible for.
5050 */
5051 script_on = center_on = par_fill_on = FALSE;
5052 scriptfile = badfile(fSCR);
5053 debugfile = nullptr; // stderr;
5054
5055 /*
5056 * Set up AGT-specific Glk styles. This needs to be done before any Glk
5057 * window is opened.
5058 */
5059 gagt_init_user_styles();
5060
5061 /*
5062 * Create the main game window. The main game window creation must succeed.
5063 * If it fails, we'll return, and the caller can detect this by looking
5064 * for a NULL main window.
5065 */
5066 g_vm->gagt_main_window = g_vm->glk_window_open(0, 0, 0, wintype_TextBuffer, 0);
5067 if (!g_vm->gagt_main_window)
5068 return;
5069
5070 /*
5071 * Set the main window to be the default window, for convenience. We do
5072 * this again in glk_main() -- this call is here just in case this version
5073 * of init_interface() is ever called by AGiliTy's main.
5074 */
5075 g_vm->glk_set_window(g_vm->gagt_main_window);
5076
5077 /*
5078 * Screen height is something we don't use. Linux Xglk returns dimensions
5079 * of 0x0 for text buffer windows, so we can't measure the main window
5080 * height anyway. But... the height does come into play in AGiliTy's
5081 * agil.c, when the interpreter is deciding how to output game titles, and
5082 * how much of its own subsequent verbiage to output. This gives us a
5083 * problem, since this "verbiage" is stuff we look for and replace with
5084 * our own special text. So... sigh, set 25, and try to cope in the
5085 * special text we've set up with all the variations that ensue.
5086 *
5087 * Screen width does get used, but so, so many games, and for that matter
5088 * the interpreter itself, assume 80 chars, so it's simplest just to set,
5089 * and keep, this, and put up with the minor odd effects (making it match
5090 * status_width, or making it something like MAX_INT to defeat the game's
5091 * own wrapping, gives a lot of odder effects, trust me on this one...).
5092 */
5093 screen_width = GAGT_DEFAULT_SCREEN_WIDTH;
5094 screen_height = GAGT_DEFAULT_SCREEN_HEIGHT;
5095
5096 /*
5097 * Create a status window, with one or two lines as selected by user
5098 * options or flags. We can live without a status window if we have to.
5099 */
5100 status_height = g_vm->gagt_extended_status_enabled ? 2 : 1;
5101 g_vm->glk_stylehint_set(wintype_TextGrid, style_User1, stylehint_ReverseColor, 1);
5102 g_vm->gagt_status_window = g_vm->glk_window_open(g_vm->gagt_main_window,
5103 winmethod_Above | winmethod_Fixed,
5104 status_height, wintype_TextGrid, 0);
5105 if (g_vm->gagt_status_window) {
5106 /*
5107 * Call gagt_status_redraw() to set the interpreter's status_width
5108 * variable initial value.
5109 */
5110 gagt_status_redraw();
5111 } else {
5112 /*
5113 * No status window, so set a suitable default status width. In this
5114 * case, we're using a value four characters less than the set screen
5115 * width. AGiliTy's status line code will fill to this width with
5116 * justified text, and we add two characters of bracketing when
5117 * displaying status lines for Glks that don't support separate windows,
5118 * making a total of 78 characters, which should be fairly standard.
5119 */
5120 status_width = GAGT_DEFAULT_STATUS_WIDTH;
5121 }
5122
5123 agt_clrscr();
5124 }
5125
5126
5127 /*---------------------------------------------------------------------*/
5128 /* Replacement interface.c functions */
5129 /*---------------------------------------------------------------------*/
5130
5131 /* Get_user_file() type codes. */
5132 enum {
5133 AGT_SCRIPT = 0,
5134 AGT_SAVE = 1,
5135 AGT_RESTORE = 2,
5136 AGT_LOG_READ = 3,
5137 AGT_LOG_WRITE = 4
5138 };
5139
5140 /* Longest acceptable filename. */
5141 enum { GAGT_MAX_PATH = 1024 };
5142
5143
5144 #ifdef GLK_ANSI_ONLY
5145 /*
5146 * gagt_confirm()
5147 *
5148 * Print a confirmation prompt, and read a single input character, taking
5149 * only [YyNn] input. If the character is 'Y' or 'y', return TRUE.
5150 *
5151 * This function is only required for the ANSI version of get_user_file().
5152 */
5153 static int
gagt_confirm(const char * prompt)5154 gagt_confirm(const char *prompt) {
5155 event_t event;
5156 unsigned char response;
5157 assert(prompt);
5158
5159 /*
5160 * Print the confirmation prompt, in a style that hints that it's from the
5161 * interpreter, not the game.
5162 */
5163 gagt_standout_string(prompt);
5164
5165 /* Wait for a single 'Y' or 'N' character response. */
5166 response = ' ';
5167 do {
5168 g_vm->glk_request_char_event(g_vm->gagt_main_window);
5169 gagt_event_wait(evtype_CharInput, &event);
5170
5171 if (event.val1 <= BYTE_MAX_VAL)
5172 response = g_vm->glk_char_to_upper(event.val1);
5173 } while (!(response == 'Y' || response == 'N'));
5174
5175 /* Echo the confirmation response, and a blank line. */
5176 g_vm->glk_set_style(style_Input);
5177 g_vm->glk_put_string(response == 'Y' ? "Yes" : "No");
5178 g_vm->glk_set_style(style_Normal);
5179 g_vm->glk_put_string("\n");
5180
5181 return response == 'Y';
5182 }
5183 #endif
5184
5185
5186 /*
5187 * gagt_get_user_file()
5188 *
5189 * Alternative versions of functions to get a file name from the user, and
5190 * return a file stream structure. These functions are front-ended by the
5191 * main get_user_file() function, which first converts the AGT file type
5192 * into Glk usage and filemode, and also a mode for fopen()/fdopen().
5193 *
5194 * The ANSI version of the function prompts for the file using the simple
5195 * method of querying the user through input in the main window. It then
5196 * constructs a file stream around the path entered, and returns it.
5197 *
5198 * The non-ANSI, Glk version is more sneaky. It prompts for a file using
5199 * Glk's functions to get filenames by prompt, file selection dialog, or
5200 * whatever. Then it attempts to uncover which file descriptor Glk opened
5201 * its file on, dup's it, closes the Glk stream, and returns a file stream
5202 * built on this file descriptor. This is all highly non-ANSI, requiring
5203 * dup() and fdopen(), and making some assumptions about the way that dup,
5204 * open, and friends work. It works on Linux, and on Mac (CodeWarrior).
5205 * It may also work for you, but if it doesn't, or if your system lacks
5206 * things like dup or fdopen, define g_vm->glk_ANSI_ONLY and use the safe version.
5207 *
5208 * If GARGLK is used, non-ansi version calls garglk_fileref_get_name()
5209 * instead, and opens a file the highly portable way, but still with a
5210 * Glkily nice prompt dialog.
5211 */
5212 #ifdef GLK_ANSI_ONLY
5213 static genfile
gagt_get_user_file(glui32 usage,glui32 fmode,const char * fdtype)5214 gagt_get_user_file(glui32 usage, glui32 fmode, const char *fdtype) {
5215 char filepath[GAGT_MAX_PATH];
5216 event_t event;
5217 int index, all_spaces;
5218 genfile retfile;
5219 assert(fdtype);
5220
5221 /* Prompt in a similar way to Glk. */
5222 switch (usage) {
5223 case fileusage_SavedGame:
5224 gagt_normal_string("Enter saved game");
5225 break;
5226
5227 case fileusage_Transcript:
5228 gagt_normal_string("Enter transcript file");
5229 break;
5230
5231 case fileusage_InputRecord:
5232 gagt_normal_string("Enter command record file");
5233 break;
5234 }
5235 switch (fmode) {
5236 case filemode_Read:
5237 gagt_normal_string(" to load: ");
5238 break;
5239
5240 case filemode_Write:
5241 gagt_normal_string(" to store: ");
5242 break;
5243 }
5244
5245 /* Get the path to the file from the user. */
5246 g_vm->glk_request_line_event(g_vm->gagt_main_window, filepath, sizeof(filepath) - 1, 0);
5247 gagt_event_wait(evtype_LineInput, &event);
5248
5249 /* Terminate the file path with a NUL. */
5250 assert(event.val1 < sizeof(filepath));
5251 filepath[event.val1] = '\0';
5252
5253 /* Reject file paths that only contain any whitespace characters. */
5254 all_spaces = TRUE;
5255 for (index = 0; index < strlen(filepath); index++) {
5256 if (!isspace(filepath[index])) {
5257 all_spaces = FALSE;
5258 break;
5259 }
5260 }
5261 if (all_spaces)
5262 return badfile(fSAV);
5263
5264 /* Confirm overwrite of any existing file. */
5265 if (fmode == filemode_Write) {
5266 genfile file;
5267
5268 file = fopen(filepath, "r");
5269 if (file) {
5270 fclose(file);
5271
5272 if (!gagt_confirm("Overwrite existing file? [y/n] "))
5273 return badfile(fSAV);
5274 }
5275 }
5276
5277 /* Open and return a FILE* stream, or badfile if this fails. */
5278 retfile = fopen(filepath, fdtype);
5279 return retfile ? retfile : badfile(fSAV);
5280 }
5281 #endif
5282
5283 #ifndef GLK_ANSI_ONLY
gagt_get_user_file(glui32 usage,glui32 fmode,const char * fdtype)5284 static genfile gagt_get_user_file(glui32 usage, glui32 fmode, const char *fdtype) {
5285 frefid_t fileref;
5286 genfile retfile;
5287 assert(fdtype);
5288
5289 /* Try to get a Glk file reference with these attributes. */
5290 fileref = g_vm->glk_fileref_create_by_prompt(usage, (FileMode)fmode, 0);
5291 if (!fileref)
5292 return badfile(fSAV);
5293
5294 /*
5295 * Reject the file reference if we're expecting to read from it,
5296 * and the referenced file doesn't exist.
5297 */
5298 if (fmode == filemode_Read && !g_vm->glk_fileref_does_file_exist(fileref)) {
5299 g_vm->glk_fileref_destroy(fileref);
5300 return badfile(fSAV);
5301 }
5302
5303 /*
5304 * Now, it gets ugly. Glk assumes that the interpreter will do all of
5305 * its reading and writing using the Glk streams read/write functions.
5306 * It won't; at least, not without major surgery. So here we're going
5307 * to do some dangerous stuff...
5308 *
5309 * Since a Glk stream is opaque, it's hard to tell what the underlying
5310 * file descriptor is for it. We can get it if we want to play around
5311 * in the internals of the strid_t structure, but it's unpleasant.
5312 * The alternative is, arguably, no more pleasant, but it makes for
5313 * (perhaps) more portable code. What we'll do is to dup a file, then
5314 * immediately close it, and call g_vm->glk_stream_open_file(). The open()
5315 * in g_vm->glk_stream_open_file() will return the same file descriptor number
5316 * that we just close()d (in theory...). This makes the following two
5317 * major assumptions:
5318 *
5319 * 1) g_vm->glk_stream_open_file() opens precisely one file with open()
5320 * 2) open() always uses the lowest available file descriptor number,
5321 * like dup()
5322 *
5323 * Believe it or not, this is better than the alternatives. There is
5324 * no Glk function to return the filename from a frefid_t, and it
5325 * moves about in different Glk libraries so we can't just take it
5326 * from a given offset. And there is no Glk function to return the
5327 * underlying file descriptor or FILE* from a Glk stream either. :-(
5328 */
5329
5330 #ifdef GARGLK
5331 retfile = fopen(g_vm->garglk_fileref_get_name(fileref), fdtype);
5332 #else
5333 strid_t stream;
5334 int tryfd, glkfd, dupfd, retfd;
5335
5336 /* So, start by dup()'ing the first file descriptor we can, ... */
5337 glkfd = -1;
5338 for (tryfd = 0; tryfd < FD_SETSIZE; tryfd++) {
5339 glkfd = fcntl(tryfd, F_DUPFD, 0);
5340 if (glkfd != -1)
5341 break;
5342 }
5343 if (tryfd >= FD_SETSIZE) {
5344 g_vm->glk_fileref_destroy(fileref);
5345 return badfile(fSAV);
5346 }
5347
5348 /* ...then closing it, ... */
5349 close(glkfd);
5350
5351 /* ...now open the Glk stream, assuming it opens on file 'glkfd', ... */
5352 stream = g_vm->glk_stream_open_file(fileref, fmode, 0);
5353 if (!stream) {
5354 g_vm->glk_fileref_destroy(fileref);
5355 return badfile(fSAV);
5356 }
5357
5358 /* ...dup() the Glk file onto another file descriptor, ... */
5359 dupfd = fcntl(glkfd, F_DUPFD, 0);
5360 assert(dupfd != -1);
5361
5362 /* ...close and destroy the Glk edifice for this file, ... */
5363 g_vm->glk_stream_close(stream, NULL);
5364 g_vm->glk_fileref_destroy(fileref);
5365
5366 /* ...for neatness, dup() back to the old Glk file descriptor, ... */
5367 retfd = fcntl(dupfd, F_DUPFD, 0);
5368 assert(retfd != -1 && retfd == glkfd);
5369 close(dupfd);
5370
5371 /* ...and finally, open a FILE* stream onto the return descriptor. */
5372 retfile = fdopen(retfd, fdtype);
5373 if (!retfile)
5374 return badfile(fSAV);
5375 #endif /* GARGLK */
5376
5377 /*
5378 * The result of all of this should now be that retfile is a FILE* wrapper
5379 * round a file descriptor open on a file indicated by the user through Glk.
5380 * Return it.
5381 */
5382 return retfile;
5383 }
5384 #endif
5385
5386
5387 /*
5388 * get_user_file()
5389 *
5390 * Get a file name from the user, and return the file stream structure.
5391 * This is a front-end to ANSI and non-ANSI variants of the function.
5392 */
get_user_file(int type)5393 genfile get_user_file(int type) {
5394 glui32 usage = 0, fmode = 0;
5395 const char *fdtype;
5396 genfile retfile;
5397
5398 gagt_output_flush();
5399
5400 /* Map AGiliTy type to Glk usage and filemode. */
5401 switch (type) {
5402 case AGT_SCRIPT:
5403 usage = fileusage_Transcript;
5404 fmode = filemode_Write;
5405 break;
5406
5407 case AGT_SAVE:
5408 usage = fileusage_SavedGame;
5409 fmode = filemode_Write;
5410 break;
5411
5412 case AGT_RESTORE:
5413 usage = fileusage_SavedGame;
5414 fmode = filemode_Read;
5415 break;
5416
5417 case AGT_LOG_READ:
5418 usage = fileusage_InputRecord;
5419 fmode = filemode_Read;
5420 break;
5421
5422 case AGT_LOG_WRITE:
5423 usage = fileusage_InputRecord;
5424 fmode = filemode_Write;
5425 break;
5426
5427 default:
5428 gagt_fatal("GLK: Unknown file type encountered");
5429 gagt_exit();
5430 }
5431
5432 /* From these, determine a mode type for the f[d]open() call. */
5433 if (fmode == filemode_Write)
5434 fdtype = usage == fileusage_SavedGame ? "wb" : "w";
5435 else
5436 fdtype = usage == fileusage_SavedGame ? "rb" : "r";
5437
5438 /* Get a file stream from these using the appropriate function. */
5439 retfile = gagt_get_user_file(usage, fmode, fdtype);
5440
5441 gagt_debug("get_user_file", "type=%d -> %p", type, retfile);
5442 return retfile;
5443 }
5444
5445
5446 /*
5447 * set_default_filenames()
5448 *
5449 * Set defaults for last save, log, and script filenames.
5450 */
set_default_filenames(fc_type fc)5451 void set_default_filenames(fc_type fc) {
5452 /*
5453 * There is nothing to do in this function, since Glk has its own ideas on
5454 * default names for files obtained with a prompt.
5455 */
5456 gagt_debug("set_default_filenames", "fc=%p", fc);
5457 }
5458
5459
5460 /*---------------------------------------------------------------------*/
5461 /* Functions intercepted by link-time wrappers */
5462 /*---------------------------------------------------------------------*/
5463
5464 /*
5465 * __wrap_toupper()
5466 * __wrap_tolower()
5467 *
5468 * Wrapper functions around toupper(), tolower(), and fatal(). The Linux
5469 * linker's --wrap option will convert calls to mumble() to __wrap_mumble()
5470 * if we give it the right options. We'll use this feature to translate
5471 * all toupper() and tolower() calls in the interpreter code into calls to
5472 * Glk's versions of these functions.
5473 *
5474 * It's not critical that we do this. If a linker, say a non-Linux one,
5475 * won't do --wrap, then just do without it. It's unlikely that there
5476 * will be much noticeable difference.
5477 */
__wrap_toupper(int ch)5478 int __wrap_toupper(int ch) {
5479 unsigned char uch;
5480
5481 uch = g_vm->glk_char_to_upper((unsigned char) ch);
5482 return (int) uch;
5483 }
5484
__wrap_tolower(int ch)5485 int __wrap_tolower(int ch) {
5486 unsigned char lch;
5487
5488 lch = g_vm->glk_char_to_lower((unsigned char) ch);
5489 return (int) lch;
5490 }
5491
5492
5493 /*---------------------------------------------------------------------*/
5494 /* Replacements for AGiliTy main() and options parsing */
5495 /*---------------------------------------------------------------------*/
5496
5497 /* External declaration of interface.c's set default options function. */
5498 extern void set_default_options();
5499
5500
5501 /*
5502 * gagt_startup_code()
5503 * gagt_main()
5504 *
5505 * Together, these functions take the place of the original AGiliTy main().
5506 * The first one is called from glkunix_startup_code(). The second is called
5507 * from glk_main(), and does the real work of running the game.
5508 */
gagt_startup_code()5509 bool gagt_startup_code() {
5510 /* Make the mandatory call for initialization. */
5511 set_default_options();
5512
5513 /* All startup options were handled successfully. */
5514 return TRUE;
5515 }
5516
gagt_main()5517 static void gagt_main() {
5518 fc_type fc;
5519
5520 /*
5521 * Initialize the interface. As it happens, init_interface() is in our
5522 * module here (above), and ignores argc and argv, but since the main() in
5523 * AGiliTy passes them, we'll do so here, just in case we ever want to go
5524 * back to using AGiliTy's main() function.
5525 *
5526 * init_interface() can fail if there is a problem creating the main
5527 * window. As it doesn't return status, we have to detect this by checking
5528 * that g_vm->gagt_main_window is not NULL.
5529 */
5530 init_interface();
5531 if (!g_vm->gagt_main_window) {
5532 gagt_fatal("GLK: Can't open main window");
5533 gagt_exit();
5534 }
5535 g_vm->glk_window_clear(g_vm->gagt_main_window);
5536 g_vm->glk_set_window(g_vm->gagt_main_window);
5537 g_vm->glk_set_style(style_Normal);
5538
5539 /*
5540 * Create a game file context, and try to ensure it will open successfully
5541 * in run_game().
5542 */
5543 fc = init_file_context(g_vm->gagt_gamefile, fDA1);
5544 if (!(gagt_workround_fileexist(fc, fAGX)
5545 || gagt_workround_fileexist(fc, fDA1))) {
5546 if (g_vm->gagt_status_window)
5547 g_vm->glk_window_close(g_vm->gagt_status_window, NULL);
5548 gagt_header_string("Glk AGiliTy Error\n\n");
5549 gagt_normal_string("Can't find or open game '");
5550 gagt_normal_string(g_vm->gagt_gamefile);
5551 gagt_normal_char('\'');
5552 gagt_normal_char('\n');
5553 gagt_exit();
5554 }
5555
5556 /*
5557 * Run the game interpreter in AGiliTy. run_game() releases the file
5558 * context, so we don't have to, don't want to, and shouldn't.
5559 */
5560 run_game(fc);
5561
5562 /*
5563 * Handle any updated status, and flush all remaining buffered output;
5564 * this also frees all malloc'ed memory in the buffers.
5565 */
5566 gagt_status_notify();
5567 gagt_output_flush();
5568
5569 /*
5570 * Free any temporary memory that may have been used by status line
5571 * functions.
5572 */
5573 gagt_status_cleanup();
5574
5575 /* Close any open transcript, input log, and/or read log. */
5576 if (g_vm->gagt_transcript_stream) {
5577 g_vm->glk_stream_close(g_vm->gagt_transcript_stream, NULL);
5578 g_vm->gagt_transcript_stream = NULL;
5579 }
5580 if (g_vm->gagt_inputlog_stream) {
5581 g_vm->glk_stream_close(g_vm->gagt_inputlog_stream, NULL);
5582 g_vm->gagt_inputlog_stream = NULL;
5583 }
5584 if (g_vm->gagt_readlog_stream) {
5585 g_vm->glk_stream_close(g_vm->gagt_readlog_stream, NULL);
5586 g_vm->gagt_readlog_stream = NULL;
5587 }
5588 }
5589
5590
5591 /*---------------------------------------------------------------------*/
5592 /* Linkage between Glk entry/exit calls and the AGiliTy interpreter */
5593 /*---------------------------------------------------------------------*/
5594
5595 /*
5596 * Safety flags, to ensure we always get startup before main, and that
5597 * we only get a call to main once.
5598 */
5599 static int gagt_startup_called = FALSE,
5600 gagt_main_called = FALSE;
5601
5602 /*
5603 * We try to catch calls to exit() from the interpreter, and redirect them
5604 * to g_vm->glk_exit(). To help tell these calls from a call to exit() from
5605 * g_vm->glk_exit() itself, we need to monitor when interpreter code is running,
5606 * and when not.
5607 */
5608 static int gagt_agility_running = FALSE;
5609
5610
5611 /*
5612 * gagt_finalizer()
5613 *
5614 * ANSI atexit() handler. This is the first part of trying to catch and re-
5615 * direct the calls the core AGiliTy interpreter makes to exit() -- we really
5616 * want it to call g_vm->glk_exit(), but it's hard to achieve. There are three
5617 * basic approaches possible, and all have drawbacks:
5618 *
5619 * o #define exit to gagt_something, and provide the gagt_something()
5620 * function. This type of macro definition is portable for the most
5621 * part, but tramples the code badly, and messes up the build of the
5622 * non-interpreter "support" binaries.
5623 * o Use ld's --wrap to wrapper exit. This only works with Linux's linker
5624 * and so isn't at all portable.
5625 * o Register an exit handler with atexit(), and try to cope in it after
5626 * exit() has been called.
5627 *
5628 * Here we try the last of these. The one sticky part of it is that in our
5629 * exit handler we'll want to call g_vm->glk_exit(), which will in all likelihood
5630 * call exit(). And multiple calls to exit() from a program are "undefined".
5631 *
5632 * In practice, C runtimes tend to do one of three things: they treat the
5633 * exit() call from the exit handler as if it was a return; they recurse
5634 * indefinitely through the hander; or they do something ugly (abort, for
5635 * example). The first of these is fine, ideal in fact, and seems to be the
5636 * Linux and SVR4 behavior. The second we can avoid with a flag. The last
5637 * is the problem case, seen only with SVR3 (and even then, it occurs only
5638 * on program exit, after everything's cleaned up, and for that matter only
5639 * on abnormal exit).
5640 *
5641 * Note that here we're not expecting to get a call to this routine, and if
5642 * we do, and interpreter code is still running, it's a sign that we need
5643 * to take actions we'd hoped not to have to take.
5644 */
gagt_finalizer()5645 void gagt_finalizer() {
5646 /*
5647 * If interpreter code is still active, and we're not in a g_vm->glk_select(),
5648 * the core interpreter code called exit(). Handle cleanup.
5649 */
5650 if (gagt_agility_running && !gagt_event_in_glk_select()) {
5651 event_t event;
5652
5653 /*
5654 * If we have a main window, try to update status (which may go to the
5655 * status window, or to the main window) and flush any pending buffered
5656 * output.
5657 */
5658 if (g_vm->gagt_main_window) {
5659 gagt_status_notify();
5660 gagt_output_flush();
5661 }
5662
5663 /*
5664 * Clear the flag to avoid recursion, and call g_vm->glk_exit() to clean up
5665 * Glk and terminate. This is the call that probably re-calls exit(),
5666 * and thus prods "undefined" bits of the C runtime, so we'll make it
5667 * configurable and overrideable for problem cases.
5668 */
5669 gagt_agility_running = FALSE;
5670
5671 /*
5672 * We've decided not to take the dangerous route.
5673 *
5674 * In that case, providing we have a main window, fake a Glk-like-ish
5675 * hit-any-key-and-wait message using a simple string in the main
5676 * window. Not great, but usable where we're forced into bypassing
5677 * g_vm->glk_exit(). If we have no main window, there's no point in doing
5678 * anything more.
5679 */
5680 if (g_vm->gagt_main_window) {
5681 g_vm->glk_cancel_char_event(g_vm->gagt_main_window);
5682 g_vm->glk_cancel_line_event(g_vm->gagt_main_window, NULL);
5683
5684 g_vm->glk_set_style(style_Alert);
5685 g_vm->glk_put_string("\n\nHit any key to exit.\n");
5686 g_vm->glk_request_char_event(g_vm->gagt_main_window);
5687 gagt_event_wait(evtype_CharInput, &event);
5688 }
5689 }
5690 }
5691
5692
5693 /*
5694 * gagt_exit()
5695 *
5696 * g_vm->glk_exit() local wrapper. This is the second part of trying to catch
5697 * and redirect calls to exit(). g_vm->glk_finalizer() above needs to know that
5698 * we called g_vm->glk_exit() already from here, so it doesn't try to do it again.
5699 */
gagt_exit()5700 static void gagt_exit() {
5701 assert(gagt_agility_running);
5702
5703 /*
5704 * Clear the running flag to neutralize gagt_finalizer(), throw out any
5705 * buffered output data, and then call the real g_vm->glk_exit().
5706 */
5707 gagt_agility_running = FALSE;
5708 gagt_output_delete();
5709 g_vm->glk_exit();
5710 }
5711
5712
5713 /*
5714 * __wrap_exit()
5715 *
5716 * Exit() wrapper where a linker does --wrap. This is the third part of
5717 * trying to catch and redirect calls to exit().
5718 *
5719 * This function is for use only with IFP, and avoids a nasty attempt at
5720 * reusing a longjmp buffer. IFP will redirect calls to exit() into
5721 * g_vm->glk_exit() as a matter of course. It also handles atexit(), and we've
5722 * registered a function with atexit() that calls g_vm->glk_exit(), and
5723 * IFP redirects g_vm->glk_exit() to be an effective return from glk_main(). At
5724 * that point it calls finalizers. So without doing something special for
5725 * IFP, we'll find ourselves calling g_vm->glk_exit() twice -- once as the IFP
5726 * redirected exit(), and once from our finalizer. Two returns from the
5727 * function glk_main() is a recipe for unpleasantness.
5728 *
5729 * As IFP is Linux-only, at present, --wrap will always be available to IFP
5730 * plugin builds. So here, we'll wrap exit() before IFP can get to it, and
5731 * handle it safely. For non-IFP/non-wrap links, this is just an unused
5732 * function definition, and can be safely ignored...
5733 */
__wrap_exit(int status)5734 void __wrap_exit(int status) {
5735 assert(gagt_agility_running);
5736
5737 /*
5738 * In an IFP plugin, only the core interpreter code could have called exit()
5739 * here -- we don't, and IFP redirects g_vm->glk_exit(), the only other potential
5740 * caller of exit(). (It also redirects exit() if we don't get to it here
5741 * first.)
5742 *
5743 * So, if we have a main window, flush it. This is the same cleanup as
5744 * done by the finalizer.
5745 */
5746 if (g_vm->gagt_main_window) {
5747 gagt_status_notify();
5748 gagt_output_flush();
5749 }
5750
5751 /* Clear the running flag, and transform exit() into a g_vm->glk_exit(). */
5752 gagt_agility_running = FALSE;
5753 g_vm->glk_exit();
5754 }
5755
5756
5757 /*
5758 * glk_main)
5759 *
5760 * Main entry point for Glk. Here, all startup is done, and we call our
5761 * function to run the game.
5762 */
glk_main()5763 void glk_main() {
5764 assert(gagt_startup_called && !gagt_main_called);
5765 gagt_main_called = TRUE;
5766
5767 /*
5768 * If we're testing for a clean exit, deliberately call exit() to see what
5769 * happens. We're hoping for a clean process termination, but our exit
5770 * code explores "undefined" ANSI. If we get something ugly, like a core
5771 * dump, we'll want to set GLK[AGIL]_CLEAN_EXIT.
5772 */
5773 if (g_vm->gagt_clean_exit_test) {
5774 gagt_agility_running = TRUE;
5775 return;
5776 }
5777
5778 /*
5779 * The final part of trapping exit(). Set the running flag, and call the
5780 * interpreter main function. Clear the flag when the main function returns.
5781 */
5782 gagt_agility_running = TRUE;
5783 gagt_main();
5784 gagt_agility_running = FALSE;
5785 }
5786
5787
5788 /*---------------------------------------------------------------------*/
5789 /* Glk linkage relevant only to the UNIX platform */
5790 /*---------------------------------------------------------------------*/
5791
5792 /*
5793 * Glk arguments for UNIX versions of the Glk interpreter.
5794 */
5795 /*
5796 glkunix_argumentlist_t glkunix_arguments[] = {
5797 {(char *) "-gf", glkunix_arg_NoValue,
5798 (char *) "-gf Force Glk to use only a fixed width font"},
5799 {(char *) "-gp", glkunix_arg_NoValue,
5800 (char *) "-gp Allow Glk to use only a proportional font"},
5801 {(char *) "-ga", glkunix_arg_NoValue,
5802 (char *) "-ga Try to use a suitable Glk font automatically"},
5803 {(char *) "-gd", glkunix_arg_NoValue,
5804 (char *) "-gd Delay for the full period in Glk"},
5805 {(char *) "-gh", glkunix_arg_NoValue,
5806 (char *) "-gh Delay for approximately half the period in Glk"},
5807 {(char *) "-gn", glkunix_arg_NoValue,
5808 (char *) "-gn Turn off all game delays in Glk"},
5809 {(char *) "-gr", glkunix_arg_NoValue,
5810 (char *) "-gr Turn off Glk text replacement"},
5811 {(char *) "-gx", glkunix_arg_NoValue,
5812 (char *) "-gx Turn off Glk abbreviation expansions"},
5813 {(char *) "-gs", glkunix_arg_NoValue,
5814 (char *) "-gs Display a short status window in Glk"},
5815 {(char *) "-gl", glkunix_arg_NoValue,
5816 (char *) "-gl Display an extended status window in Glk"},
5817 {(char *) "-gc", glkunix_arg_NoValue,
5818 (char *) "-gc Turn off Glk command escapes in games"},
5819 {(char *) "-gD", glkunix_arg_NoValue,
5820 (char *) "-gD Turn on Glk port module debug tracing"},
5821 {(char *) "-g#", glkunix_arg_NoValue,
5822 (char *) "-g# Test for clean exit (Glk module debugging only)"},
5823 {(char *) "-1", glkunix_arg_NoValue,
5824 (char *) "-1 IRUN Mode: Print messages in first person"},
5825 {(char *) "-d", glkunix_arg_NoValue,
5826 (char *) "-d Debug metacommand execution"},
5827 {(char *) "-t", glkunix_arg_NoValue,
5828 (char *) "-t Test mode"},
5829 {(char *) "-c", glkunix_arg_NoValue,
5830 (char *) "-c Create test file"},
5831 {(char *) "-m", glkunix_arg_NoValue,
5832 (char *) "-m Force descriptions to be loaded from disk"},
5833 #ifdef OPEN_AS_TEXT
5834 {(char *) "-b", glkunix_arg_NoValue,
5835 (char *) "-b Open data files as binary files"},
5836 #endif
5837 {(char *) "-p", glkunix_arg_NoValue,
5838 (char *) "-p Debug parser"},
5839 {(char *) "-x", glkunix_arg_NoValue,
5840 (char *) "-x Debug verb execution loop"},
5841 {(char *) "-a", glkunix_arg_NoValue,
5842 (char *) "-a Debug disambiguation system"},
5843 {(char *) "-s", glkunix_arg_NoValue,
5844 (char *) "-s Debug STANDARD message handler"},
5845 #ifdef MEM_INFO
5846 {(char *) "-M", glkunix_arg_NoValue,
5847 (char *) "-M Debug memory allocation"},
5848 #endif
5849 {(char *) "", glkunix_arg_ValueCanFollow,
5850 (char *) "filename game to run"},
5851 {NULL, glkunix_arg_End, NULL}
5852 };
5853
5854 */
5855 /*
5856 * glkunix_startup_code()
5857 *
5858 * Startup entry point for UNIX versions of Glk AGiliTy. Glk will call
5859 * glkunix_startup_code() to pass in arguments. On startup, we call our
5860 * function to parse arguments and generally set stuff up.
5861 */
5862
glk_startup_code()5863 int glk_startup_code() {
5864 assert(!gagt_startup_called);
5865 gagt_startup_called = TRUE;
5866
5867 return gagt_startup_code();
5868 }
5869
5870 } // End of namespace AGT
5871 } // End of namespace Glk
5872