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/magnetic/magnetic_defs.h"
24 #include "glk/magnetic/magnetic.h"
25
26 namespace Glk {
27 namespace Magnetic {
28
29 const gms_command_t Magnetic::GMS_COMMAND_TABLE[14] = {
30 { &Magnetic::gms_command_summary, "summary", false, false },
31 { &Magnetic::gms_command_undo, "undo", false, true },
32 { &Magnetic::gms_command_script, "script", true, false },
33 { &Magnetic::gms_command_inputlog, "inputlog", true, false },
34 { &Magnetic::gms_command_readlog, "readlog", true, false },
35 { &Magnetic::gms_command_abbreviations, "abbreviations", true, false },
36 { &Magnetic::gms_command_graphics, "graphics", true, false },
37 { &Magnetic::gms_command_gamma, "gamma", true, false },
38 { &Magnetic::gms_command_animations, "animations", true, false },
39 { &Magnetic::gms_command_prompts, "prompts", true, false },
40 { &Magnetic::gms_command_version, "version", false, false },
41 { &Magnetic::gms_command_commands, "commands", true, false },
42 { &Magnetic::gms_command_help, "help", true, false },
43
44 { nullptr, nullptr, false, false}
45 };
46
47 const gms_gamma_t Magnetic::GMS_GAMMA_TABLE[38] = {
48 { "0.90", { 0, 29, 63, 99, 137, 175, 215, 255 }, true },
49 { "0.95", { 0, 33, 68, 105, 141, 179, 217, 255 }, true },
50 { "1.00", { 0, 36, 73, 109, 146, 182, 219, 255 }, false },
51 { "1.05", { 0, 40, 77, 114, 150, 185, 220, 255 }, true },
52 { "1.10", { 0, 43, 82, 118, 153, 188, 222, 255 }, true },
53 { "1.15", { 0, 47, 86, 122, 157, 190, 223, 255 }, true },
54 { "1.20", { 0, 50, 90, 126, 160, 193, 224, 255 }, true },
55 { "1.25", { 0, 54, 94, 129, 163, 195, 225, 255 }, true },
56 { "1.30", { 0, 57, 97, 133, 166, 197, 226, 255 }, true },
57 { "1.35", { 0, 60, 101, 136, 168, 199, 227, 255 }, true },
58 { "1.40", { 0, 64, 104, 139, 171, 201, 228, 255 }, true },
59 { "1.45", { 0, 67, 107, 142, 173, 202, 229, 255 }, true },
60 { "1.50", { 0, 70, 111, 145, 176, 204, 230, 255 }, true },
61 { "1.55", { 0, 73, 114, 148, 178, 205, 231, 255 }, true },
62 { "1.60", { 0, 76, 117, 150, 180, 207, 232, 255 }, true },
63 { "1.65", { 0, 78, 119, 153, 182, 208, 232, 255 }, true },
64 { "1.70", { 0, 81, 122, 155, 183, 209, 233, 255 }, true },
65 { "1.75", { 0, 84, 125, 157, 185, 210, 233, 255 }, true },
66 { "1.80", { 0, 87, 127, 159, 187, 212, 234, 255 }, true },
67 { "1.85", { 0, 89, 130, 161, 188, 213, 235, 255 }, true },
68 { "1.90", { 0, 92, 132, 163, 190, 214, 235, 255 }, true },
69 { "1.95", { 0, 94, 134, 165, 191, 215, 236, 255 }, true },
70 { "2.00", { 0, 96, 136, 167, 193, 216, 236, 255 }, true },
71 { "2.05", { 0, 99, 138, 169, 194, 216, 237, 255 }, true },
72 { "2.10", { 0, 101, 140, 170, 195, 217, 237, 255 }, true },
73 { "2.15", { 0, 103, 142, 172, 197, 218, 237, 255 }, true },
74 { "2.20", { 0, 105, 144, 173, 198, 219, 238, 255 }, true },
75 { "2.25", { 0, 107, 146, 175, 199, 220, 238, 255 }, true },
76 { "2.30", { 0, 109, 148, 176, 200, 220, 238, 255 }, true },
77 { "2.35", { 0, 111, 150, 178, 201, 221, 239, 255 }, true },
78 { "2.40", { 0, 113, 151, 179, 202, 222, 239, 255 }, true },
79 { "2.45", { 0, 115, 153, 180, 203, 222, 239, 255 }, true },
80 { "2.50", { 0, 117, 154, 182, 204, 223, 240, 255 }, true },
81 { "2.55", { 0, 119, 156, 183, 205, 223, 240, 255 }, true },
82 { "2.60", { 0, 121, 158, 184, 206, 224, 240, 255 }, true },
83 { "2.65", { 0, 122, 159, 185, 206, 225, 241, 255 }, true },
84 { "2.70", { 0, 124, 160, 186, 207, 225, 241, 255 }, true },
85 { NULL, { 0, 0, 0, 0, 0, 0, 0, 0 }, false }
86 };
87
88 static gms_abbreviation_t GMS_ABBREVIATIONS[] = {
89 {'c', "close"}, {'g', "again"}, {'i', "inventory"},
90 {'k', "attack"}, {'l', "look"}, {'p', "open"},
91 {'q', "quit"}, {'r', "drop"}, {'t', "take"},
92 {'x', "examine"}, {'y', "yes"}, {'z', "wait"},
93 {'\0', NULL}
94 };
95
96 /*---------------------------------------------------------------------*/
97 /* Module constants */
98 /*---------------------------------------------------------------------*/
99
100 #ifndef GARGLK
101 /*
102 * Maximum number of regions to consider in a single repaint pass. A
103 * couple of hundred seems to strike the right balance between not too
104 * sluggardly picture updates, and responsiveness to input during graphics
105 * rendering, when combined with short timeouts.
106 */
107 static const int GMS_REPAINT_LIMIT = 256;
108 #endif
109
110 /*
111 * Graphics timeout; we like an update call after this period (ms). In
112 * practice, this timeout may actually be shorter than the time taken
113 * to reach the limit on repaint regions, but because Glk guarantees that
114 * user interactions (in this case, line events) take precedence over
115 * timeouts, this should be okay; we'll still see a game that responds to
116 * input each time the background repaint function yields.
117 *
118 * Setting this value is tricky. We'd like it to be the shortest possible
119 * consistent with getting other stuff done, say 10ms. However, Xglk has
120 * a granularity of 50ms on checking for timeouts, as it uses a 1/20s
121 * timeout on X select. This means that the shortest timeout we'll ever
122 * get from Xglk will be 50ms, so there's no point in setting this shorter
123 * than that. With luck, other Glk libraries will be more efficient than
124 * this, and can give us higher timer resolution; we'll set 50ms here, and
125 * hope that no other Glk library is worse.
126 */
127 static const glui32 GMS_GRAPHICS_TIMEOUT = 50;
128
129 /*
130 * Count of timeouts to wait in between animation paints, and to wait on
131 * repaint request. Waiting for 2 timeouts of around 50ms, gets us to the
132 * 100ms recommended animation frame rate. Waiting after a repaint smooths
133 * the display where the frame is being resized, by helping to avoid
134 * graphics output while more resize events are received; around 1/2 second
135 * seems okay.
136 */
137 static const int GMS_GRAPHICS_ANIMATION_WAIT = 2,
138 GMS_GRAPHICS_REPAINT_WAIT = 10;
139
140 /* Pixel size multiplier for image size scaling. */
141 static const int GMS_GRAPHICS_PIXEL = 2;
142
143 /* Proportion of the display to use for graphics. */
144 static const glui32 GMS_GRAPHICS_PROPORTION = 60;
145
146 /*
147 * Border and shading control. For cases where we can't detect the back-
148 * ground color of the main window, there's a default, white, background.
149 * Bordering is black, with a 1 pixel border, 2 pixel shading, and 8 steps
150 * of shading fade.
151 */
152 static const glui32 GMS_GRAPHICS_DEFAULT_BACKGROUND = 0x00ffffff,
153 GMS_GRAPHICS_BORDER_COLOR = 0x00000000;
154 static const int GMS_GRAPHICS_BORDER = 1,
155 GMS_GRAPHICS_SHADING = 2,
156 GMS_GRAPHICS_SHADE_STEPS = 8;
157
158 /*
159 * Guaranteed unused pixel value. This value is used to fill the on-screen
160 * buffer on new pictures or repaints, resulting in a full paint of all
161 * pixels since no off-screen, real picture, pixel will match it.
162 */
163 static const int GMS_GRAPHICS_UNUSED_PIXEL = 0xff;
164
165 /* Default width used for non-windowing Glk status lines. */
166 static const int GMS_DEFAULT_STATUS_WIDTH = 74;
167
168 /* Success and fail return codes from hint functions. */
169 static const type8 GMS_HINT_SUCCESS = 1;
170 // static const type8 GMS_HINT_ERROR = 0;
171
172 /* Default window sizes for non-windowing Glk libraries. */
173 static const glui32 GMS_HINT_DEFAULT_WIDTH = 72,
174 GMS_HINT_DEFAULT_HEIGHT = 25;
175
176 /*
177 * Special hint nodes indicating the root hint node, and a value to signal
178 * quit from hints subsystem.
179 */
180 static const type16 GMS_HINT_ROOT_NODE = 0,
181 GMS_HINTS_DONE = UINT16_MAX_VAL;
182
183 /* Generic hint topic for the root hints node. */
184 static const char *const GMS_GENERIC_TOPIC = "Hints Menu";
185
186 /*---------------------------------------------------------------------*/
187 /* Glk port utility functions */
188 /*---------------------------------------------------------------------*/
189
gms_fatal(const char * str)190 void Magnetic::gms_fatal(const char *str) {
191 /*
192 * If the failure happens too early for us to have a window, print
193 * the message to stderr.
194 */
195 if (!gms_main_window)
196 error("\n\nINTERNAL ERROR: %s", str);
197
198 /* Cancel all possible pending window input events. */
199 glk_cancel_line_event(gms_main_window, NULL);
200 glk_cancel_char_event(gms_main_window);
201 if (gms_hint_menu_window) {
202 glk_cancel_char_event(gms_hint_menu_window);
203 glk_window_close(gms_hint_menu_window, NULL);
204 }
205 if (gms_hint_text_window) {
206 glk_cancel_char_event(gms_hint_text_window);
207 glk_window_close(gms_hint_text_window, NULL);
208 }
209
210 /* Print a message indicating the error. */
211 glk_set_window(gms_main_window);
212 glk_set_style(style_Normal);
213 glk_put_string("\n\nINTERNAL ERROR: ");
214 glk_put_string(str);
215
216 glk_put_string("\n\nPlease record the details of this error, try to"
217 " note down everything you did to cause it, and email"
218 " this information to simon_baldwin@yahoo.com.\n\n");
219 }
220
gms_malloc(size_t size)221 void *Magnetic::gms_malloc(size_t size) {
222 void *pointer;
223
224 pointer = malloc(size);
225 if (!pointer) {
226 gms_fatal("GLK: Out of system memory");
227 glk_exit();
228 }
229
230 return pointer;
231 }
232
gms_realloc(void * ptr,size_t size)233 void *Magnetic::gms_realloc(void *ptr, size_t size) {
234 void *pointer;
235
236 pointer = realloc(ptr, size);
237 if (!pointer) {
238 gms_fatal("GLK: Out of system memory");
239 glk_exit();
240 }
241
242 return pointer;
243 }
244
gms_strncasecmp(const char * s1,const char * s2,size_t n)245 int Magnetic::gms_strncasecmp(const char *s1, const char *s2, size_t n) {
246 size_t index;
247
248 for (index = 0; index < n; index++) {
249 int diff;
250
251 diff = glk_char_to_lower(s1[index]) - glk_char_to_lower(s2[index]);
252 if (diff < 0 || diff > 0)
253 return diff < 0 ? -1 : 1;
254 }
255
256 return 0;
257 }
258
gms_strcasecmp(const char * s1,const char * s2)259 int Magnetic::gms_strcasecmp(const char *s1, const char *s2) {
260 size_t s1len, s2len;
261 int result;
262
263 s1len = strlen(s1);
264 s2len = strlen(s2);
265
266 result = gms_strncasecmp(s1, s2, s1len < s2len ? s1len : s2len);
267 if (result < 0 || result > 0)
268 return result;
269 else
270 return s1len < s2len ? -1 : s1len > s2len ? 1 : 0;
271 }
272
273 /*---------------------------------------------------------------------*/
274 /* Glk port CRC functions */
275 /*---------------------------------------------------------------------*/
276
gms_get_buffer_crc(const void * void_buffer,size_t length)277 glui32 Magnetic::gms_get_buffer_crc(const void *void_buffer, size_t length) {
278 const char *buf = (const char *) void_buffer;
279 uint32 crc;
280 size_t index;
281
282 /*
283 * Start with all ones in the crc, then update using table entries. Xor
284 * with all ones again, finally, before returning.
285 */
286 crc = 0xffffffff;
287 for (index = 0; index < length; index++)
288 crc = crc_table[(crc ^ buf[index]) & BYTE_MAX_VAL] ^ (crc >> BITS_PER_BYTE);
289 return crc ^ 0xffffffff;
290 }
291
292 /*---------------------------------------------------------------------*/
293 /* Glk port game identification data and identification functions */
294 /*---------------------------------------------------------------------*/
295
gms_gameid_read_uint32(int offset,Common::SeekableReadStream * stream)296 type32 Magnetic::gms_gameid_read_uint32(int offset, Common::SeekableReadStream *stream) {
297 if (!stream->seek(offset))
298 return 0;
299 return stream->readUint32BE();
300 }
301
gms_gameid_identify_game(const Common::String & text_file)302 void Magnetic::gms_gameid_identify_game(const Common::String &text_file) {
303 Common::File stream;
304
305 if (!stream.open(text_file))
306 error("Error opening game file");
307
308 type32 game_size, game_pc;
309 gms_game_tableref_t game;
310
311 /* Read the game's signature undo size and undo pc values. */
312 game_size = gms_gameid_read_uint32(0x22, &stream);
313 game_pc = gms_gameid_read_uint32(0x26, &stream);
314
315 /* Search for these values in the table, and set game name if found. */
316 game = gms_gameid_lookup_game(game_size, game_pc);
317 gms_gameid_game_name = game ? game->name : NULL;
318 }
319
320 /*---------------------------------------------------------------------*/
321 /* Glk port picture functions */
322 /*---------------------------------------------------------------------*/
323
gms_graphics_open()324 int Magnetic::gms_graphics_open() {
325 if (!gms_graphics_window) {
326 gms_graphics_window = glk_window_open(gms_main_window,
327 winmethod_Above
328 | winmethod_Proportional,
329 GMS_GRAPHICS_PROPORTION,
330 wintype_Graphics, 0);
331 }
332
333 return gms_graphics_window != NULL;
334 }
335
gms_graphics_close()336 void Magnetic::gms_graphics_close() {
337 if (gms_graphics_window) {
338 glk_window_close(gms_graphics_window, NULL);
339 gms_graphics_window = NULL;
340 }
341 }
342
gms_graphics_start()343 void Magnetic::gms_graphics_start() {
344 if (gms_graphics_enabled) {
345 /* If not running, start the updating "thread". */
346 if (!gms_graphics_active) {
347 glk_request_timer_events(GMS_GRAPHICS_TIMEOUT);
348 gms_graphics_active = true;
349 }
350 }
351 }
352
gms_graphics_stop()353 void Magnetic::gms_graphics_stop() {
354 /* If running, stop the updating "thread". */
355 if (gms_graphics_active) {
356 glk_request_timer_events(0);
357 gms_graphics_active = false;
358 }
359 }
360
gms_graphics_paint()361 void Magnetic::gms_graphics_paint() {
362 if (gms_graphics_enabled && gms_graphics_are_displayed()) {
363 /* Set the repaint flag, and start graphics. */
364 gms_graphics_repaint = true;
365 gms_graphics_start();
366 }
367 }
368
gms_graphics_restart()369 void Magnetic::gms_graphics_restart() {
370 if (gms_graphics_enabled && gms_graphics_are_displayed()) {
371 /*
372 * If the picture is animated, we'll need to be able to re-get the
373 * first animation frame so that the picture can be treated as if
374 * it is a new one. So here, we'll try to re-extract the current
375 * picture to do this. Calling ms_extract() is safe because we
376 * don't get here unless graphics are displayed, and graphics aren't
377 * displayed until there's a valid picture loaded, and ms_showpic
378 * only loads a picture after it's called ms_extract and set the
379 * picture id into gms_graphics_picture.
380 *
381 * The bitmap and other picture stuff can be ignored because it's
382 * the precise same stuff as we already have in picture details
383 * variables. If the ms_extract() fails, we'll carry on regardless,
384 * which may, or may not, result in the ideal picture display.
385 *
386 * One or two non-animated pictures return NULL from ms_extract()
387 * being re-called, so we'll restrict calls to animations only.
388 * And just to be safe, we'll also call only if we're already
389 * holding a bitmap (and we should be; how else could the graphics
390 * animation flag be set?...).
391 */
392 if (gms_graphics_animated && gms_graphics_bitmap) {
393 type8 animated;
394 type16 width, height, palette[GMS_PALETTE_SIZE];
395
396 /* Extract the bitmap into dummy variables. */
397 (void)ms_extract(gms_graphics_picture, &width, &height, palette, &animated);
398 }
399
400 /* Set the new picture flag, and start graphics. */
401 gms_graphics_new_picture = true;
402 gms_graphics_start();
403 }
404 }
405
gms_graphics_count_colors(type8 bitmap[],type16 width,type16 height,int * color_count,long color_usage[])406 void Magnetic::gms_graphics_count_colors(type8 bitmap[], type16 width, type16 height,
407 int *color_count, long color_usage[]) {
408 int x, y, count;
409 long usage[GMS_PALETTE_SIZE], index_row;
410 assert(bitmap);
411
412 /*
413 * Traverse the image, counting each pixel usage. For the y iterator,
414 * maintain an index row as an optimization to avoid multiplications in
415 * the loop.
416 */
417 count = 0;
418 memset(usage, 0, sizeof(usage));
419 for (y = 0, index_row = 0; y < height; y++, index_row += width) {
420 for (x = 0; x < width; x++) {
421 long index;
422
423 /* Get the pixel index, and update the count for this color. */
424 index = index_row + x;
425 usage[bitmap[index]]++;
426
427 /* If color usage is now 1, note new color encountered. */
428 if (usage[bitmap[index]] == 1)
429 count++;
430 }
431 }
432
433 if (color_count)
434 *color_count = count;
435
436 if (color_usage)
437 memcpy(color_usage, usage, sizeof(usage));
438 }
439
gms_graphics_game_to_rgb_color(type16 color,gms_gammaref_t gamma,gms_rgbref_t rgb_color)440 void Magnetic::gms_graphics_game_to_rgb_color(type16 color, gms_gammaref_t gamma,
441 gms_rgbref_t rgb_color) {
442 assert(gamma && rgb_color);
443
444 /*
445 * Convert Magnetic Scrolls color, through gamma, into RGB. This splits
446 * the color into components based on the 3-bits used in the game palette,
447 * and gamma-corrects and rescales each to the range 0-255, using the given
448 * correction.
449 */
450 rgb_color->red = gamma->table[(color & 0x700) >> 8];
451 rgb_color->green = gamma->table[(color & 0x070) >> 4];
452 rgb_color->blue = gamma->table[(color & 0x007)];
453 }
454
gms_graphics_split_color(glui32 color,gms_rgbref_t rgb_color)455 void Magnetic::gms_graphics_split_color(glui32 color, gms_rgbref_t rgb_color) {
456 assert(rgb_color);
457
458 rgb_color->red = (color >> 16) & 0xff;
459 rgb_color->green = (color >> 8) & 0xff;
460 rgb_color->blue = color & 0xff;
461 }
462
gms_graphics_combine_color(gms_rgbref_t rgb_color)463 glui32 Magnetic::gms_graphics_combine_color(gms_rgbref_t rgb_color) {
464 assert(rgb_color && _screen->format.bytesPerPixel == 2);
465 return _screen->format.RGBToColor(rgb_color->red, rgb_color->green, rgb_color->blue);
466 }
467
gms_graphics_color_luminance(gms_rgbref_t rgb_color)468 int Magnetic::gms_graphics_color_luminance(gms_rgbref_t rgb_color) {
469 /* Calculate the luminance and scale back by 1000 to 0-255 before return. */
470 long luminance = ((long) rgb_color->red * (long) GMS_LUMINANCE_WEIGHTS.red
471 + (long) rgb_color->green * (long) GMS_LUMINANCE_WEIGHTS.green
472 + (long) rgb_color->blue * (long) GMS_LUMINANCE_WEIGHTS.blue);
473
474 assert(luminance_weighting > 0);
475 return (int)(luminance / luminance_weighting);
476 }
477
gms_graphics_compare_luminance(const void * void_first,const void * void_second)478 int Magnetic::gms_graphics_compare_luminance(const void *void_first,
479 const void *void_second) {
480 long first = *(const long *)void_first;
481 long second = *(const long *)void_second;
482
483 return first > second ? 1 : second > first ? -1 : 0;
484 }
485
gms_graphics_contrast_variance(type16 palette[],long color_usage[],gms_gammaref_t gamma)486 long Magnetic::gms_graphics_contrast_variance(type16 palette[],
487 long color_usage[], gms_gammaref_t gamma) {
488 int index, count, has_black, mean;
489 long sum;
490 int contrast[GMS_PALETTE_SIZE];
491 int luminance[GMS_PALETTE_SIZE + 1]; /* Luminance for each color,
492 plus one extra for black */
493
494 /* Calculate the luminance energy of each palette color at this gamma. */
495 has_black = false;
496 for (index = 0, count = 0; index < GMS_PALETTE_SIZE; index++) {
497 if (color_usage[index] > 0) {
498 gms_rgb_t rgb_color;
499
500 /*
501 * Convert the 16-bit base picture color to RGB using the gamma
502 * currently under consideration. Calculate luminance for this
503 * color and store in the next available luminance array entry.
504 */
505 gms_graphics_game_to_rgb_color(palette[index], gamma, &rgb_color);
506 luminance[count++] = gms_graphics_color_luminance(&rgb_color);
507
508 /* Note if black is present in the palette. */
509 has_black |= luminance[count - 1] == 0;
510 }
511 }
512
513 /*
514 * For best results, we want to anchor contrast calculations to black, so
515 * if black is not represented in the palette, add it as an extra luminance.
516 */
517 if (!has_black)
518 luminance[count++] = 0;
519
520 /* Sort luminance values so that the darkest color is at index 0. */
521 qsort(luminance, count,
522 sizeof(*luminance), gms_graphics_compare_luminance);
523
524 /*
525 * Calculate the difference in luminance between adjacent luminances in
526 * the sorted array, as contrast, and at the same time sum contrasts to
527 * calculate the mean.
528 */
529 sum = 0;
530 for (index = 0; index < count - 1; index++) {
531 contrast[index] = luminance[index + 1] - luminance[index];
532 sum += contrast[index];
533 }
534 mean = sum / (count - 1);
535
536 /* Calculate and return the variance in contrasts. */
537 sum = 0;
538 for (index = 0; index < count - 1; index++)
539 sum += (contrast[index] - mean) * (contrast[index] - mean);
540
541 return sum / (count - 1);
542 }
543
gms_graphics_equal_contrast_gamma(type16 palette[],long color_usage[])544 gms_gammaref_t Magnetic::gms_graphics_equal_contrast_gamma(type16 palette[], long color_usage[]) {
545 gms_gammaref_t gamma, result;
546 long lowest_variance;
547 assert(palette && color_usage);
548
549 result = NULL;
550 lowest_variance = INT32_MAX_VAL;
551
552 /* Search the gamma table for the entry with the lowest contrast variance. */
553 for (gamma = GMS_GAMMA_TABLE; gamma->level; gamma++) {
554 long variance;
555
556 /* Find the color contrast variance of the palette at this gamma. */
557 variance = gms_graphics_contrast_variance(palette, color_usage, gamma);
558
559 /*
560 * Compare the variance to the lowest so far, and if it is lower, note
561 * the gamma entry that produced it as being the current best found.
562 */
563 if (variance < lowest_variance) {
564 result = gamma;
565 lowest_variance = variance;
566 }
567 }
568
569 assert(result);
570 return result;
571 }
572
gms_graphics_select_gamma(type8 bitmap[],type16 width,type16 height,type16 palette[])573 gms_gammaref_t Magnetic::gms_graphics_select_gamma(type8 bitmap[],
574 type16 width, type16 height, type16 palette[]) {
575 long color_usage[GMS_PALETTE_SIZE];
576 int color_count;
577 gms_gammaref_t contrast_gamma;
578 assert(linear_gamma);
579
580 /*
581 * Check to see if automated correction is turned off; if it is, return
582 * the linear gamma.
583 */
584 if (gms_gamma_mode == GAMMA_OFF)
585 return linear_gamma;
586
587 /*
588 * Get the color usage and count of total colors represented. For a
589 * degenerate picture with one color or less, return the linear gamma.
590 */
591 gms_graphics_count_colors(bitmap, width, height, &color_count, color_usage);
592 if (color_count <= 1)
593 return linear_gamma;
594
595 /*
596 * Now calculate a gamma setting to give the most equal contrast across the
597 * picture colors. We'll return either half this gamma, or all of it.
598 */
599 contrast_gamma = gms_graphics_equal_contrast_gamma(palette, color_usage);
600
601 /*
602 * For normal automated correction, return a gamma value half way between
603 * the linear gamma and the equal contrast gamma.
604 */
605 if (gms_gamma_mode == GAMMA_NORMAL)
606 return linear_gamma + (contrast_gamma - linear_gamma) / 2;
607
608 /* Correction must be high; return the equal contrast gamma. */
609 assert(gms_gamma_mode == GAMMA_HIGH);
610 return contrast_gamma;
611 }
612
gms_graphics_clear_and_border(winid_t glk_window,int x_offset,int y_offset,int pixel_size,type16 width,type16 height)613 void Magnetic::gms_graphics_clear_and_border(winid_t glk_window,
614 int x_offset, int y_offset, int pixel_size, type16 width, type16 height) {
615 uint background;
616 glui32 fade_color, shading_color;
617 gms_rgb_t rgb_background, rgb_border, rgb_fade;
618 int index;
619 assert(glk_window);
620
621 /*
622 * Try to detect the background color of the main window, by getting the
623 * background for Normal style (Glk offers no way to directly get a window's
624 * background color). If we can get it, we'll match the graphics window
625 * background to it. If we can't, we'll default the color to white.
626 */
627 if (!glk_style_measure(gms_main_window,
628 style_Normal, stylehint_BackColor, &background)) {
629 /*
630 * Unable to get the main window background, so assume, and default
631 * graphics to white.
632 */
633 background = GMS_GRAPHICS_DEFAULT_BACKGROUND;
634 }
635
636 /*
637 * Set the graphics window background to match the main window background,
638 * as best as we can tell, and clear the window.
639 */
640 glk_window_set_background_color(glk_window, background);
641 glk_window_clear(glk_window);
642
643 /*
644 * For very small pictures, just border them, but don't try and do any
645 * shading. Failing this check is probably highly unlikely.
646 */
647 if (width < 2 * GMS_GRAPHICS_SHADE_STEPS
648 || height < 2 * GMS_GRAPHICS_SHADE_STEPS) {
649 /* Paint a rectangle bigger than the picture by border pixels. */
650 glk_window_fill_rect(glk_window,
651 GMS_GRAPHICS_BORDER_COLOR,
652 x_offset - GMS_GRAPHICS_BORDER,
653 y_offset - GMS_GRAPHICS_BORDER,
654 width * pixel_size + GMS_GRAPHICS_BORDER * 2,
655 height * pixel_size + GMS_GRAPHICS_BORDER * 2);
656 return;
657 }
658
659 /*
660 * Paint a rectangle bigger than the picture by border pixels all round,
661 * and with additional shading pixels right and below. Some of these
662 * shading pixels are later overwritten by the fading loop below. The
663 * picture will sit over this rectangle.
664 */
665 glk_window_fill_rect(glk_window,
666 GMS_GRAPHICS_BORDER_COLOR,
667 x_offset - GMS_GRAPHICS_BORDER,
668 y_offset - GMS_GRAPHICS_BORDER,
669 width * pixel_size + GMS_GRAPHICS_BORDER * 2
670 + GMS_GRAPHICS_SHADING,
671 height * pixel_size + GMS_GRAPHICS_BORDER * 2
672 + GMS_GRAPHICS_SHADING);
673
674 /*
675 * Split the main window background color and the border color into
676 * components.
677 */
678 gms_graphics_split_color(background, &rgb_background);
679 gms_graphics_split_color(GMS_GRAPHICS_BORDER_COLOR, &rgb_border);
680
681 /*
682 * Generate the incremental color to use in fade steps. Here we're
683 * assuming that the border is always darker than the main window
684 * background (currently valid, as we're using black).
685 */
686 rgb_fade.red = (rgb_background.red - rgb_border.red)
687 / GMS_GRAPHICS_SHADE_STEPS;
688 rgb_fade.green = (rgb_background.green - rgb_border.green)
689 / GMS_GRAPHICS_SHADE_STEPS;
690 rgb_fade.blue = (rgb_background.blue - rgb_border.blue)
691 / GMS_GRAPHICS_SHADE_STEPS;
692
693 /* Combine RGB fade into a single incremental Glk color. */
694 fade_color = gms_graphics_combine_color(&rgb_fade);
695
696 /* Fade in edge, from background to border, shading in stages. */
697 shading_color = background;
698 for (index = 0; index < GMS_GRAPHICS_SHADE_STEPS; index++) {
699 /* Shade the two border areas with this color. */
700 glk_window_fill_rect(glk_window, shading_color,
701 x_offset + width * pixel_size
702 + GMS_GRAPHICS_BORDER,
703 y_offset + index - GMS_GRAPHICS_BORDER,
704 GMS_GRAPHICS_SHADING, 1);
705 glk_window_fill_rect(glk_window, shading_color,
706 x_offset + index - GMS_GRAPHICS_BORDER,
707 y_offset + height * pixel_size
708 + GMS_GRAPHICS_BORDER,
709 1, GMS_GRAPHICS_SHADING);
710
711 /* Update the shading color for the fade next iteration. */
712 shading_color -= fade_color;
713 }
714 }
715
gms_graphics_convert_palette(type16 ms_palette[],gms_gammaref_t gamma,glui32 glk_palette[])716 void Magnetic::gms_graphics_convert_palette(type16 ms_palette[], gms_gammaref_t gamma,
717 glui32 glk_palette[]) {
718 int index;
719 assert(ms_palette && gamma && glk_palette);
720
721 for (index = 0; index < GMS_PALETTE_SIZE; index++) {
722 gms_rgb_t rgb_color;
723
724 /*
725 * Convert the 16-bit base picture color through gamma to a 32-bit
726 * RGB color, and combine into a Glk color and store in the Glk palette.
727 */
728 gms_graphics_game_to_rgb_color(ms_palette[index], gamma, &rgb_color);
729 glk_palette[index] = gms_graphics_combine_color(&rgb_color);
730 }
731 }
732
gms_graphics_position_picture(winid_t glk_window,int pixel_size,type16 width,type16 height,int * x_offset,int * y_offset)733 void Magnetic::gms_graphics_position_picture(winid_t glk_window,
734 int pixel_size, type16 width, type16 height, int *x_offset, int *y_offset) {
735 uint window_width, window_height;
736 assert(glk_window && x_offset && y_offset);
737
738 /* Measure the current graphics window dimensions. */
739 glk_window_get_size(glk_window, &window_width, &window_height);
740
741 /*
742 * Calculate and return an x and y offset to use on point plotting, so that
743 * the image centers inside the graphical window.
744 */
745 *x_offset = ((int) window_width - width * pixel_size) / 2;
746 *y_offset = ((int) window_height - height * pixel_size) / 2;
747 }
748
gms_graphics_apply_animation_frame(type8 bitmap[],type16 frame_width,type16 frame_height,type8 mask[],int frame_x,int frame_y,type8 off_screen[],type16 width,type16 height)749 void Magnetic::gms_graphics_apply_animation_frame(type8 bitmap[],
750 type16 frame_width, type16 frame_height, type8 mask[], int frame_x, int frame_y,
751 type8 off_screen[], type16 width, type16 height) {
752 int mask_width, x, y;
753 type8 mask_hibit;
754 long frame_row, buffer_row, mask_row;
755 assert(bitmap && off_screen);
756
757 /*
758 * It turns out that the mask isn't quite as described in defs.h, and thanks
759 * to Torbjorn Andersson and his Gtk port of Magnetic for illuminating this.
760 * The mask is made up of lines of 16-bit words, so the mask width is always
761 * even. Here we'll calculate the real width of a mask, and also set a high
762 * bit for later on.
763 */
764 mask_width = (((frame_width - 1) / BITS_PER_BYTE) + 2) & (~1);
765 mask_hibit = 1 << (BITS_PER_BYTE - 1);
766
767 /*
768 * Initialize row index components; these are optimizations to avoid the
769 * need for multiplications in the frame iteration loop.
770 */
771 frame_row = 0;
772 buffer_row = frame_y * width;
773 mask_row = 0;
774
775 /*
776 * Iterate over each frame row, clipping where y lies outside the main
777 * picture area.
778 */
779 for (y = 0; y < frame_height; y++) {
780 /* Clip if y is outside the main picture area. */
781 if (y + frame_y < 0 || y + frame_y >= height) {
782 /* Update optimization variables as if not clipped. */
783 frame_row += frame_width;
784 buffer_row += width;
785 mask_row += mask_width;
786 continue;
787 }
788
789 /* Iterate over each frame column, clipping again. */
790 for (x = 0; x < frame_width; x++) {
791 long frame_index, buffer_index;
792
793 /* Clip if x is outside the main picture area. */
794 if (x + frame_x < 0 || x + frame_x >= width)
795 continue;
796
797 /*
798 * If there's a mask, check the bit associated with this x,y, and
799 * ignore any transparent pixels.
800 */
801 if (mask) {
802 type8 mask_byte;
803
804 /* Isolate the mask byte, and test the transparency bit. */
805 mask_byte = mask[mask_row + (x / BITS_PER_BYTE)];
806 if ((mask_byte & (mask_hibit >> (x % BITS_PER_BYTE))) != 0)
807 continue;
808 }
809
810 /*
811 * Calculate indexes for this pixel into the frame, and into the
812 * main off-screen buffer, and transfer the frame pixel into the
813 * off-screen buffer.
814 */
815 frame_index = frame_row + x;
816 buffer_index = buffer_row + x + frame_x;
817 off_screen[buffer_index] = bitmap[frame_index];
818 }
819
820 /* Update row index components on change of y. */
821 frame_row += frame_width;
822 buffer_row += width;
823 mask_row += mask_width;
824 }
825 }
826
gms_graphics_animate(type8 off_screen[],type16 width,type16 height)827 int Magnetic::gms_graphics_animate(type8 off_screen[], type16 width, type16 height) {
828 struct ms_position *positions;
829 type16 count;
830 type8 status;
831 int frame;
832 assert(off_screen);
833
834 /* Search for more animation frames, and return zero if none. */
835 status = ms_animate(&positions, &count);
836 if (status == 0)
837 return false;
838
839 /* Apply each animation frame to the off-screen buffer. */
840 for (frame = 0; frame < count; frame++) {
841 type8 *bitmap, *mask;
842 type16 frame_width, frame_height;
843
844 /*
845 * Get the bitmap and other details for this frame. If we can't get
846 * this animation frame, skip it and see if any others are available.
847 */
848 bitmap = ms_get_anim_frame(positions[frame].number,
849 &frame_width, &frame_height, &mask);
850 if (bitmap) {
851 gms_graphics_apply_animation_frame(bitmap,
852 frame_width, frame_height, mask,
853 positions[frame].x,
854 positions[frame].y,
855 off_screen, width, height);
856 }
857 }
858
859 /* Return true since more animation frames remain. */
860 return true;
861 }
862
863 #ifndef GARGLK
gms_graphics_is_vertex(type8 off_screen[],type16 width,type16 height,int x,int y)864 int Magnetic::gms_graphics_is_vertex(type8 off_screen[], type16 width, type16 height,
865 int x, int y) {
866 type8 pixel;
867 int above, below, left, right;
868 long index_row;
869 assert(off_screen);
870
871 /* Use an index row to cut down on multiplications. */
872 index_row = y * width;
873
874 /* Find the color of the reference pixel. */
875 pixel = off_screen[index_row + x];
876 assert(pixel < GMS_PALETTE_SIZE);
877
878 /*
879 * Detect differences between the reference pixel and its upper, lower, left
880 * and right neighbors. Mark as different if the neighbor doesn't exist,
881 * that is, at the edge of the picture.
882 */
883 above = (y == 0 || off_screen[index_row - width + x] != pixel);
884 below = (y == height - 1 || off_screen[index_row + width + x] != pixel);
885 left = (x == 0 || off_screen[index_row + x - 1] != pixel);
886 right = (x == width - 1 || off_screen[index_row + x + 1] != pixel);
887
888 /*
889 * Return true if this pixel lies at the vertex of a rectangular, fillable,
890 * area. That is, if two adjacent neighbors aren't the same color (or if
891 * absent -- at the edge of the picture).
892 */
893 return ((above || below) && (left || right));
894 }
895
gms_graphics_compare_layering_inverted(const void * void_first,const void * void_second)896 int Magnetic::gms_graphics_compare_layering_inverted(const void *void_first,
897 const void *void_second) {
898 gms_layering_t *first = (gms_layering_t *) void_first;
899 gms_layering_t *second = (gms_layering_t *) void_second;
900
901 /*
902 * Order by complexity first, then by usage, putting largest first. Some
903 * colors may have no vertices at all when doing animation frames, but
904 * rendering optimization relies on the first layer that contains no areas
905 * to fill halting the rendering loop. So it's important here that we order
906 * indexes so that colors that render complex shapes come first, non-empty,
907 * but simpler shaped colors next, and finally all genuinely empty layers.
908 */
909 return second->complexity > first->complexity ? 1 :
910 first->complexity > second->complexity ? -1 :
911 second->usage > first->usage ? 1 :
912 first->usage > second->usage ? -1 : 0;
913 }
914
gms_graphics_assign_layers(type8 off_screen[],type8 on_screen[],type16 width,type16 height,int layers[],long layer_usage[])915 void Magnetic::gms_graphics_assign_layers(type8 off_screen[], type8 on_screen[],
916 type16 width, type16 height,
917 int layers[], long layer_usage[]) {
918 int index, x, y;
919 long index_row;
920 gms_layering_t layering[GMS_PALETTE_SIZE];
921 assert(off_screen && on_screen && layers && layer_usage);
922
923 /* Clear initial complexity and usage counts, and set initial colors. */
924 for (index = 0; index < GMS_PALETTE_SIZE; index++) {
925 layering[index].complexity = 0;
926 layering[index].usage = 0;
927 layering[index].color = index;
928 }
929
930 /*
931 * Traverse the image, counting vertices and pixel usage where the pixels
932 * differ between the off-screen and on-screen buffers. Optimize by
933 * maintaining an index row to avoid multiplications.
934 */
935 for (y = 0, index_row = 0; y < height; y++, index_row += width) {
936 for (x = 0; x < width; x++) {
937 long idx;
938
939 /*
940 * Get the index for this pixel, and update complexity and usage
941 * if off-screen and on-screen pixels differ.
942 */
943 idx = index_row + x;
944 if (on_screen[idx] != off_screen[idx]) {
945 if (gms_graphics_is_vertex(off_screen, width, height, x, y))
946 layering[off_screen[idx]].complexity++;
947
948 layering[off_screen[idx]].usage++;
949 }
950 }
951 }
952
953 /*
954 * Sort counts to form color indexes. The primary sort is on the shape
955 * complexity, and within this, on color usage.
956 */
957 qsort(layering, GMS_PALETTE_SIZE,
958 sizeof(*layering), gms_graphics_compare_layering_inverted);
959
960 /*
961 * Assign a layer to each palette color, and also return the layer usage
962 * for each layer.
963 */
964 for (index = 0; index < GMS_PALETTE_SIZE; index++) {
965 layers[layering[index].color] = index;
966 layer_usage[index] = layering[index].usage;
967 }
968 }
969
gms_graphics_paint_region(winid_t glk_window,glui32 palette[],int layers[],type8 off_screen[],type8 on_screen[],int x,int y,int x_offset,int y_offset,int pixel_size,type16 width,type16 height)970 void Magnetic::gms_graphics_paint_region(winid_t glk_window, glui32 palette[], int layers[],
971 type8 off_screen[], type8 on_screen[],
972 int x, int y, int x_offset, int y_offset,
973 int pixel_size, type16 width, type16 height) {
974 type8 pixel;
975 int layer, x_min, x_max, y_min, y_max, x_index, y_index;
976 long index_row;
977 assert(glk_window && palette && layers && off_screen && on_screen);
978
979 /* Find the color and layer for the initial pixel. */
980 pixel = off_screen[y * width + x];
981 layer = layers[pixel];
982 assert(pixel < GMS_PALETTE_SIZE);
983
984 /*
985 * Start by finding the extent to which we can pull the x coordinate and
986 * still find either invalidated pixels, or pixels in this layer.
987 *
988 * Use an index row to remove multiplications from the loops.
989 */
990 index_row = y * width;
991 for (x_min = x; x_min - 1 >= 0; x_min--) {
992 long index = index_row + x_min - 1;
993
994 if (on_screen[index] == off_screen[index]
995 && layers[off_screen[index]] != layer)
996 break;
997 }
998 for (x_max = x; x_max + 1 < width; x_max++) {
999 long index = index_row + x_max + 1;
1000
1001 if (on_screen[index] == off_screen[index]
1002 && layers[off_screen[index]] != layer)
1003 break;
1004 }
1005
1006 /*
1007 * Now try to stretch the height of the region, by extending the y
1008 * coordinate as much as possible too. Again, we're looking for pixels
1009 * that are invalidated or ones in the same layer. We need to check
1010 * across the full width of the current region.
1011 *
1012 * As above, an index row removes multiplications from the loops.
1013 */
1014 for (y_min = y, index_row = (y - 1) * width;
1015 y_min - 1 >= 0; y_min--, index_row -= width) {
1016 for (x_index = x_min; x_index <= x_max; x_index++) {
1017 long index = index_row + x_index;
1018
1019 if (on_screen[index] == off_screen[index]
1020 && layers[off_screen[index]] != layer)
1021 goto break_y_min;
1022 }
1023 }
1024 break_y_min:
1025
1026 for (y_max = y, index_row = (y + 1) * width;
1027 y_max + 1 < height; y_max++, index_row += width) {
1028 for (x_index = x_min; x_index <= x_max; x_index++) {
1029 long index = index_row + x_index;
1030
1031 if (on_screen[index] == off_screen[index]
1032 && layers[off_screen[index]] != layer)
1033 goto break_y_max;
1034 }
1035 }
1036 break_y_max:
1037
1038 /* Fill the region using Glk's rectangle fill. */
1039 glk_window_fill_rect(glk_window, palette[pixel],
1040 x_min * pixel_size + x_offset,
1041 y_min * pixel_size + y_offset,
1042 (x_max - x_min + 1) * pixel_size,
1043 (y_max - y_min + 1) * pixel_size);
1044
1045 /*
1046 * Validate each pixel in the reference layer that was rendered by the
1047 * rectangle fill. We don't validate pixels that are not in this layer
1048 * (and are by definition in higher layers, as we've validated all lower
1049 * layers), since although we colored them, we did it for optimization
1050 * reasons, and they're not yet colored correctly.
1051 *
1052 * Maintain an index row as an optimization to avoid multiplication.
1053 */
1054 index_row = y_min * width;
1055 for (y_index = y_min; y_index <= y_max; y_index++) {
1056 for (x_index = x_min; x_index <= x_max; x_index++) {
1057 long index;
1058
1059 /*
1060 * Get the index for x_index,y_index. If the layers match, update
1061 * the on-screen buffer.
1062 */
1063 index = index_row + x_index;
1064 if (layers[off_screen[index]] == layer) {
1065 assert(off_screen[index] == pixel);
1066 on_screen[index] = off_screen[index];
1067 }
1068 }
1069
1070 /* Update row index component on change of y. */
1071 index_row += width;
1072 }
1073 }
1074 #endif
1075
gms_graphics_paint_everything(winid_t glk_window,glui32 palette[],type8 off_screen[],int x_offset,int y_offset,type16 width,type16 height)1076 void Magnetic::gms_graphics_paint_everything(winid_t glk_window,
1077 glui32 palette[], type8 off_screen[], int x_offset, int y_offset,
1078 type16 width, type16 height) {
1079 type16 x, y;
1080 glui32 pixel;
1081
1082 Graphics::ManagedSurface s(width, height, _screen->format);
1083
1084 for (y = 0; y < height; y++) {
1085 uint16 *lineP = (uint16 *)s.getBasePtr(0, y);
1086
1087 for (x = 0; x < width; ++x, ++lineP) {
1088 pixel = palette[off_screen[y * width + x]];
1089 *lineP = pixel;
1090 }
1091 }
1092
1093 glk_image_draw_scaled(glk_window, s, (uint)-1, x_offset, y_offset,
1094 width * GMS_GRAPHICS_PIXEL, height * GMS_GRAPHICS_PIXEL);
1095 }
1096
gms_graphics_timeout()1097 void Magnetic::gms_graphics_timeout() {
1098 static glui32 palette[GMS_PALETTE_SIZE]; /* Precomputed Glk palette */
1099 #ifndef GARGLK
1100 static int layers[GMS_PALETTE_SIZE]; /* Assigned image layers */
1101 static long layer_usage[GMS_PALETTE_SIZE]; /* Image layer occupancies */
1102 #endif
1103
1104 static int deferred_repaint = false; /* Local delayed repaint flag */
1105 static int ignore_counter; /* Count of calls ignored */
1106
1107 static int x_offset, y_offset; /* Point plot offsets */
1108 static int yield_counter; /* Yields in rendering */
1109 #ifndef GARGLK
1110 static int saved_layer; /* Saved current layer */
1111 static int saved_x, saved_y; /* Saved x,y coord */
1112 static int total_regions; /* Debug statistic */
1113 #endif
1114
1115 type8 *on_screen; /* On-screen image buffer */
1116 type8 *off_screen; /* Off-screen image buffer */
1117 long picture_size; /* Picture size in pixels */
1118 // int layer; /* Image layer iterator */
1119 // int x, y; /* Image iterators */
1120 // int regions; /* Count of regions painted */
1121
1122 /* Ignore the call if the current graphics state is inactive. */
1123 if (!gms_graphics_active)
1124 return;
1125 assert(gms_graphics_window);
1126
1127 /*
1128 * On detecting a repaint request, note the flag in a local static variable,
1129 * then set up a graphics delay to wait until, hopefully, the resize, if
1130 * that's what caused it, is complete, and return. This makes resizing the
1131 * window a lot smoother, since it prevents unnecessary region paints where
1132 * we are receiving consecutive Glk arrange or redraw events.
1133 */
1134 if (gms_graphics_repaint) {
1135 deferred_repaint = true;
1136 gms_graphics_repaint = false;
1137 ignore_counter = GMS_GRAPHICS_REPAINT_WAIT - 1;
1138 return;
1139 }
1140
1141 /*
1142 * If asked to ignore a given number of calls, decrement the ignore counter
1143 * and return having done nothing more. This lets us delay graphics
1144 * operations by a number of timeouts, providing animation timing and
1145 * partial protection from resize event "storms".
1146 *
1147 * Note -- to wait for N timeouts, set the count of timeouts to be ignored
1148 * to N-1.
1149 */
1150 assert(ignore_counter >= 0);
1151 if (ignore_counter > 0) {
1152 ignore_counter--;
1153 return;
1154 }
1155
1156 /* Calculate the picture size, and synchronize screen buffer pointers. */
1157 picture_size = gms_graphics_width * gms_graphics_height;
1158 off_screen = gms_graphics_off_screen;
1159 on_screen = gms_graphics_on_screen;
1160
1161 /*
1162 * If we received a new picture, set up the local static variables for that
1163 * picture -- decide on gamma correction, convert the color palette, and
1164 * initialize the off_screen buffer to be the base picture.
1165 */
1166 if (gms_graphics_new_picture) {
1167 /* Initialize the off_screen buffer to be a copy of the base picture. */
1168 free(off_screen);
1169 off_screen = (type8 *)gms_malloc(picture_size * sizeof(*off_screen));
1170 memcpy(off_screen, gms_graphics_bitmap,
1171 picture_size * sizeof(*off_screen));
1172
1173 /* Note the buffer for freeing on cleanup. */
1174 gms_graphics_off_screen = off_screen;
1175
1176 /*
1177 * If the picture is animated, apply the first animation frames now.
1178 * This is important, since they form an intrinsic part of the first
1179 * displayed image (in type2 animation cases, perhaps _all_ of the
1180 * first displayed image).
1181 */
1182 if (gms_graphics_animated) {
1183 gms_graphics_animate(off_screen,
1184 gms_graphics_width, gms_graphics_height);
1185 }
1186
1187 /*
1188 * Select a suitable gamma for the picture, taking care to use the
1189 * off-screen buffer.
1190 */
1191 gms_graphics_current_gamma =
1192 gms_graphics_select_gamma(off_screen,
1193 gms_graphics_width,
1194 gms_graphics_height,
1195 gms_graphics_palette);
1196
1197 /*
1198 * Pre-convert all the picture palette colors into their corresponding
1199 * Glk colors.
1200 */
1201 gms_graphics_convert_palette(gms_graphics_palette,
1202 gms_graphics_current_gamma, palette);
1203
1204 /* Save the color count for possible queries later. */
1205 gms_graphics_count_colors(off_screen,
1206 gms_graphics_width, gms_graphics_height,
1207 &gms_graphics_color_count, NULL);
1208 }
1209
1210 /*
1211 * For a new picture, or a repaint of a prior one, calculate new values for
1212 * the x and y offsets used to draw image points, and set the on-screen
1213 * buffer to an unused pixel value, in effect invalidating all on-screen
1214 * data. Also, reset the saved image scan coordinates so that we scan for
1215 * unpainted pixels from top left starting at layer zero, and clear the
1216 * graphics window.
1217 */
1218 if (gms_graphics_new_picture || deferred_repaint) {
1219 /*
1220 * Calculate the x and y offset to center the picture in the graphics
1221 * window.
1222 */
1223 gms_graphics_position_picture(gms_graphics_window,
1224 GMS_GRAPHICS_PIXEL,
1225 gms_graphics_width, gms_graphics_height,
1226 &x_offset, &y_offset);
1227
1228 /*
1229 * Reset all on-screen pixels to an unused value, guaranteed not to
1230 * match any in a real picture. This forces all pixels to be repainted
1231 * on a buffer/on-screen comparison.
1232 */
1233 free(on_screen);
1234 on_screen = (type8 *)gms_malloc(picture_size * sizeof(*on_screen));
1235 memset(on_screen, GMS_GRAPHICS_UNUSED_PIXEL,
1236 picture_size * sizeof(*on_screen));
1237
1238 /* Note the buffer for freeing on cleanup. */
1239 gms_graphics_on_screen = on_screen;
1240
1241 /*
1242 * Assign new layers to the current image. This sorts colors by usage
1243 * and puts the most used colors in the lower layers. It also hands us
1244 * a count of pixels in each layer, useful for knowing when to stop
1245 * scanning for layers in the rendering loop.
1246 */
1247 #ifndef GARGLK
1248 gms_graphics_assign_layers(off_screen, on_screen,
1249 gms_graphics_width, gms_graphics_height,
1250 layers, layer_usage);
1251
1252 saved_layer = 0;
1253 saved_x = 0;
1254 saved_y = 0;
1255 total_regions = 0;
1256 #endif
1257
1258 /* Clear the graphics window. */
1259 gms_graphics_clear_and_border(gms_graphics_window,
1260 x_offset, y_offset,
1261 GMS_GRAPHICS_PIXEL,
1262 gms_graphics_width, gms_graphics_height);
1263
1264 /* Start a fresh picture rendering pass. */
1265 yield_counter = 0;
1266
1267 /* Clear the new picture and deferred repaint flags. */
1268 gms_graphics_new_picture = false;
1269 deferred_repaint = false;
1270 }
1271
1272 #ifndef GARGLK
1273 /*
1274 * Make a portion of an image pass, from lower to higher image layers,
1275 * scanning for invalidated pixels that are in the current image layer we
1276 * are painting. Each invalidated pixel gives rise to a region paint,
1277 * which equates to one Glk rectangle fill.
1278 *
1279 * When the limit on regions is reached, save the current image pass layer
1280 * and coordinates, and yield control to the main game playing code by
1281 * returning. On the next call, pick up where we left off.
1282 *
1283 * As an optimization, we can leave the loop on the first empty layer we
1284 * encounter. Since layers are ordered by complexity and color usage, all
1285 * layers higher than the first unused one will also be empty, so we don't
1286 * need to scan them.
1287 */
1288 regions = 0;
1289 for (layer = saved_layer;
1290 layer < GMS_PALETTE_SIZE && layer_usage[layer] > 0; layer++) {
1291 long index_row;
1292
1293 /*
1294 * As an optimization to avoid multiplications in the loop, maintain a
1295 * separate index row.
1296 */
1297 index_row = saved_y * gms_graphics_width;
1298 for (y = saved_y; y < gms_graphics_height; y++) {
1299 for (x = saved_x; x < gms_graphics_width; x++) {
1300 long index;
1301
1302 /* Get the index for this pixel. */
1303 index = index_row + x;
1304 assert(index < picture_size * sizeof(*off_screen));
1305
1306 /*
1307 * Ignore pixels not in the current layer, and pixels not
1308 * currently invalid (that is, ones whose on-screen represen-
1309 * tation matches the off-screen buffer).
1310 */
1311 if (layers[off_screen[index]] == layer
1312 && on_screen[index] != off_screen[index]) {
1313 /*
1314 * Rather than painting just one pixel, here we try to
1315 * paint the maximal region we can for the layer of the
1316 * given pixel.
1317 */
1318 gms_graphics_paint_region(gms_graphics_window,
1319 palette, layers,
1320 off_screen, on_screen,
1321 x, y, x_offset, y_offset,
1322 GMS_GRAPHICS_PIXEL,
1323 gms_graphics_width,
1324 gms_graphics_height);
1325
1326 /*
1327 * Increment count of regions handled, and yield, by
1328 * returning, if the limit on paint regions is reached.
1329 * Before returning, save the current layer and scan
1330 * coordinates, so we can pick up here on the next call.
1331 */
1332 regions++;
1333 if (regions >= GMS_REPAINT_LIMIT) {
1334 yield_counter++;
1335 saved_layer = layer;
1336 saved_x = x;
1337 saved_y = y;
1338 total_regions += regions;
1339 return;
1340 }
1341 }
1342 }
1343
1344 /* Reset the saved x coordinate on y increment. */
1345 saved_x = 0;
1346
1347 /* Update the index row on change of y. */
1348 index_row += gms_graphics_width;
1349 }
1350
1351 /* Reset the saved y coordinate on layer change. */
1352 saved_y = 0;
1353 }
1354
1355 /*
1356 * If we reach this point, then we didn't get to the limit on regions
1357 * painted on this pass. In that case, we've finished rendering the
1358 * image.
1359 */
1360 assert(regions < GMS_REPAINT_LIMIT);
1361 total_regions += regions;
1362
1363 #else
1364 gms_graphics_paint_everything
1365 (gms_graphics_window,
1366 palette, off_screen,
1367 x_offset, y_offset,
1368 gms_graphics_width,
1369 gms_graphics_height);
1370 #endif
1371
1372 /*
1373 * If animated, and if animations are enabled, handle further animation
1374 * frames, if any.
1375 */
1376 if (gms_animation_enabled && gms_graphics_animated) {
1377 int more_animation;
1378
1379 /*
1380 * Reset the off-screen buffer to a copy of the base picture. This is
1381 * the correct state for applying animation frames.
1382 */
1383 memcpy(off_screen, gms_graphics_bitmap,
1384 picture_size * sizeof(*off_screen));
1385
1386 /*
1387 * Apply any further animations. If none, then stop the graphics
1388 * "thread" and return. There's no more to be done until something
1389 * restarts us.
1390 */
1391 more_animation = gms_graphics_animate(off_screen,
1392 gms_graphics_width,
1393 gms_graphics_height);
1394 if (!more_animation) {
1395 /*
1396 * There's one extra wrinkle here. The base picture we've just put
1397 * into the off-screen buffer isn't really complete (and for type2
1398 * animations, might be pure garbage), so if we happen to get a
1399 * repaint after an animation has ended, the off-screen data we'll
1400 * be painting could well look wrong.
1401 *
1402 * So... here we want to set the off-screen buffer to contain the
1403 * final animation frame. Fortunately, we still have it in the
1404 * on-screen buffer.
1405 */
1406 memcpy(off_screen, on_screen, picture_size * sizeof(*off_screen));
1407 gms_graphics_stop();
1408 return;
1409 }
1410
1411 /*
1412 * Re-assign layers based on animation changes to the off-screen
1413 * buffer.
1414 */
1415 #ifndef GARGLK
1416 gms_graphics_assign_layers(off_screen, on_screen,
1417 gms_graphics_width, gms_graphics_height,
1418 layers, layer_usage);
1419 #endif
1420
1421 /*
1422 * Set up an animation wait, adjusted here by the number of times we
1423 * had to yield while rendering, as we're now that late with animations,
1424 * and capped at zero, as we can't do anything to compensate for being
1425 * too late. In practice, we're running too close to the edge to have
1426 * much of an effect here, but nevertheless...
1427 */
1428 ignore_counter = GMS_GRAPHICS_ANIMATION_WAIT - 1;
1429 if (yield_counter > ignore_counter)
1430 ignore_counter = 0;
1431 else
1432 ignore_counter -= yield_counter;
1433
1434 /* Start a fresh picture rendering pass. */
1435 yield_counter = 0;
1436 #ifndef GARGLK
1437 saved_layer = 0;
1438 saved_x = 0;
1439 saved_y = 0;
1440 total_regions = 0;
1441 #endif
1442 } else {
1443 /*
1444 * Not an animated picture, so just stop graphics, as again, there's
1445 * no more to be done until something restarts us.
1446 */
1447 gms_graphics_stop();
1448 }
1449 }
1450
ms_showpic(type32 picture,type8 mode)1451 void Magnetic::ms_showpic(type32 picture, type8 mode) {
1452 type8 *bitmap, animated;
1453 type16 width, height, palette[GMS_PALETTE_SIZE];
1454 long picture_bytes;
1455 glui32 crc;
1456
1457 /* See if the mode indicates no graphics. */
1458 if (mode == 0) {
1459 /* Note that the interpreter turned graphics off. */
1460 gms_graphics_interpreter = false;
1461
1462 /*
1463 * If we are currently displaying the graphics window, stop any update
1464 * "thread" and turn off graphics.
1465 */
1466 if (gms_graphics_enabled && gms_graphics_are_displayed()) {
1467 gms_graphics_stop();
1468 gms_graphics_close();
1469 }
1470
1471 /* Nothing more to do now graphics are off. */
1472 return;
1473 }
1474
1475 /* Note that the interpreter turned graphics on. */
1476 gms_graphics_interpreter = true;
1477
1478 /*
1479 * Obtain the image details for the requested picture. The call returns
1480 * NULL if there's a problem with the picture.
1481 */
1482 bitmap = ms_extract(picture, &width, &height, palette, &animated);
1483 if (!bitmap)
1484 return;
1485
1486 /* Note the last thing passed to ms_extract, in case of graphics restarts. */
1487 gms_graphics_picture = picture;
1488
1489 /* Calculate the picture size, and the CRC for the bitmap data. */
1490 picture_bytes = width * height * sizeof(*bitmap);
1491 crc = gms_get_buffer_crc(bitmap, picture_bytes);
1492
1493 /*
1494 * If there is no change of picture, we might be able to largely ignore the
1495 * call. Check for a change, and if we don't see one, and if graphics are
1496 * enabled and being displayed, we can safely ignore the call.
1497 */
1498 if (width == gms_graphics_width
1499 && height == gms_graphics_height
1500 && crc == pic_current_crc
1501 && gms_graphics_enabled && gms_graphics_are_displayed())
1502 return;
1503
1504 /*
1505 * We know now that this is either a genuine change of picture, or graphics
1506 * were off and have been turned on. So, record picture details, ensure
1507 * graphics is on, set the flags, and start the background graphics update.
1508 */
1509
1510 /*
1511 * Save the picture details for the update code. Here we take a complete
1512 * local copy of the bitmap, since the interpreter core may reuse part of
1513 * its memory for animations.
1514 */
1515 free(gms_graphics_bitmap);
1516 gms_graphics_bitmap = (type8 *)gms_malloc(picture_bytes);
1517 memcpy(gms_graphics_bitmap, bitmap, picture_bytes);
1518 gms_graphics_width = width;
1519 gms_graphics_height = height;
1520 memcpy(gms_graphics_palette, palette, sizeof(palette));
1521 gms_graphics_animated = animated;
1522
1523 /* Retain the new picture CRC. */
1524 pic_current_crc = crc;
1525
1526 /*
1527 * If graphics are enabled, ensure the window is displayed, set the
1528 * appropriate flags, and start graphics update. If they're not enabled,
1529 * the picture details will simply stick around in module variables until
1530 * they are required.
1531 */
1532 if (gms_graphics_enabled) {
1533 /*
1534 * Ensure graphics on, then set the new picture flag and start the
1535 * updating "thread".
1536 */
1537 if (gms_graphics_open()) {
1538 gms_graphics_new_picture = true;
1539 gms_graphics_start();
1540 }
1541 }
1542 }
1543
gms_graphics_get_picture_details(int * width,int * height,int * is_animated)1544 int Magnetic::gms_graphics_get_picture_details(int *width, int *height, int *is_animated) {
1545 if (gms_graphics_picture_is_available()) {
1546 if (width)
1547 *width = gms_graphics_width;
1548 if (height)
1549 *height = gms_graphics_height;
1550 if (is_animated)
1551 *is_animated = gms_graphics_animated;
1552
1553 return true;
1554 }
1555
1556 return false;
1557 }
1558
gms_graphics_get_rendering_details(const char ** gamma,int * color_count,int * is_active)1559 int Magnetic::gms_graphics_get_rendering_details(const char **gamma,
1560 int *color_count, int *is_active) {
1561 if (gms_graphics_enabled && gms_graphics_are_displayed()) {
1562 /*
1563 * Return the string representing the gamma correction. If racing
1564 * with timeouts, we might return the gamma for the last picture.
1565 */
1566 if (gamma) {
1567 assert(gms_graphics_current_gamma);
1568 *gamma = gms_graphics_current_gamma->level;
1569 }
1570
1571 /*
1572 * Return the color count noted by timeouts on the first timeout
1573 * following a new picture. Again, we might return the one for
1574 * the prior picture.
1575 */
1576 if (color_count)
1577 *color_count = gms_graphics_color_count;
1578
1579 /* Return graphics active flag. */
1580 if (is_active)
1581 *is_active = gms_graphics_active;
1582
1583 return true;
1584 }
1585
1586 return false;
1587 }
1588
gms_graphics_interpreter_enabled()1589 int Magnetic::gms_graphics_interpreter_enabled() {
1590 return gms_graphics_interpreter;
1591 }
1592
gms_graphics_cleanup()1593 void Magnetic::gms_graphics_cleanup() {
1594 free(gms_graphics_bitmap);
1595 gms_graphics_bitmap = NULL;
1596 free(gms_graphics_off_screen);
1597 gms_graphics_off_screen = NULL;
1598 free(gms_graphics_on_screen);
1599 gms_graphics_on_screen = NULL;
1600
1601 gms_graphics_animated = false;
1602 gms_graphics_picture = 0;
1603 }
1604
1605 /*---------------------------------------------------------------------*/
1606 /* Glk port status line functions */
1607 /*---------------------------------------------------------------------*/
1608
ms_statuschar(type8 c)1609 void Magnetic::ms_statuschar(type8 c) {
1610 static char buffer_[GMS_STATBUFFER_LENGTH];
1611 static int length = 0;
1612
1613 /*
1614 * If the status character is newline, transfer locally buffered data to
1615 * the common buffer, empty the local buffer; otherwise, if space permits,
1616 * buffer the character.
1617 */
1618 if (c == '\n') {
1619 memcpy(gms_status_buffer, buffer_, length);
1620 gms_status_length = length;
1621
1622 length = 0;
1623 } else {
1624 if (length < (int)sizeof(buffer_))
1625 buffer_[length++] = c;
1626 }
1627 }
1628
gms_status_update()1629 void Magnetic::gms_status_update() {
1630 uint width, height;
1631 int index;
1632 assert(gms_status_window);
1633
1634 glk_window_get_size(gms_status_window, &width, &height);
1635 if (height > 0) {
1636 glk_window_clear(gms_status_window);
1637 glk_window_move_cursor(gms_status_window, 0, 0);
1638 glk_set_window(gms_status_window);
1639
1640 glk_set_style(style_User1);
1641 for (index = 0; index < (int)width; index++)
1642 glk_put_char(' ');
1643 glk_window_move_cursor(gms_status_window, 1, 0);
1644
1645 if (gms_status_length > 0) {
1646 /*
1647 * Output each character from the status line buffer. If the
1648 * character is Tab, position the cursor to eleven characters shy
1649 * of the status window right.
1650 */
1651 for (index = 0; index < gms_status_length; index++) {
1652 if (gms_status_buffer[index] == '\t')
1653 glk_window_move_cursor(gms_status_window, width - 11, 0);
1654 else
1655 glk_put_char(gms_status_buffer[index]);
1656 }
1657 } else {
1658 const char *game_name;
1659
1660 /*
1661 * We have no status line to display, so print the game's name, or
1662 * a standard message if unable to identify the game. Having no
1663 * status line is common with Magnetic Windows games, which don't,
1664 * in general, seem to use one.
1665 */
1666 game_name = gms_gameid_get_game_name();
1667 glk_put_string(game_name ? game_name : "ScummVM Magnetic version 2.3");
1668 }
1669
1670 glk_set_window(gms_main_window);
1671 }
1672 }
1673
gms_status_print()1674 void Magnetic::gms_status_print() {
1675 static char buffer_[GMS_STATBUFFER_LENGTH];
1676 static int length = 0;
1677
1678 int index, column;
1679
1680 /*
1681 * Do nothing if there is no status line to print, or if the status
1682 * line hasn't changed since last printed.
1683 */
1684 if (gms_status_length == 0
1685 || (gms_status_length == length
1686 && strncmp(buffer_, gms_status_buffer, length)) == 0)
1687 return;
1688
1689 /* Set fixed width font to try to preserve status line formatting. */
1690 glk_set_style(style_Preformatted);
1691
1692 /* Bracket, and output the status line buffer_. */
1693 glk_put_string("[ ");
1694 column = 1;
1695 for (index = 0; index < gms_status_length; index++) {
1696 /*
1697 * If the character is Tab, position the cursor to eleven characters
1698 * shy of the right edge. In the absence of the real window dimensions,
1699 * we'll select 74 characters, which gives us a 78 character status
1700 * line; pretty standard.
1701 */
1702 if (gms_status_buffer[index] == '\t') {
1703 while (column <= GMS_DEFAULT_STATUS_WIDTH - 11) {
1704 glk_put_char(' ');
1705 column++;
1706 }
1707 } else {
1708 glk_put_char(gms_status_buffer[index]);
1709 column++;
1710 }
1711 }
1712
1713 while (column <= GMS_DEFAULT_STATUS_WIDTH) {
1714 glk_put_char(' ');
1715 column++;
1716 }
1717 glk_put_string(" ]\n");
1718
1719 /* Save the details of the printed status buffer_. */
1720 memcpy(buffer_, gms_status_buffer, gms_status_length);
1721 length = gms_status_length;
1722 }
1723
gms_status_notify()1724 void Magnetic::gms_status_notify() {
1725 if (gms_status_window)
1726 gms_status_update();
1727 else
1728 gms_status_print();
1729 }
1730
gms_status_redraw()1731 void Magnetic::gms_status_redraw() {
1732 if (gms_status_window) {
1733 winid_t parent;
1734
1735 /*
1736 * Rearrange the status window, without changing its actual arrangement
1737 * in any way. This is a hack to work round incorrect window repainting
1738 * in Xglk; it forces a complete repaint of affected windows on Glk
1739 * window resize and arrange events, and works in part because Xglk
1740 * doesn't check for actual arrangement changes in any way before
1741 * invalidating its windows. The hack should be harmless to Glk
1742 * libraries other than Xglk, moreover, we're careful to activate it
1743 * only on resize and arrange events.
1744 */
1745 parent = glk_window_get_parent(gms_status_window);
1746 glk_window_set_arrangement(parent,
1747 winmethod_Above | winmethod_Fixed, 1, NULL);
1748
1749 gms_status_update();
1750 }
1751 }
1752
1753 /*---------------------------------------------------------------------*/
1754 /* Glk port output functions */
1755 /*---------------------------------------------------------------------*/
1756
gms_output_register_help_request()1757 void Magnetic::gms_output_register_help_request() {
1758 gms_help_requested = true;
1759 }
1760
gms_output_silence_help_hints()1761 void Magnetic::gms_output_silence_help_hints() {
1762 gms_help_hints_silenced = true;
1763 }
1764
gms_output_provide_help_hint()1765 void Magnetic::gms_output_provide_help_hint() {
1766 if (gms_help_requested && !gms_help_hints_silenced) {
1767 glk_set_style(style_Emphasized);
1768 glk_put_string("[Try 'glk help' for help on special interpreter"
1769 " commands]\n");
1770
1771 gms_help_requested = false;
1772 glk_set_style(style_Normal);
1773 }
1774 }
1775
gms_game_prompted()1776 int Magnetic::gms_game_prompted() {
1777 int result;
1778
1779 result = gms_output_prompt;
1780 gms_output_prompt = false;
1781
1782 return result;
1783 }
1784
gms_detect_game_prompt()1785 void Magnetic::gms_detect_game_prompt() {
1786 int index;
1787
1788 gms_output_prompt = false;
1789
1790 /*
1791 * Search for a prompt across any last unterminated buffered line; a prompt
1792 * is any non-space character on that line.
1793 */
1794 for (index = gms_output_length - 1;
1795 index >= 0 && gms_output_buffer[index] != '\n'; index--) {
1796 if (gms_output_buffer[index] != ' ') {
1797 gms_output_prompt = true;
1798 break;
1799 }
1800 }
1801 }
1802
gms_output_delete()1803 void Magnetic::gms_output_delete() {
1804 free(gms_output_buffer);
1805 gms_output_buffer = NULL;
1806 gms_output_allocation = gms_output_length = 0;
1807 }
1808
gms_output_flush()1809 void Magnetic::gms_output_flush() {
1810 assert(glk_stream_get_current());
1811
1812 if (gms_output_length > 0) {
1813 /*
1814 * See if the game issued a standard prompt, then print the buffer to
1815 * the main window. If providing a help hint, position that before
1816 * the game's prompt (if any).
1817 */
1818 gms_detect_game_prompt();
1819 glk_set_style(style_Normal);
1820
1821 if (gms_output_prompt) {
1822 int index;
1823
1824 for (index = gms_output_length - 1;
1825 index >= 0 && gms_output_buffer[index] != '\n';)
1826 index--;
1827
1828 glk_put_buffer(gms_output_buffer, index + 1);
1829 gms_output_provide_help_hint();
1830 glk_put_buffer(gms_output_buffer + index + 1,
1831 gms_output_length - index - 1);
1832 } else {
1833 glk_put_buffer(gms_output_buffer, gms_output_length);
1834 gms_output_provide_help_hint();
1835 }
1836
1837 gms_output_delete();
1838 }
1839 }
1840
ms_putchar(type8 c)1841 void Magnetic::ms_putchar(type8 c) {
1842 int bytes;
1843 assert(gms_output_length <= gms_output_allocation);
1844
1845 /*
1846 * See if the character is a backspace. Magnetic Scrolls games can send
1847 * backspace characters to the display. We'll need to handle such
1848 * characters specially, by taking the last character out of the buffer.
1849 */
1850 if (c == '\b') {
1851 if (gms_output_length > 0)
1852 gms_output_length--;
1853
1854 return;
1855 }
1856
1857 /* Grow the output buffer if necessary, then add the character. */
1858 for (bytes = gms_output_allocation; bytes < gms_output_length + 1;)
1859 bytes = bytes == 0 ? 1 : bytes << 1;
1860
1861 if (bytes > gms_output_allocation) {
1862 gms_output_buffer = (char *)gms_realloc(gms_output_buffer, bytes);
1863 gms_output_allocation = bytes;
1864 }
1865
1866 gms_output_buffer[gms_output_length++] = c;
1867 }
1868
gms_styled_string(glui32 style,const char * message)1869 void Magnetic::gms_styled_string(glui32 style, const char *message) {
1870 assert(message);
1871
1872 glk_set_style(style);
1873 glk_put_string(message);
1874 glk_set_style(style_Normal);
1875 }
1876
gms_styled_char(glui32 style,char c)1877 void Magnetic::gms_styled_char(glui32 style, char c) {
1878 char str[2];
1879
1880 str[0] = c;
1881 str[1] = '\0';
1882 gms_styled_string(style, str);
1883 }
1884
gms_standout_string(const char * message)1885 void Magnetic::gms_standout_string(const char *message) {
1886 gms_styled_string(style_Emphasized, message);
1887 }
1888
gms_normal_string(const char * message)1889 void Magnetic::gms_normal_string(const char *message) {
1890 gms_styled_string(style_Normal, message);
1891 }
1892
gms_normal_char(char c)1893 void Magnetic::gms_normal_char(char c) {
1894 gms_styled_char(style_Normal, c);
1895 }
1896
gms_header_string(const char * message)1897 void Magnetic::gms_header_string(const char *message) {
1898 gms_styled_string(style_Header, message);
1899 }
1900
gms_banner_string(const char * message)1901 void Magnetic::gms_banner_string(const char *message) {
1902 gms_styled_string(style_Subheader, message);
1903 }
1904
ms_flush()1905 void Magnetic::ms_flush() {
1906 }
1907
1908 /*---------------------------------------------------------------------*/
1909 /* Glk port hint functions */
1910 /*---------------------------------------------------------------------*/
1911
gms_get_hint_max_node(const struct ms_hint hints_[],type16 node)1912 type16 Magnetic::gms_get_hint_max_node(const struct ms_hint hints_[], type16 node) {
1913 const struct ms_hint *hint;
1914 int index;
1915 type16 max_node;
1916 assert(hints_);
1917
1918 hint = hints_ + node;
1919 max_node = node;
1920
1921 switch (hint->nodetype) {
1922 case GMS_HINT_TYPE_TEXT:
1923 break;
1924
1925 case GMS_HINT_TYPE_FOLDER:
1926 /*
1927 * Recursively find the maximum node reference for each link, and keep
1928 * the largest value found.
1929 */
1930 for (index = 0; index < hint->elcount; index++) {
1931 type16 link_max;
1932
1933 link_max = gms_get_hint_max_node(hints_, hint->links[index]);
1934 if (link_max > max_node)
1935 max_node = link_max;
1936 }
1937 break;
1938
1939 default:
1940 gms_fatal("GLK: Invalid hints_ node type encountered");
1941 glk_exit();
1942 }
1943
1944 /*
1945 * Return the largest node reference found, capped to avoid overlapping the
1946 * special end-hints_ value.
1947 */
1948 return max_node < GMS_HINTS_DONE ? max_node : GMS_HINTS_DONE - 1;
1949 }
1950
gms_get_hint_content(const struct ms_hint hints_[],type16 node,int number)1951 const char *Magnetic::gms_get_hint_content(const struct ms_hint hints_[], type16 node, int number) {
1952 const struct ms_hint *hint;
1953 int offset, index;
1954 assert(hints_);
1955
1956 hint = hints_ + node;
1957
1958 /* Run through content until 'number' strings found. */
1959 offset = 0;
1960 for (index = 0; index < number; index++)
1961 offset += strlen(hint->content + offset) + 1;
1962
1963 /* Return the start of the number'th string encountered. */
1964 return hint->content + offset;
1965 }
1966
gms_get_hint_topic(const ms_hint hints_[],type16 node)1967 const char *Magnetic::gms_get_hint_topic(const ms_hint hints_[], type16 node) {
1968 assert(hints_);
1969
1970 if (node == GMS_HINT_ROOT_NODE) {
1971 /* If the node is the root node, return a generic string. */
1972 return GMS_GENERIC_TOPIC;
1973 } else {
1974 type16 parent;
1975 int index;
1976 const char *topic;
1977
1978 /*
1979 * Search the parent for a link to node, and use that as the hint topic;
1980 * NULL if none found.
1981 */
1982 parent = hints_[node].parent;
1983
1984 topic = NULL;
1985 for (index = 0; index < hints_[parent].elcount; index++) {
1986 if (hints_[parent].links[index] == node) {
1987 topic = gms_get_hint_content(hints_, parent, index);
1988 break;
1989 }
1990 }
1991
1992 return topic ? topic : GMS_GENERIC_TOPIC;
1993 }
1994 }
1995
gms_hint_open()1996 int Magnetic::gms_hint_open() {
1997 if (!gms_hint_menu_window) {
1998 assert(!gms_hint_text_window);
1999
2000 /*
2001 * Open the hint menu window. The initial size is two lines, but we'll
2002 * change this later to suit the hint.
2003 */
2004 gms_hint_menu_window = glk_window_open(gms_main_window,
2005 winmethod_Above | winmethod_Fixed,
2006 2, wintype_TextGrid, 0);
2007 if (!gms_hint_menu_window)
2008 return false;
2009
2010 /*
2011 * Now open the hints text window. This is set to be 100% of the size
2012 * of the main window, so should cover what remains of it completely.
2013 */
2014 gms_hint_text_window = glk_window_open(gms_main_window,
2015 winmethod_Above
2016 | winmethod_Proportional,
2017 100, wintype_TextBuffer, 0);
2018 if (!gms_hint_text_window) {
2019 glk_window_close(gms_hint_menu_window, NULL);
2020 gms_hint_menu_window = NULL;
2021 return false;
2022 }
2023 }
2024
2025 return true;
2026 }
2027
gms_hint_close()2028 void Magnetic::Magnetic::gms_hint_close() {
2029 if (gms_hint_menu_window) {
2030 assert(gms_hint_text_window);
2031
2032 glk_window_close(gms_hint_menu_window, NULL);
2033 gms_hint_menu_window = NULL;
2034 glk_window_close(gms_hint_text_window, NULL);
2035 gms_hint_text_window = NULL;
2036 }
2037 }
2038
gms_hint_windows_available()2039 int Magnetic::gms_hint_windows_available() {
2040 return (gms_hint_menu_window && gms_hint_text_window);
2041 }
2042
gms_hint_menu_print(int line,int column,const char * string_,glui32 width,glui32 height)2043 void Magnetic::gms_hint_menu_print(int line, int column, const char *string_,
2044 glui32 width, glui32 height) {
2045 assert(string_);
2046
2047 /* Ignore the call if the text position is outside the window. */
2048 if (!(line > (int)height || column > (int)width)) {
2049 if (gms_hint_windows_available()) {
2050 int posn, index;
2051
2052 glk_window_move_cursor(gms_hint_menu_window, column, line);
2053 glk_set_window(gms_hint_menu_window);
2054
2055 /* Write until the end of the string_, or the end of the window. */
2056 for (posn = column, index = 0;
2057 posn < (int)width && index < (int)strlen(string_); posn++, index++) {
2058 glk_put_char(string_[index]);
2059 }
2060
2061 glk_set_window(gms_main_window);
2062 } else {
2063 static int current_line = 0; /* Retained line number */
2064 static int current_column = 0; /* Retained col number */
2065
2066 int index;
2067
2068 /*
2069 * Check the line number against the last one output. If it is less,
2070 * assume the start of a new block. In this case, perform a hokey
2071 * type of screen clear.
2072 */
2073 if (line < current_line) {
2074 for (index = 0; index < (int)height; index++)
2075 gms_normal_char('\n');
2076
2077 current_line = 0;
2078 current_column = 0;
2079 }
2080
2081 /* Print blank lines until the target line is reached. */
2082 for (; current_line < line; current_line++) {
2083 gms_normal_char('\n');
2084 current_column = 0;
2085 }
2086
2087 /* Now print spaces until the target column is reached. */
2088 for (; current_column < column; current_column++)
2089 gms_normal_char(' ');
2090
2091 /*
2092 * Write characters until the end of the string_, or the end of the
2093 * (self-imposed not-really-there) window.
2094 */
2095 for (index = 0;
2096 current_column < (int)width && index < (int)strlen(string_);
2097 current_column++, index++) {
2098 gms_normal_char(string_[index]);
2099 }
2100 }
2101 }
2102 }
2103
gms_hint_menu_header(int line,const char * string_,glui32 width,glui32 height)2104 void Magnetic::gms_hint_menu_header(int line, const char *string_,
2105 glui32 width, glui32 height) {
2106 int posn, length;
2107 assert(string_);
2108
2109 /* Output the text in the approximate line center. */
2110 length = strlen(string_);
2111 posn = length < (int)width ? (width - length) / 2 : 0;
2112 gms_hint_menu_print(line, posn, string_, width, height);
2113 }
2114
gms_hint_menu_justify(int line,const char * left_string,const char * right_string,glui32 width,glui32 height)2115 void Magnetic::gms_hint_menu_justify(int line, const char *left_string,
2116 const char *right_string, glui32 width, glui32 height) {
2117 int posn, length;
2118 assert(left_string && right_string);
2119
2120 /* Write left text normally to window left. */
2121 gms_hint_menu_print(line, 0, left_string, width, height);
2122
2123 /* Output the right text flush with the right of the window. */
2124 length = strlen(right_string);
2125 posn = length < (int)width ? width - length : 0;
2126 gms_hint_menu_print(line, posn, right_string, width, height);
2127 }
2128
gms_hint_text_print(const char * string_)2129 void Magnetic::gms_hint_text_print(const char *string_) {
2130 assert(string_);
2131
2132 if (gms_hint_windows_available()) {
2133 glk_set_window(gms_hint_text_window);
2134 glk_put_string(string_);
2135 glk_set_window(gms_main_window);
2136 } else
2137 gms_normal_string(string_);
2138 }
2139
gms_hint_menutext_start()2140 void Magnetic::gms_hint_menutext_start() {
2141 /*
2142 * Twiddle for non-windowing libraries; 'clear' the main window by writing
2143 * a null string at line 1, then a null string at line 0. This works
2144 * because we know the current output line in gms_hint_menu_print() is zero,
2145 * since we set it that way with gms_hint_menutext_done(), or if this is
2146 * the first call, then that's its initial value.
2147 */
2148 if (!gms_hint_windows_available()) {
2149 gms_hint_menu_print(1, 0, "",
2150 GMS_HINT_DEFAULT_WIDTH, GMS_HINT_DEFAULT_HEIGHT);
2151 gms_hint_menu_print(0, 0, "",
2152 GMS_HINT_DEFAULT_WIDTH, GMS_HINT_DEFAULT_HEIGHT);
2153 }
2154 }
2155
gms_hint_menutext_done()2156 void Magnetic::gms_hint_menutext_done() {
2157 /*
2158 * Twiddle for non-windowing libraries; 'clear' the main window by writing
2159 * an empty string to line zero. For windowing Glk libraries, this function
2160 * does nothing.
2161 */
2162 if (!gms_hint_windows_available()) {
2163 gms_hint_menu_print(0, 0, "",
2164 GMS_HINT_DEFAULT_WIDTH, GMS_HINT_DEFAULT_HEIGHT);
2165 }
2166 }
2167
gms_hint_menutext_char_event(event_t * event)2168 void Magnetic::gms_hint_menutext_char_event(event_t *event) {
2169 assert(event);
2170
2171 if (gms_hint_windows_available()) {
2172 glk_request_char_event(gms_hint_menu_window);
2173 glk_request_char_event(gms_hint_text_window);
2174
2175 gms_event_wait(evtype_CharInput, event);
2176 assert(event->window == gms_hint_menu_window
2177 || event->window == gms_hint_text_window);
2178
2179 glk_cancel_char_event(gms_hint_menu_window);
2180 glk_cancel_char_event(gms_hint_text_window);
2181 } else {
2182 glk_request_char_event(gms_main_window);
2183 gms_event_wait(evtype_CharInput, event);
2184 }
2185 }
2186
gms_hint_arrange_windows(int requested_lines,glui32 * width,glui32 * height)2187 void Magnetic::gms_hint_arrange_windows(int requested_lines, glui32 *width, glui32 *height) {
2188 if (gms_hint_windows_available()) {
2189 winid_t parent;
2190
2191 /* Resize the hint menu window to fit the current hint. */
2192 parent = glk_window_get_parent(gms_hint_menu_window);
2193 glk_window_set_arrangement(parent,
2194 winmethod_Above | winmethod_Fixed,
2195 requested_lines, NULL);
2196
2197 uint width_temp, height_temp;
2198
2199 /* Measure, and return the size of the hint menu window. */
2200 glk_window_get_size(gms_hint_menu_window, &width_temp, &height_temp);
2201
2202 *width = width_temp;
2203 *height = height_temp;
2204
2205 /* Clear both the hint menu and the hint text window. */
2206 glk_window_clear(gms_hint_menu_window);
2207 glk_window_clear(gms_hint_text_window);
2208 } else {
2209 /*
2210 * No hints windows, so default width and height. The hints output
2211 * functions will cope with this.
2212 */
2213 if (width)
2214 *width = GMS_HINT_DEFAULT_WIDTH;
2215 if (height)
2216 *height = GMS_HINT_DEFAULT_HEIGHT;
2217 }
2218 }
2219
gms_hint_display_folder(const ms_hint hints_[],const int cursor[],type16 node)2220 void Magnetic::gms_hint_display_folder(const ms_hint hints_[],
2221 const int cursor[], type16 node) {
2222 glui32 width, height;
2223 int line, index;
2224 assert(hints_ && cursor);
2225
2226 /*
2227 * Arrange windows to suit the hint folder. For a folder menu window we
2228 * use one line for each element, three for the controls, and two spacers,
2229 * making a total of five additional lines. Width and height receive the
2230 * actual menu window dimensions.
2231 */
2232 gms_hint_arrange_windows(hints_[node].elcount + 5, &width, &height);
2233
2234 /* Paint in the menu header. */
2235 line = 0;
2236 gms_hint_menu_header(line++,
2237 gms_get_hint_topic(hints_, node),
2238 width, height);
2239 gms_hint_menu_justify(line++,
2240 " N = next subject ", " P = previous ",
2241 width, height);
2242 gms_hint_menu_justify(line++,
2243 " RETURN = read subject ",
2244 node == GMS_HINT_ROOT_NODE
2245 ? " Q = resume game " : " Q = previous menu ",
2246 width, height);
2247
2248 /*
2249 * Output a blank line, then the menu for the node's folder hint. The folder
2250 * text for the selected hint is preceded by a '>' pointer.
2251 */
2252 line++;
2253 for (index = 0; index < hints_[node].elcount; index++) {
2254 gms_hint_menu_print(line, 3,
2255 index == cursor[node] ? ">" : " ",
2256 width, height);
2257 gms_hint_menu_print(line++, 5,
2258 gms_get_hint_content(hints_, node, index),
2259 width, height);
2260 }
2261
2262 /*
2263 * Terminate with a blank line; using a single space here improves cursor
2264 * positioning for optimized output libraries (for example, without it,
2265 * curses output will leave the cursor at the end of the previous line).
2266 */
2267 gms_hint_menu_print(line, 0, " ", width, height);
2268 }
2269
gms_hint_display_text(const ms_hint hints_[],const int cursor[],type16 node)2270 void Magnetic::gms_hint_display_text(const ms_hint hints_[],
2271 const int cursor[], type16 node) {
2272 glui32 width, height;
2273 int line, index;
2274 assert(hints_ && cursor);
2275
2276 /*
2277 * Arrange windows to suit the hint text. For a hint menu, we use a simple
2278 * two-line set of controls; everything else is in the hints_ text window.
2279 * Width and height receive the actual menu window dimensions.
2280 */
2281 gms_hint_arrange_windows(2, &width, &height);
2282
2283 /* Paint in a short menu header. */
2284 line = 0;
2285 gms_hint_menu_header(line++,
2286 gms_get_hint_topic(hints_, node),
2287 width, height);
2288 gms_hint_menu_justify(line++,
2289 " RETURN = read hint ", " Q = previous menu ",
2290 width, height);
2291
2292 /*
2293 * Output hints_ to the hints_ text window. hints_ not yet exposed are
2294 * indicated by the cursor for the hint, and are displayed as a dash.
2295 */
2296 gms_hint_text_print("\n");
2297 for (index = 0; index < hints_[node].elcount; index++) {
2298 char buf[16];
2299
2300 sprintf(buf, "%3d. ", index + 1);
2301 gms_hint_text_print(buf);
2302
2303 gms_hint_text_print(index < cursor[node]
2304 ? gms_get_hint_content(hints_, node, index) : "-");
2305 gms_hint_text_print("\n");
2306 }
2307 }
2308
gms_hint_display(const ms_hint hints_[],const int cursor[],type16 node)2309 void Magnetic::gms_hint_display(const ms_hint hints_[], const int cursor[], type16 node) {
2310 assert(hints_ && cursor);
2311
2312 switch (hints_[node].nodetype) {
2313 case GMS_HINT_TYPE_TEXT:
2314 gms_hint_display_text(hints_, cursor, node);
2315 break;
2316
2317 case GMS_HINT_TYPE_FOLDER:
2318 gms_hint_display_folder(hints_, cursor, node);
2319 break;
2320
2321 default:
2322 gms_fatal("GLK: Invalid hints_ node type encountered");
2323 glk_exit();
2324 }
2325 }
2326
gms_hint_handle_folder(const ms_hint hints_[],int cursor[],type16 node,glui32 keycode)2327 type16 Magnetic::gms_hint_handle_folder(const ms_hint hints_[],
2328 int cursor[], type16 node, glui32 keycode) {
2329 unsigned char response;
2330 type16 next_node;
2331 assert(hints_ && cursor);
2332
2333 /* Convert key code into a single response character. */
2334 switch (keycode) {
2335 case keycode_Down:
2336 response = 'N';
2337 break;
2338 case keycode_Up:
2339 response = 'P';
2340 break;
2341 case keycode_Right:
2342 case keycode_Return:
2343 response = '\n';
2344 break;
2345 case keycode_Left:
2346 case keycode_Escape:
2347 response = 'Q';
2348 break;
2349 default:
2350 response = keycode <= BYTE_MAX_VAL ? glk_char_to_upper(keycode) : 0;
2351 break;
2352 }
2353
2354 /*
2355 * Now handle the response character. We'll default the next node to be
2356 * this node, but a response case can change it.
2357 */
2358 next_node = node;
2359 switch (response) {
2360 case 'N':
2361 /* Advance the hint cursor, wrapping at the folder end. */
2362 if (cursor[node] < hints_[node].elcount - 1)
2363 cursor[node]++;
2364 else
2365 cursor[node] = 0;
2366 break;
2367
2368 case 'P':
2369 /* Regress the hint cursor, wrapping at the folder start. */
2370 if (cursor[node] > 0)
2371 cursor[node]--;
2372 else
2373 cursor[node] = hints_[node].elcount - 1;
2374 break;
2375
2376 case '\n':
2377 /* The next node is the hint node at the cursor position. */
2378 next_node = hints_[node].links[cursor[node]];
2379 break;
2380
2381 case 'Q':
2382 /* If root, we're done; if not, next node is node's parent. */
2383 next_node = node == GMS_HINT_ROOT_NODE
2384 ? GMS_HINTS_DONE : hints_[node].parent;
2385 break;
2386
2387 default:
2388 break;
2389 }
2390
2391 return next_node;
2392 }
2393
gms_hint_handle_text(const ms_hint hints_[],int cursor[],type16 node,glui32 keycode)2394 type16 Magnetic::gms_hint_handle_text(const ms_hint hints_[],
2395 int cursor[], type16 node, glui32 keycode) {
2396 unsigned char response;
2397 type16 next_node;
2398 assert(hints_ && cursor);
2399
2400 /* Convert key code into a single response character. */
2401 switch (keycode) {
2402 case keycode_Right:
2403 case keycode_Return:
2404 response = '\n';
2405 break;
2406 case keycode_Left:
2407 case keycode_Escape:
2408 response = 'Q';
2409 break;
2410 default:
2411 response = keycode <= BYTE_MAX_VAL ? glk_char_to_upper(keycode) : 0;
2412 break;
2413 }
2414
2415 /*
2416 * Now handle the response character. We'll default the next node to be
2417 * this node, but a response case can change it.
2418 */
2419 next_node = node;
2420 switch (response) {
2421 case '\n':
2422 /* If not at end of the hint, advance the hint cursor. */
2423 if (cursor[node] < hints_[node].elcount)
2424 cursor[node]++;
2425 break;
2426
2427 case 'Q':
2428 /* Done with this hint node, so next node is its parent. */
2429 next_node = hints_[node].parent;
2430 break;
2431
2432 default:
2433 break;
2434 }
2435
2436 return next_node;
2437 }
2438
gms_hint_handle(const ms_hint hints_[],int cursor[],type16 node,glui32 keycode)2439 type16 Magnetic::gms_hint_handle(const ms_hint hints_[],
2440 int cursor[], type16 node, glui32 keycode) {
2441 type16 next_node;
2442 assert(hints_ && cursor);
2443
2444 next_node = GMS_HINT_ROOT_NODE;
2445 switch (hints_[node].nodetype) {
2446 case GMS_HINT_TYPE_TEXT:
2447 next_node = gms_hint_handle_text(hints_, cursor, node, keycode);
2448 break;
2449
2450 case GMS_HINT_TYPE_FOLDER:
2451 next_node = gms_hint_handle_folder(hints_, cursor, node, keycode);
2452 break;
2453
2454 default:
2455 gms_fatal("GLK: Invalid hints_ node type encountered");
2456 glk_exit();
2457 }
2458
2459 return next_node;
2460 }
2461
ms_showhints(ms_hint * hints_)2462 type8 Magnetic::ms_showhints(ms_hint *hints_) {
2463 type16 hint_count;
2464 glui32 crc;
2465 assert(hints_);
2466
2467 /*
2468 * Find the number of hints_ in the array. To do this, we'll visit every
2469 * node in a tree search, starting at the root, to locate the maximum node
2470 * number found, then add one to that. It's a pity that the interpreter
2471 * doesn't hand us this information directly.
2472 */
2473 hint_count = gms_get_hint_max_node(hints_, GMS_HINT_ROOT_NODE) + 1;
2474
2475 /*
2476 * Calculate a CRC for the hints_ array data. If the CRC has changed, or
2477 * this is the first call, assign a new cursor array.
2478 */
2479 crc = gms_get_buffer_crc(hints_, hint_count * sizeof(*hints_));
2480 if (crc != hints_current_crc || !hints_crc_initialized) {
2481 int bytes;
2482
2483 /* Allocate new cursors, and set all to zero initial state. */
2484 free(gms_hint_cursor);
2485 bytes = hint_count * sizeof(*gms_hint_cursor);
2486 gms_hint_cursor = (int *)gms_malloc(bytes);
2487 memset(gms_hint_cursor, 0, bytes);
2488
2489 /*
2490 * Retain the hints_ CRC, for later comparisons, and set is_initialized
2491 * flag.
2492 */
2493 hints_current_crc = crc;
2494 hints_crc_initialized = true;
2495 }
2496
2497 /*
2498 * Save the hints_ array passed in. This is done here since even if the data
2499 * remains the same (found by the CRC check above), the pointer to it might
2500 * have changed.
2501 */
2502 gms_hints = hints_;
2503
2504 /*
2505 * Try to create the hints_ windows. If they can't be created, perhaps
2506 * because the Glk library doesn't support it, the output functions will
2507 * work around this.
2508 */
2509 gms_hint_open();
2510 gms_hint_menutext_start();
2511
2512 /*
2513 * Begin hints_ display at the root node, and navigate until the user exits
2514 * hints_.
2515 */
2516 gms_current_hint_node = GMS_HINT_ROOT_NODE;
2517 while (gms_current_hint_node != GMS_HINTS_DONE) {
2518 event_t event;
2519
2520 assert(gms_current_hint_node < hint_count);
2521 gms_hint_display(gms_hints, gms_hint_cursor, gms_current_hint_node);
2522
2523 /* Get and handle a character key event for hint navigation. */
2524 gms_hint_menutext_char_event(&event);
2525 assert(event.type == evtype_CharInput);
2526 gms_current_hint_node = gms_hint_handle(gms_hints,
2527 gms_hint_cursor,
2528 gms_current_hint_node,
2529 event.val1);
2530 }
2531
2532 /* Done with hint windows. */
2533 gms_hint_menutext_done();
2534 gms_hint_close();
2535
2536 return GMS_HINT_SUCCESS;
2537 }
2538
gms_hint_redraw()2539 void Magnetic::gms_hint_redraw() {
2540 if (gms_hint_windows_available()) {
2541 assert(gms_hints && gms_hint_cursor);
2542 gms_hint_display(gms_hints, gms_hint_cursor, gms_current_hint_node);
2543 }
2544 }
2545
gms_hints_cleanup()2546 void Magnetic::gms_hints_cleanup() {
2547 free(gms_hint_cursor);
2548 gms_hint_cursor = NULL;
2549
2550 gms_hints = NULL;
2551 gms_current_hint_node = 0;
2552 }
2553
ms_playmusic(type8 * midi_data,type32 length,type16 tempo)2554 void Magnetic::ms_playmusic(type8 *midi_data, type32 length, type16 tempo) {
2555 }
2556
2557 /*---------------------------------------------------------------------*/
2558 /* Glk command escape functions */
2559 /*---------------------------------------------------------------------*/
2560
gms_command_undo(const char * argument)2561 void Magnetic::gms_command_undo(const char *argument) {
2562 assert(argument);
2563 }
2564
gms_command_script(const char * argument)2565 void Magnetic::gms_command_script(const char *argument) {
2566 assert(argument);
2567
2568 if (gms_strcasecmp(argument, "on") == 0) {
2569 frefid_t fileref;
2570
2571 if (gms_transcript_stream) {
2572 gms_normal_string("Glk transcript is already on.\n");
2573 return;
2574 }
2575
2576 fileref = glk_fileref_create_by_prompt(fileusage_Transcript
2577 | fileusage_TextMode,
2578 filemode_WriteAppend, 0);
2579 if (!fileref) {
2580 gms_standout_string("Glk transcript failed.\n");
2581 return;
2582 }
2583
2584 gms_transcript_stream = glk_stream_open_file(fileref,
2585 filemode_WriteAppend, 0);
2586 glk_fileref_destroy(fileref);
2587 if (!gms_transcript_stream) {
2588 gms_standout_string("Glk transcript failed.\n");
2589 return;
2590 }
2591
2592 glk_window_set_echo_stream(gms_main_window, gms_transcript_stream);
2593
2594 gms_normal_string("Glk transcript is now on.\n");
2595 }
2596
2597 else if (gms_strcasecmp(argument, "off") == 0) {
2598 if (!gms_transcript_stream) {
2599 gms_normal_string("Glk transcript is already off.\n");
2600 return;
2601 }
2602
2603 glk_stream_close(gms_transcript_stream, NULL);
2604 gms_transcript_stream = NULL;
2605
2606 glk_window_set_echo_stream(gms_main_window, NULL);
2607
2608 gms_normal_string("Glk transcript is now off.\n");
2609 }
2610
2611 else if (strlen(argument) == 0) {
2612 gms_normal_string("Glk transcript is ");
2613 gms_normal_string(gms_transcript_stream ? "on" : "off");
2614 gms_normal_string(".\n");
2615 }
2616
2617 else {
2618 gms_normal_string("Glk transcript can be ");
2619 gms_standout_string("on");
2620 gms_normal_string(", or ");
2621 gms_standout_string("off");
2622 gms_normal_string(".\n");
2623 }
2624 }
2625
gms_command_inputlog(const char * argument)2626 void Magnetic::gms_command_inputlog(const char *argument) {
2627 assert(argument);
2628
2629 if (gms_strcasecmp(argument, "on") == 0) {
2630 frefid_t fileref;
2631
2632 if (gms_inputlog_stream) {
2633 gms_normal_string("Glk input logging is already on.\n");
2634 return;
2635 }
2636
2637 fileref = glk_fileref_create_by_prompt(fileusage_InputRecord
2638 | fileusage_BinaryMode,
2639 filemode_WriteAppend, 0);
2640 if (!fileref) {
2641 gms_standout_string("Glk input logging failed.\n");
2642 return;
2643 }
2644
2645 gms_inputlog_stream = glk_stream_open_file(fileref,
2646 filemode_WriteAppend, 0);
2647 glk_fileref_destroy(fileref);
2648 if (!gms_inputlog_stream) {
2649 gms_standout_string("Glk input logging failed.\n");
2650 return;
2651 }
2652
2653 gms_normal_string("Glk input logging is now on.\n");
2654 }
2655
2656 else if (gms_strcasecmp(argument, "off") == 0) {
2657 if (!gms_inputlog_stream) {
2658 gms_normal_string("Glk input logging is already off.\n");
2659 return;
2660 }
2661
2662 glk_stream_close(gms_inputlog_stream, NULL);
2663 gms_inputlog_stream = NULL;
2664
2665 gms_normal_string("Glk input log is now off.\n");
2666 }
2667
2668 else if (strlen(argument) == 0) {
2669 gms_normal_string("Glk input logging is ");
2670 gms_normal_string(gms_inputlog_stream ? "on" : "off");
2671 gms_normal_string(".\n");
2672 }
2673
2674 else {
2675 gms_normal_string("Glk input logging can be ");
2676 gms_standout_string("on");
2677 gms_normal_string(", or ");
2678 gms_standout_string("off");
2679 gms_normal_string(".\n");
2680 }
2681 }
2682
gms_command_readlog(const char * argument)2683 void Magnetic::gms_command_readlog(const char *argument) {
2684 assert(argument);
2685
2686 if (gms_strcasecmp(argument, "on") == 0) {
2687 frefid_t fileref;
2688
2689 if (gms_readlog_stream) {
2690 gms_normal_string("Glk read log is already on.\n");
2691 return;
2692 }
2693
2694 fileref = glk_fileref_create_by_prompt(fileusage_InputRecord
2695 | fileusage_BinaryMode,
2696 filemode_Read, 0);
2697 if (!fileref) {
2698 gms_standout_string("Glk read log failed.\n");
2699 return;
2700 }
2701
2702 if (!glk_fileref_does_file_exist(fileref)) {
2703 glk_fileref_destroy(fileref);
2704 gms_standout_string("Glk read log failed.\n");
2705 return;
2706 }
2707
2708 gms_readlog_stream = glk_stream_open_file(fileref, filemode_Read, 0);
2709 glk_fileref_destroy(fileref);
2710 if (!gms_readlog_stream) {
2711 gms_standout_string("Glk read log failed.\n");
2712 return;
2713 }
2714
2715 gms_normal_string("Glk read log is now on.\n");
2716 }
2717
2718 else if (gms_strcasecmp(argument, "off") == 0) {
2719 if (!gms_readlog_stream) {
2720 gms_normal_string("Glk read log is already off.\n");
2721 return;
2722 }
2723
2724 glk_stream_close(gms_readlog_stream, NULL);
2725 gms_readlog_stream = NULL;
2726
2727 gms_normal_string("Glk read log is now off.\n");
2728 }
2729
2730 else if (strlen(argument) == 0) {
2731 gms_normal_string("Glk read log is ");
2732 gms_normal_string(gms_readlog_stream ? "on" : "off");
2733 gms_normal_string(".\n");
2734 }
2735
2736 else {
2737 gms_normal_string("Glk read log can be ");
2738 gms_standout_string("on");
2739 gms_normal_string(", or ");
2740 gms_standout_string("off");
2741 gms_normal_string(".\n");
2742 }
2743 }
2744
gms_command_abbreviations(const char * argument)2745 void Magnetic::gms_command_abbreviations(const char *argument) {
2746 assert(argument);
2747
2748 if (gms_strcasecmp(argument, "on") == 0) {
2749 if (gms_abbreviations_enabled) {
2750 gms_normal_string("Glk abbreviation expansions are already on.\n");
2751 return;
2752 }
2753
2754 gms_abbreviations_enabled = true;
2755 gms_normal_string("Glk abbreviation expansions are now on.\n");
2756 }
2757
2758 else if (gms_strcasecmp(argument, "off") == 0) {
2759 if (!gms_abbreviations_enabled) {
2760 gms_normal_string("Glk abbreviation expansions are already off.\n");
2761 return;
2762 }
2763
2764 gms_abbreviations_enabled = false;
2765 gms_normal_string("Glk abbreviation expansions are now off.\n");
2766 }
2767
2768 else if (strlen(argument) == 0) {
2769 gms_normal_string("Glk abbreviation expansions are ");
2770 gms_normal_string(gms_abbreviations_enabled ? "on" : "off");
2771 gms_normal_string(".\n");
2772 }
2773
2774 else {
2775 gms_normal_string("Glk abbreviation expansions can be ");
2776 gms_standout_string("on");
2777 gms_normal_string(", or ");
2778 gms_standout_string("off");
2779 gms_normal_string(".\n");
2780 }
2781 }
2782
gms_command_graphics(const char * argument)2783 void Magnetic::gms_command_graphics(const char *argument) {
2784 assert(argument);
2785
2786 if (!gms_graphics_possible) {
2787 gms_normal_string("Glk graphics are not available.\n");
2788 return;
2789 }
2790
2791 if (gms_strcasecmp(argument, "on") == 0) {
2792 if (gms_graphics_enabled) {
2793 gms_normal_string("Glk graphics are already on.\n");
2794 return;
2795 }
2796
2797 gms_graphics_enabled = true;
2798
2799 /* If a picture is loaded, call the restart function to repaint it. */
2800 if (gms_graphics_picture_is_available()) {
2801 if (!gms_graphics_open()) {
2802 gms_normal_string("Glk graphics error.\n");
2803 return;
2804 }
2805 gms_graphics_restart();
2806 }
2807
2808 gms_normal_string("Glk graphics are now on.\n");
2809 }
2810
2811 else if (gms_strcasecmp(argument, "off") == 0) {
2812 if (!gms_graphics_enabled) {
2813 gms_normal_string("Glk graphics are already off.\n");
2814 return;
2815 }
2816
2817 /*
2818 * Set graphics to disabled, and stop any graphics processing. Close
2819 * the graphics window.
2820 */
2821 gms_graphics_enabled = false;
2822 gms_graphics_stop();
2823 gms_graphics_close();
2824
2825 gms_normal_string("Glk graphics are now off.\n");
2826 }
2827
2828 else if (strlen(argument) == 0) {
2829 gms_normal_string("Glk graphics are available,");
2830 gms_normal_string(gms_graphics_enabled
2831 ? " and enabled.\n" : " but disabled.\n");
2832
2833 if (gms_graphics_picture_is_available()) {
2834 int width, height, is_animated;
2835
2836 if (gms_graphics_get_picture_details(&width, &height, &is_animated)) {
2837 char buf[16];
2838
2839 gms_normal_string("There is ");
2840 gms_normal_string(is_animated ? "an animated" : "a");
2841 gms_normal_string(" picture loaded, ");
2842
2843 sprintf(buf, "%d", width);
2844 gms_normal_string(buf);
2845 gms_normal_string(" by ");
2846
2847 sprintf(buf, "%d", height);
2848 gms_normal_string(buf);
2849
2850 gms_normal_string(" pixels.\n");
2851 }
2852 }
2853
2854 if (!gms_graphics_interpreter_enabled())
2855 gms_normal_string("Interpreter graphics are disabled.\n");
2856
2857 if (gms_graphics_enabled && gms_graphics_are_displayed()) {
2858 int color_count, is_active;
2859 const char *gamma;
2860
2861 if (gms_graphics_get_rendering_details(&gamma, &color_count,
2862 &is_active)) {
2863 char buf[16];
2864
2865 gms_normal_string("Graphics are ");
2866 gms_normal_string(is_active ? "active, " : "displayed, ");
2867
2868 sprintf(buf, "%d", color_count);
2869 gms_normal_string(buf);
2870 gms_normal_string(" colours");
2871
2872 if (gms_gamma_mode == GAMMA_OFF)
2873 gms_normal_string(", without gamma correction");
2874 else {
2875 gms_normal_string(", with gamma ");
2876 gms_normal_string(gamma);
2877 gms_normal_string(" correction");
2878 }
2879 gms_normal_string(".\n");
2880 } else
2881 gms_normal_string("Graphics are being displayed.\n");
2882 }
2883
2884 if (gms_graphics_enabled && !gms_graphics_are_displayed())
2885 gms_normal_string("Graphics are not being displayed.\n");
2886 }
2887
2888 else {
2889 gms_normal_string("Glk graphics can be ");
2890 gms_standout_string("on");
2891 gms_normal_string(", or ");
2892 gms_standout_string("off");
2893 gms_normal_string(".\n");
2894 }
2895 }
2896
gms_command_gamma(const char * argument)2897 void Magnetic::gms_command_gamma(const char *argument) {
2898 assert(argument);
2899
2900 if (!gms_graphics_possible) {
2901 gms_normal_string("Glk automatic gamma correction is not available.\n");
2902 return;
2903 }
2904
2905 if (gms_strcasecmp(argument, "high") == 0) {
2906 if (gms_gamma_mode == GAMMA_HIGH) {
2907 gms_normal_string("Glk automatic gamma correction mode is"
2908 " already 'high'.\n");
2909 return;
2910 }
2911
2912 gms_gamma_mode = GAMMA_HIGH;
2913 gms_graphics_restart();
2914
2915 gms_normal_string("Glk automatic gamma correction mode is"
2916 " now 'high'.\n");
2917 }
2918
2919 else if (gms_strcasecmp(argument, "normal") == 0
2920 || gms_strcasecmp(argument, "on") == 0) {
2921 if (gms_gamma_mode == GAMMA_NORMAL) {
2922 gms_normal_string("Glk automatic gamma correction mode is"
2923 " already 'normal'.\n");
2924 return;
2925 }
2926
2927 gms_gamma_mode = GAMMA_NORMAL;
2928 gms_graphics_restart();
2929
2930 gms_normal_string("Glk automatic gamma correction mode is"
2931 " now 'normal'.\n");
2932 }
2933
2934 else if (gms_strcasecmp(argument, "none") == 0
2935 || gms_strcasecmp(argument, "off") == 0) {
2936 if (gms_gamma_mode == GAMMA_OFF) {
2937 gms_normal_string("Glk automatic gamma correction mode is"
2938 " already 'off'.\n");
2939 return;
2940 }
2941
2942 gms_gamma_mode = GAMMA_OFF;
2943 gms_graphics_restart();
2944
2945 gms_normal_string("Glk automatic gamma correction mode is"
2946 " now 'off'.\n");
2947 }
2948
2949 else if (strlen(argument) == 0) {
2950 gms_normal_string("Glk automatic gamma correction mode is '");
2951 switch (gms_gamma_mode) {
2952 case GAMMA_OFF:
2953 default:
2954 gms_normal_string("off");
2955 break;
2956 case GAMMA_NORMAL:
2957 gms_normal_string("normal");
2958 break;
2959 case GAMMA_HIGH:
2960 gms_normal_string("high");
2961 break;
2962 }
2963 gms_normal_string("'.\n");
2964 }
2965
2966 else {
2967 gms_normal_string("Glk automatic gamma correction mode can be ");
2968 gms_standout_string("high");
2969 gms_normal_string(", ");
2970 gms_standout_string("normal");
2971 gms_normal_string(", or ");
2972 gms_standout_string("off");
2973 gms_normal_string(".\n");
2974 }
2975 }
2976
gms_command_animations(const char * argument)2977 void Magnetic::gms_command_animations(const char *argument) {
2978 assert(argument);
2979
2980 if (!gms_graphics_possible) {
2981 gms_normal_string("Glk graphics animations are not available.\n");
2982 return;
2983 }
2984
2985 if (gms_strcasecmp(argument, "on") == 0) {
2986 int is_animated;
2987
2988 if (gms_animation_enabled) {
2989 gms_normal_string("Glk graphics animations are already on.\n");
2990 return;
2991 }
2992
2993 /*
2994 * Set animation to on, and restart graphics if the current picture
2995 * is animated; if it isn't, we can leave it displayed as is, since
2996 * changing animation mode doesn't affect this picture.
2997 */
2998 gms_animation_enabled = true;
2999 if (gms_graphics_get_picture_details(NULL, NULL, &is_animated)) {
3000 if (is_animated)
3001 gms_graphics_restart();
3002 }
3003
3004 gms_normal_string("Glk graphics animations are now on.\n");
3005 }
3006
3007 else if (gms_strcasecmp(argument, "off") == 0) {
3008 int is_animated;
3009
3010 if (!gms_animation_enabled) {
3011 gms_normal_string("Glk graphics animations are already off.\n");
3012 return;
3013 }
3014
3015 gms_animation_enabled = false;
3016 if (gms_graphics_get_picture_details(NULL, NULL, &is_animated)) {
3017 if (is_animated)
3018 gms_graphics_restart();
3019 }
3020
3021 gms_normal_string("Glk graphics animations are now off.\n");
3022 }
3023
3024 else if (strlen(argument) == 0) {
3025 gms_normal_string("Glk graphics animations are ");
3026 gms_normal_string(gms_animation_enabled ? "on" : "off");
3027 gms_normal_string(".\n");
3028 }
3029
3030 else {
3031 gms_normal_string("Glk graphics animations can be ");
3032 gms_standout_string("on");
3033 gms_normal_string(", or ");
3034 gms_standout_string("off");
3035 gms_normal_string(".\n");
3036 }
3037 }
3038
gms_command_prompts(const char * argument)3039 void Magnetic::gms_command_prompts(const char *argument) {
3040 assert(argument);
3041
3042 if (gms_strcasecmp(argument, "on") == 0) {
3043 if (gms_prompt_enabled) {
3044 gms_normal_string("Glk extra prompts are already on.\n");
3045 return;
3046 }
3047
3048 gms_prompt_enabled = true;
3049 gms_normal_string("Glk extra prompts are now on.\n");
3050
3051 /* Check for a game prompt to clear the flag. */
3052 gms_game_prompted();
3053 }
3054
3055 else if (gms_strcasecmp(argument, "off") == 0) {
3056 if (!gms_prompt_enabled) {
3057 gms_normal_string("Glk extra prompts are already off.\n");
3058 return;
3059 }
3060
3061 gms_prompt_enabled = false;
3062 gms_normal_string("Glk extra prompts are now off.\n");
3063 }
3064
3065 else if (strlen(argument) == 0) {
3066 gms_normal_string("Glk extra prompts are ");
3067 gms_normal_string(gms_prompt_enabled ? "on" : "off");
3068 gms_normal_string(".\n");
3069 }
3070
3071 else {
3072 gms_normal_string("Glk extra prompts can be ");
3073 gms_standout_string("on");
3074 gms_normal_string(", or ");
3075 gms_standout_string("off");
3076 gms_normal_string(".\n");
3077 }
3078 }
3079
gms_command_print_version_number(glui32 version_)3080 void Magnetic::gms_command_print_version_number(glui32 version_) {
3081 Common::String str = Common::String::format("%lu.%lu.%lu",
3082 (unsigned long)version_ >> 16,
3083 (unsigned long)(version_ >> 8) & 0xff,
3084 (unsigned long)version_ & 0xff);
3085 gms_normal_string(str.c_str());
3086 }
3087
gms_command_version(const char * argument)3088 void Magnetic::gms_command_version(const char *argument) {
3089 glui32 version_;
3090 assert(argument);
3091
3092 gms_normal_string("This is version_ ");
3093 gms_command_print_version_number(GMS_PORT_VERSION);
3094 gms_normal_string(" of the Glk Magnetic port.\n");
3095
3096 version_ = glk_gestalt(gestalt_Version, 0);
3097 gms_normal_string("The Glk library version_ is ");
3098 gms_command_print_version_number(version_);
3099 gms_normal_string(".\n");
3100 }
3101
gms_command_commands(const char * argument)3102 void Magnetic::gms_command_commands(const char *argument) {
3103 assert(argument);
3104
3105 if (gms_strcasecmp(argument, "on") == 0) {
3106 gms_normal_string("Glk commands are already on.\n");
3107 }
3108
3109 else if (gms_strcasecmp(argument, "off") == 0) {
3110 gms_commands_enabled = false;
3111 gms_normal_string("Glk commands are now off.\n");
3112 }
3113
3114 else if (strlen(argument) == 0) {
3115 gms_normal_string("Glk commands are ");
3116 gms_normal_string(gms_commands_enabled ? "on" : "off");
3117 gms_normal_string(".\n");
3118 }
3119
3120 else {
3121 gms_normal_string("Glk commands can be ");
3122 gms_standout_string("on");
3123 gms_normal_string(", or ");
3124 gms_standout_string("off");
3125 gms_normal_string(".\n");
3126 }
3127 }
3128
gms_command_summary(const char * argument)3129 void Magnetic::gms_command_summary(const char *argument) {
3130 const gms_command_t *entry;
3131 assert(argument);
3132
3133 /*
3134 * Call handlers that have status to report with an empty argument,
3135 * prompting each to print its current setting.
3136 */
3137 for (entry = GMS_COMMAND_TABLE; entry->command; entry++) {
3138 if (entry->handler == &Magnetic::gms_command_summary
3139 || entry->handler == &Magnetic::gms_command_undo
3140 || entry->handler == &Magnetic::gms_command_help)
3141 continue;
3142
3143 (this->*entry->handler)("");
3144 }
3145 }
3146
gms_command_help(const char * command)3147 void Magnetic::gms_command_help(const char *command) {
3148 const gms_command_t *entry, *matched;
3149 assert(command);
3150
3151 if (strlen(command) == 0) {
3152 gms_normal_string("Glk commands are");
3153 for (entry = GMS_COMMAND_TABLE; entry->command; entry++) {
3154 const gms_command_t *next;
3155
3156 next = entry + 1;
3157 gms_normal_string(next->command ? " " : " and ");
3158 gms_standout_string(entry->command);
3159 gms_normal_string(next->command ? "," : ".\n\n");
3160 }
3161
3162 gms_normal_string("Glk commands may be abbreviated, as long as"
3163 " the abbreviation is unambiguous. Use ");
3164 gms_standout_string("glk help");
3165 gms_normal_string(" followed by a Glk command name for help on that"
3166 " command.\n");
3167 return;
3168 }
3169
3170 matched = NULL;
3171 for (entry = GMS_COMMAND_TABLE; entry->command; entry++) {
3172 if (gms_strncasecmp(command, entry->command, strlen(command)) == 0) {
3173 if (matched) {
3174 gms_normal_string("The Glk command ");
3175 gms_standout_string(command);
3176 gms_normal_string(" is ambiguous. Try ");
3177 gms_standout_string("glk help");
3178 gms_normal_string(" for more information.\n");
3179 return;
3180 }
3181 matched = entry;
3182 }
3183 }
3184 if (!matched) {
3185 gms_normal_string("The Glk command ");
3186 gms_standout_string(command);
3187 gms_normal_string(" is not valid. Try ");
3188 gms_standout_string("glk help");
3189 gms_normal_string(" for more information.\n");
3190 return;
3191 }
3192
3193 if (matched->handler == &Magnetic::gms_command_summary) {
3194 gms_normal_string("Prints a summary of all the current Glk Magnetic"
3195 " settings.\n");
3196 }
3197
3198 else if (matched->handler == &Magnetic::gms_command_undo) {
3199 gms_normal_string("Undoes a single game turn.\n\nEquivalent to the"
3200 " standalone game 'undo' command.\n");
3201 }
3202
3203 else if (matched->handler == &Magnetic::gms_command_script) {
3204 gms_normal_string("Logs the game's output to a file.\n\nUse ");
3205 gms_standout_string("glk script on");
3206 gms_normal_string(" to begin logging game output, and ");
3207 gms_standout_string("glk script off");
3208 gms_normal_string(" to end it. Glk Magnetic will ask you for a file"
3209 " when you turn scripts on.\n");
3210 }
3211
3212 else if (matched->handler == &Magnetic::gms_command_inputlog) {
3213 gms_normal_string("Records the commands you type into a game.\n\nUse ");
3214 gms_standout_string("glk inputlog on");
3215 gms_normal_string(", to begin recording your commands, and ");
3216 gms_standout_string("glk inputlog off");
3217 gms_normal_string(" to turn off input logs. You can play back"
3218 " recorded commands into a game with the ");
3219 gms_standout_string("glk readlog");
3220 gms_normal_string(" command.\n");
3221 }
3222
3223 else if (matched->handler == &Magnetic::gms_command_readlog) {
3224 gms_normal_string("Plays back commands recorded with ");
3225 gms_standout_string("glk inputlog on");
3226 gms_normal_string(".\n\nUse ");
3227 gms_standout_string("glk readlog on");
3228 gms_normal_string(". Command play back stops at the end of the"
3229 " file. You can also play back commands from a"
3230 " text file created using any standard editor.\n");
3231 }
3232
3233 else if (matched->handler == &Magnetic::gms_command_abbreviations) {
3234 gms_normal_string("Controls abbreviation expansion.\n\nGlk Magnetic"
3235 " automatically expands several standard single"
3236 " letter abbreviations for you; for example, \"x\""
3237 " becomes \"examine\". Use ");
3238 gms_standout_string("glk abbreviations on");
3239 gms_normal_string(" to turn this feature on, and ");
3240 gms_standout_string("glk abbreviations off");
3241 gms_normal_string(" to turn it off. While the feature is on, you"
3242 " can bypass abbreviation expansion for an"
3243 " individual game command by prefixing it with a"
3244 " single quote.\n");
3245 }
3246
3247 else if (matched->handler == &Magnetic::gms_command_graphics) {
3248 gms_normal_string("Turns interpreter graphics on and off.\n\nUse ");
3249 gms_standout_string("glk graphics on");
3250 gms_normal_string(" to enable interpreter graphics, and ");
3251 gms_standout_string("glk graphics off");
3252 gms_normal_string(" to turn graphics off and close the graphics window."
3253 " This control works slightly differently to the"
3254 " 'graphics' command in Magnetic Windows and Magnetic"
3255 " Scrolls games themselves; the game's 'graphics'"
3256 " command may disable new images, but leave old ones"
3257 " displayed. For graphics to be displayed, they"
3258 " must be turned on in both the game and the"
3259 " interpreter.\n");
3260 }
3261
3262 else if (matched->handler == &Magnetic::gms_command_gamma) {
3263 gms_normal_string("Sets the level of automatic gamma correction applied"
3264 " to game graphics.\n\nUse ");
3265 gms_standout_string("glk gamma normal");
3266 gms_normal_string(" to set moderate automatic colour contrast"
3267 " correction, ");
3268 gms_standout_string("glk gamma high");
3269 gms_normal_string(" to set high automatic colour contrast correction,"
3270 " or ");
3271 gms_standout_string("glk gamma off");
3272 gms_normal_string(" to turn off all automatic gamma correction.\n");
3273 }
3274
3275 else if (matched->handler == &Magnetic::gms_command_animations) {
3276 gms_normal_string("Turns graphic animations on and off.\n\nUse ");
3277 gms_standout_string("glk animation on");
3278 gms_normal_string(" to enable animations, or ");
3279 gms_standout_string("glk animation off");
3280 gms_normal_string(" to turn animations off. Not all game graphics are"
3281 " animated, so this control works only on graphics"
3282 " that are animated. When animation is off, Glk"
3283 " Magnetic displays only the static portions of a"
3284 " game's pictures.\n");
3285 }
3286
3287 else if (matched->handler == &Magnetic::gms_command_prompts) {
3288 gms_normal_string("Controls extra input prompting.\n\n"
3289 "Glk Magnetic can issue a replacement '>' input"
3290 " prompt if it detects that the game hasn't prompted"
3291 " after, say, an empty input line. Use ");
3292 gms_standout_string("glk prompts on");
3293 gms_normal_string(" to turn this feature on, and ");
3294 gms_standout_string("glk prompts off");
3295 gms_normal_string(" to turn it off.\n");
3296 }
3297
3298 else if (matched->handler == &Magnetic::gms_command_version) {
3299 gms_normal_string("Prints the version numbers of the Glk library"
3300 " and the Glk Magnetic port.\n");
3301 }
3302
3303 else if (matched->handler == &Magnetic::gms_command_commands) {
3304 gms_normal_string("Turn off Glk commands.\n\nUse ");
3305 gms_standout_string("glk commands off");
3306 gms_normal_string(" to disable all Glk commands, including this one."
3307 " Once turned off, there is no way to turn Glk"
3308 " commands back on while inside the game.\n");
3309 }
3310
3311 else if (matched->handler == &Magnetic::gms_command_help)
3312 gms_command_help("");
3313
3314 else
3315 gms_normal_string("There is no help available on that Glk command."
3316 " Sorry.\n");
3317 }
3318
gms_command_escape(const char * string_,int * undo_command)3319 int Magnetic::gms_command_escape(const char *string_, int *undo_command) {
3320 int posn;
3321 char *string_copy, *command, *argument;
3322 assert(string_ && undo_command);
3323
3324 /*
3325 * Return false if the string doesn't begin with the Glk command escape
3326 * introducer.
3327 */
3328 posn = strspn(string_, "\t ");
3329 if (gms_strncasecmp(string_ + posn, "glk", strlen("glk")) != 0)
3330 return false;
3331
3332 /* Take a copy of the string_, without any leading space or introducer. */
3333 string_copy = (char *)gms_malloc(strlen(string_ + posn) + 1 - strlen("glk"));
3334 strcpy(string_copy, string_ + posn + strlen("glk"));
3335
3336 /*
3337 * Find the subcommand; the first word in the string copy. Find its end,
3338 * and ensure it terminates with a NUL.
3339 */
3340 posn = strspn(string_copy, "\t ");
3341 command = string_copy + posn;
3342 posn += strcspn(string_copy + posn, "\t ");
3343 if (string_copy[posn] != '\0')
3344 string_copy[posn++] = '\0';
3345
3346 /*
3347 * Now find any argument data for the command, ensuring it too terminates
3348 * with a NUL.
3349 */
3350 posn += strspn(string_copy + posn, "\t ");
3351 argument = string_copy + posn;
3352 posn += strcspn(string_copy + posn, "\t ");
3353 string_copy[posn] = '\0';
3354
3355 /*
3356 * Try to handle the command and argument as a Glk subcommand. If it
3357 * doesn't run unambiguously, print command usage. Treat an empty command
3358 * as "help".
3359 */
3360 if (strlen(command) > 0) {
3361 const gms_command_t *entry, *matched;
3362 int matches;
3363
3364 /*
3365 * Search for the first unambiguous table command string_ matching
3366 * the command passed in.
3367 */
3368 matches = 0;
3369 matched = NULL;
3370 for (entry = GMS_COMMAND_TABLE; entry->command; entry++) {
3371 if (gms_strncasecmp(command, entry->command, strlen(command)) == 0) {
3372 matches++;
3373 matched = entry;
3374 }
3375 }
3376
3377 /* If the match was unambiguous, call the command handler. */
3378 if (matches == 1) {
3379 if (!matched->undo_return)
3380 gms_normal_char('\n');
3381 (this->*(matched->handler))(argument);
3382
3383 if (!matched->takes_argument && strlen(argument) > 0) {
3384 gms_normal_string("[The ");
3385 gms_standout_string(matched->command);
3386 gms_normal_string(" command ignores arguments.]\n");
3387 }
3388
3389 *undo_command = matched->undo_return;
3390 }
3391
3392 /* No match, or the command was ambiguous. */
3393 else {
3394 gms_normal_string("\nThe Glk command ");
3395 gms_standout_string(command);
3396 gms_normal_string(" is ");
3397 gms_normal_string(matches == 0 ? "not valid" : "ambiguous");
3398 gms_normal_string(". Try ");
3399 gms_standout_string("glk help");
3400 gms_normal_string(" for more information.\n");
3401 }
3402 } else {
3403 gms_normal_char('\n');
3404 gms_command_help("");
3405 }
3406
3407 /* The string_ contained a Glk command; return true. */
3408 free(string_copy);
3409 return true;
3410 }
3411
gms_command_undo_special(const char * string_)3412 int Magnetic::gms_command_undo_special(const char *string_) {
3413 int posn, end;
3414 assert(string_);
3415
3416 /* Find the start and end of the first string_ word. */
3417 posn = strspn(string_, "\t ");
3418 end = posn + strcspn(string_ + posn, "\t ");
3419
3420 /* See if string_ contains an "undo" request, with nothing following. */
3421 if (end - posn == (int)strlen("undo")
3422 && gms_strncasecmp(string_ + posn, "undo", end - posn) == 0) {
3423 posn = end + strspn(string_ + end, "\t ");
3424 if (string_[posn] == '\0')
3425 return true;
3426 }
3427
3428 return false;
3429 }
3430
3431 /*---------------------------------------------------------------------*/
3432 /* Glk port input functions */
3433 /*---------------------------------------------------------------------*/
3434
gms_expand_abbreviations(char * buffer_,int size)3435 void Magnetic::gms_expand_abbreviations(char *buffer_, int size) {
3436 char *command, abbreviation;
3437 const char *expansion;
3438 gms_abbreviationref_t entry;
3439 assert(buffer_);
3440
3441 /* Ignore anything that isn't a single letter command. */
3442 command = buffer_ + strspn(buffer_, "\t ");
3443 if (!(strlen(command) == 1
3444 || (strlen(command) > 1 && Common::isSpace(command[1]))))
3445 return;
3446
3447 /* Scan the abbreviations table for a match. */
3448 abbreviation = glk_char_to_lower((unsigned char) command[0]);
3449 expansion = NULL;
3450 for (entry = GMS_ABBREVIATIONS; entry->expansion; entry++) {
3451 if (entry->abbreviation == abbreviation) {
3452 expansion = entry->expansion;
3453 break;
3454 }
3455 }
3456
3457 /*
3458 * If a match found, check for a fit, then replace the character with the
3459 * expansion string.
3460 */
3461 if (expansion) {
3462 if ((int)(strlen(buffer_) + strlen(expansion)) - 1 >= size)
3463 return;
3464
3465 memmove(command + strlen(expansion) - 1, command, strlen(command) + 1);
3466 memcpy(command, expansion, strlen(expansion));
3467
3468 #if 0
3469 gms_standout_string("[");
3470 gms_standout_char(abbreviation);
3471 gms_standout_string(" -> ");
3472 gms_standout_string(expansion);
3473 gms_standout_string("]\n");
3474 #endif
3475 }
3476 }
3477
gms_buffer_input()3478 void Magnetic::gms_buffer_input() {
3479 event_t event;
3480
3481 /*
3482 * Update the current status line display, and flush any pending buffered
3483 * output.
3484 */
3485 gms_status_notify();
3486 gms_output_flush();
3487
3488 /*
3489 * Magnetic Windows games tend not to issue a prompt after reading an empty
3490 * line of input. This can make for a very blank looking screen.
3491 *
3492 * To slightly improve things, if it looks like we didn't get a prompt from
3493 * the game, do our own.
3494 */
3495 if (gms_prompt_enabled && !gms_game_prompted()) {
3496 gms_normal_char('\n');
3497 gms_normal_string(GMS_INPUT_PROMPT);
3498 }
3499
3500 /*
3501 * If we have an input log to read from, use that until it is exhausted. On
3502 * end of file, close the stream and resume input from line requests.
3503 */
3504 if (gms_readlog_stream) {
3505 glui32 chars;
3506
3507 /* Get the next line from the log stream. */
3508 chars = glk_get_line_stream(gms_readlog_stream,
3509 gms_input_buffer, sizeof(gms_input_buffer));
3510 if (chars > 0) {
3511 /* Echo the line just read in input style. */
3512 glk_set_style(style_Input);
3513 glk_put_buffer(gms_input_buffer, chars);
3514 glk_set_style(style_Normal);
3515
3516 /* Note how many characters buffered, and return. */
3517 gms_input_length = chars;
3518 return;
3519 }
3520
3521 /*
3522 * We're at the end of the log stream. Close it, and then continue
3523 * on to request a line from Glk.
3524 */
3525 glk_stream_close(gms_readlog_stream, NULL);
3526 gms_readlog_stream = NULL;
3527 }
3528
3529 /*
3530 * No input log being read, or we just hit the end of file on one. Revert
3531 * to normal line input; start by getting a new line from Glk.
3532 */
3533 glk_request_line_event(gms_main_window,
3534 gms_input_buffer, sizeof(gms_input_buffer) - 1, 0);
3535 gms_event_wait(evtype_LineInput, &event);
3536 if (shouldQuit()) {
3537 glk_cancel_line_event(gms_main_window, &event);
3538 return;
3539 }
3540
3541 /* Terminate the input line with a NUL. */
3542 assert(event.val1 <= sizeof(gms_input_buffer) - 1);
3543 gms_input_buffer[event.val1] = '\0';
3544
3545 /* Special handling for "undo" commands. */
3546 if (gms_command_undo_special(gms_input_buffer)) {
3547 /* Write the "undo" to any input log. */
3548 if (gms_inputlog_stream) {
3549 glk_put_string_stream(gms_inputlog_stream, gms_input_buffer);
3550 glk_put_char_stream(gms_inputlog_stream, '\n');
3551 }
3552
3553 /* Overwrite buffer with an empty line if we saw "undo". */
3554 gms_input_buffer[0] = '\n';
3555 gms_input_length = 1;
3556
3557 gms_undo_notification = true;
3558 return;
3559 }
3560
3561 /*
3562 * If neither abbreviations nor local commands are enabled, use the data
3563 * read above without further massaging.
3564 */
3565 if (gms_abbreviations_enabled || gms_commands_enabled) {
3566 char *command;
3567
3568 /*
3569 * If the first non-space input character is a quote, bypass all
3570 * abbreviation expansion and local command recognition, and use the
3571 * unadulterated input, less introductory quote.
3572 */
3573 command = gms_input_buffer + strspn(gms_input_buffer, "\t ");
3574 if (command[0] == '\'') {
3575 /* Delete the quote with memmove(). */
3576 memmove(command, command + 1, strlen(command));
3577 } else {
3578 /* Check for, and expand, any abbreviated commands. */
3579 if (gms_abbreviations_enabled) {
3580 gms_expand_abbreviations(gms_input_buffer,
3581 sizeof(gms_input_buffer));
3582 }
3583
3584 /*
3585 * Check for standalone "help", then for Glk port special commands;
3586 * suppress the interpreter's use of this input for Glk commands
3587 * by overwriting the line with a single newline character.
3588 */
3589 if (gms_commands_enabled) {
3590 int posn;
3591
3592 posn = strspn(gms_input_buffer, "\t ");
3593 if (gms_strncasecmp(gms_input_buffer + posn,
3594 "help", strlen("help")) == 0) {
3595 if (strspn(gms_input_buffer + posn + strlen("help"), "\t ")
3596 == strlen(gms_input_buffer + posn + strlen("help"))) {
3597 gms_output_register_help_request();
3598 }
3599 }
3600
3601 if (gms_command_escape(gms_input_buffer,
3602 &gms_undo_notification)) {
3603 gms_output_silence_help_hints();
3604 gms_input_buffer[0] = '\n';
3605 gms_input_length = 1;
3606 return;
3607 }
3608 }
3609 }
3610 }
3611
3612 /*
3613 * If there is an input log active, log this input string to it. Note that
3614 * by logging here we get any abbreviation expansions but we won't log glk
3615 * special commands, nor any input read from a current open input log.
3616 */
3617 if (gms_inputlog_stream) {
3618 glk_put_string_stream(gms_inputlog_stream, gms_input_buffer);
3619 glk_put_char_stream(gms_inputlog_stream, '\n');
3620 }
3621
3622 /*
3623 * Now append a newline to the buffer, since Glk line input doesn't provide
3624 * one, and in any case, abbreviation expansion may have edited the buffer
3625 * contents (and in particular, changed the length).
3626 */
3627 gms_input_buffer[strlen(gms_input_buffer) + 1] = '\0';
3628 gms_input_buffer[strlen(gms_input_buffer)] = '\n';
3629
3630 /* Note how many characters are buffered after all of the above. */
3631 gms_input_length = strlen(gms_input_buffer);
3632 }
3633
ms_getchar(type8 trans)3634 type8 Magnetic::ms_getchar(type8 trans) {
3635 /* See if we are at the end of the input buffer. */
3636 if (gms_input_cursor == gms_input_length) {
3637 /*
3638 * Try to read in more data, and rewind buffer cursor. As well as
3639 * reading input, this may set an undo notification.
3640 */
3641 gms_buffer_input();
3642 gms_input_cursor = 0;
3643
3644 if (shouldQuit())
3645 return '\0';
3646
3647 if (gms_undo_notification) {
3648 /*
3649 * Clear the undo notification, and discard buffered input (usually
3650 * just the '\n' placed there when the undo command was recognized).
3651 */
3652 gms_undo_notification = false;
3653 gms_input_length = 0;
3654
3655 /*
3656 * Return the special 0, or a blank line if no undo is allowed at
3657 * this point.
3658 */
3659 return trans ? 0 : '\n';
3660 }
3661 }
3662
3663 /* Return the next character from the input buffer. */
3664 assert(gms_input_cursor < gms_input_length);
3665 return gms_input_buffer[gms_input_cursor++];
3666 }
3667
3668 /*---------------------------------------------------------------------*/
3669 /* Glk port event functions */
3670 /*---------------------------------------------------------------------*/
3671
gms_event_wait(glui32 wait_type,event_t * event)3672 void Magnetic::gms_event_wait(glui32 wait_type, event_t *event) {
3673 assert(event);
3674
3675 do {
3676 glk_select(event);
3677
3678 switch (event->type) {
3679 case evtype_Arrange:
3680 case evtype_Redraw:
3681 /* Refresh any sensitive windows on size events. */
3682 gms_status_redraw();
3683 gms_hint_redraw();
3684 gms_graphics_paint();
3685 break;
3686
3687 case evtype_Timer:
3688 /* Do background graphics updates on timeout. */
3689 gms_graphics_timeout();
3690 break;
3691
3692 case evtype_Quit:
3693 return;
3694
3695 default:
3696 break;
3697 }
3698 } while (event->type != (EvType)wait_type);
3699 }
3700
3701 /*---------------------------------------------------------------------*/
3702 /* Functions intercepted by link-time wrappers */
3703 /*---------------------------------------------------------------------*/
3704
__wrap_toupper(int ch)3705 int Magnetic::__wrap_toupper(int ch) {
3706 unsigned char uch;
3707
3708 uch = glk_char_to_upper((unsigned char) ch);
3709 return (int) uch;
3710 }
3711
__wrap_tolower(int ch)3712 int Magnetic::__wrap_tolower(int ch) {
3713 unsigned char lch;
3714
3715 lch = glk_char_to_lower((unsigned char) ch);
3716 return (int) lch;
3717 }
3718
3719 /*---------------------------------------------------------------------*/
3720 /* main() and options parsing */
3721 /*---------------------------------------------------------------------*/
3722
gms_establish_filenames(const char * name,char ** text,char ** graphics,char ** hints_)3723 void Magnetic::gms_establish_filenames(const char *name, char **text, char **graphics, char **hints_) {
3724 char *base, *text_file, *graphics_file, *hints_file;
3725 Common::File stream;
3726 assert(name && text && graphics && hints_);
3727
3728 /* Take a destroyable copy of the input filename. */
3729 base = (char *)gms_malloc(strlen(name) + 1);
3730 strcpy(base, name);
3731
3732 /* If base has an extension .MAG, .GFX, or .HNT, remove it. */
3733 if (strlen(base) > strlen(".XXX")) {
3734 if (gms_strcasecmp(base + strlen(base) - strlen(".MAG"), ".MAG") == 0
3735 || gms_strcasecmp(base + strlen(base) - strlen(".GFX"), ".GFX") == 0
3736 || gms_strcasecmp(base + strlen(base) - strlen(".HNT"), ".HNT") == 0)
3737 base[strlen(base) - strlen(".XXX")] = '\0';
3738 }
3739
3740 /* Allocate space for the return text file. */
3741 text_file = (char *)gms_malloc(strlen(base) + strlen(".MAG") + 1);
3742
3743 /* Form a candidate text file, by adding a .MAG extension. */
3744 strcpy(text_file, base);
3745 strcat(text_file, ".MAG");
3746
3747 if (!stream.open(text_file)) {
3748 /* Retry, using a .mag extension instead. */
3749 strcpy(text_file, base);
3750 strcat(text_file, ".mag");
3751
3752 if (!stream.open(text_file)) {
3753 /*
3754 * No access to a usable game text file. Return immediately,
3755 * without looking for any associated graphics or hints_ files.
3756 */
3757 *text = NULL;
3758 *graphics = NULL;
3759 *hints_ = NULL;
3760
3761 free(text_file);
3762 free(base);
3763 return;
3764 }
3765 }
3766 stream.close();
3767
3768 /* Now allocate space for the return graphics file. */
3769 graphics_file = (char *)gms_malloc(strlen(base) + strlen(".GFX") + 1);
3770
3771 /* As above, form a candidate graphics file, using a .GFX extension. */
3772 strcpy(graphics_file, base);
3773 strcat(graphics_file, ".GFX");
3774
3775 if (!stream.open(graphics_file)) {
3776 /* Retry, using a .gfx extension instead. */
3777 strcpy(graphics_file, base);
3778 strcat(graphics_file, ".gfx");
3779
3780 if (!stream.open(graphics_file)) {
3781 /*
3782 * No access to any graphics file. In this case, free memory and
3783 * reset graphics file to NULL.
3784 */
3785 free(graphics_file);
3786 graphics_file = NULL;
3787 }
3788 }
3789 stream.close();
3790
3791 /* Now allocate space for the return hints_ file. */
3792 hints_file = (char *)gms_malloc(strlen(base) + strlen(".HNT") + 1);
3793
3794 /* As above, form a candidate graphics file, using a .HNT extension. */
3795 strcpy(hints_file, base);
3796 strcat(hints_file, ".HNT");
3797
3798 if (!stream.open(hints_file)) {
3799 /* Retry, using a .hnt extension instead. */
3800 strcpy(hints_file, base);
3801 strcat(hints_file, ".hnt");
3802
3803 if (!stream.open(hints_file)) {
3804 /*
3805 * No access to any hints_ file. In this case, free memory and
3806 * reset hints_ file to NULL.
3807 */
3808 free(hints_file);
3809 hints_file = NULL;
3810 }
3811 }
3812 stream.close();
3813
3814 /* Return the text file, and graphics and hints_, which may be NULL. */
3815 *text = text_file;
3816 *graphics = graphics_file;
3817 *hints_ = hints_file;
3818
3819 free(base);
3820 }
3821
gms_main()3822 void Magnetic::gms_main() {
3823 char *text_file = NULL, *graphics_file = NULL, *hints_file = NULL;
3824 int ms_init_status, is_running;
3825
3826 /* Create the main Glk window, and set its stream as current. */
3827 gms_main_window = glk_window_open(0, 0, 0, wintype_TextBuffer, 0);
3828 if (!gms_main_window) {
3829 gms_fatal("GLK: Can't open main window");
3830 glk_exit();
3831 return;
3832 }
3833 glk_window_clear(gms_main_window);
3834 glk_set_window(gms_main_window);
3835 glk_set_style(style_Normal);
3836
3837 /*
3838 * Given the basic game name, try to come up with usable text, graphics,
3839 * and hints filenames. The graphics and hints files may be null, but the
3840 * text file may not.
3841 */
3842 Common::String gameFile = getFilename();
3843 gms_establish_filenames(gameFile.c_str(), &text_file, &graphics_file, &hints_file);
3844
3845 /* Set the possibility of pictures depending on graphics file. */
3846 if (graphics_file) {
3847 /*
3848 * Check Glk library capabilities, and note pictures are impossible if
3849 * the library can't offer both graphics and timers. We need timers to
3850 * create the background "thread" for picture updates.
3851 */
3852 gms_graphics_possible = glk_gestalt(gestalt_Graphics, 0)
3853 && glk_gestalt(gestalt_Timer, 0);
3854 } else
3855 gms_graphics_possible = false;
3856
3857
3858 /*
3859 * If pictures are impossible, clear pictures enabled flag. That is, act
3860 * as if -np was given on the command line, even though it may not have
3861 * been. If pictures are impossible, they can never be enabled.
3862 */
3863 if (!gms_graphics_possible)
3864 gms_graphics_enabled = false;
3865
3866 /* Try to create a one-line status window. We can live without it. */
3867 glk_stylehint_set(wintype_TextGrid, style_User1, stylehint_ReverseColor, 1);
3868 gms_status_window = glk_window_open(gms_main_window,
3869 winmethod_Above | winmethod_Fixed,
3870 1, wintype_TextGrid, 0);
3871
3872 /*
3873 * Load the game. If no graphics are possible, then passing the NULL to
3874 * ms_init() runs a game without graphics.
3875 */
3876 if (gms_graphics_possible) {
3877 assert(graphics_file);
3878 ms_init_status = ms_init(text_file, graphics_file, hints_file, NULL);
3879 } else
3880 ms_init_status = ms_init(text_file, NULL, hints_file, NULL);
3881
3882 /* Look for a complete failure to load the game. */
3883 if (ms_init_status == 0) {
3884 if (gms_status_window)
3885 glk_window_close(gms_status_window, NULL);
3886 gms_header_string("Glk Magnetic Error\n\n");
3887 gms_normal_string("Can't load game '");
3888 gms_normal_string(gameFile.c_str());
3889 gms_normal_char('\'');
3890
3891 gms_normal_char('\n');
3892
3893 /*
3894 * Free the text file path, any graphics/hints file path, and
3895 * interpreter allocated memory.
3896 */
3897 free(text_file);
3898 free(graphics_file);
3899 free(hints_file);
3900 ms_freemem();
3901 glk_exit();
3902 }
3903
3904 /* Try to identify the game from its text file header. */
3905 gms_gameid_identify_game(text_file);
3906
3907 /* Look for failure to load just game graphics. */
3908 if (gms_graphics_possible && ms_init_status == 1) {
3909 /*
3910 * Output a warning if graphics failed, but the main game text
3911 * initialized okay.
3912 */
3913 gms_standout_string("Error: Unable to open graphics file\n"
3914 "Continuing without pictures...\n\n");
3915
3916 gms_graphics_possible = false;
3917 }
3918
3919 /* Run the game opcodes -- ms_rungame() returns false on game end. */
3920 do {
3921 is_running = ms_rungame() && !shouldQuit();
3922 glk_tick();
3923 } while (is_running);
3924
3925 /* Handle any updated status and pending buffered output. */
3926 gms_status_notify();
3927 gms_output_flush();
3928
3929 /* Turn off any background graphics "thread". */
3930 gms_graphics_stop();
3931
3932 /* Free interpreter allocated memory. */
3933 ms_freemem();
3934
3935 /*
3936 * Free any temporary memory that may have been used by graphics and hints.
3937 */
3938 gms_graphics_cleanup();
3939 gms_hints_cleanup();
3940
3941 /* Close any open transcript, input log, and/or read log. */
3942 if (gms_transcript_stream) {
3943 glk_stream_close(gms_transcript_stream, NULL);
3944 gms_transcript_stream = NULL;
3945 }
3946 if (gms_inputlog_stream) {
3947 glk_stream_close(gms_inputlog_stream, NULL);
3948 gms_inputlog_stream = NULL;
3949 }
3950 if (gms_readlog_stream) {
3951 glk_stream_close(gms_readlog_stream, NULL);
3952 gms_readlog_stream = NULL;
3953 }
3954
3955 /* Free the text file path, and any graphics/hints file path. */
3956 free(text_file);
3957 free(graphics_file);
3958 free(hints_file);
3959 }
3960
3961 /*---------------------------------------------------------------------*/
3962 /* Linkage between Glk entry/exit calls and the Magnetic interpreter */
3963 /*---------------------------------------------------------------------*/
3964
glk_main()3965 void Magnetic::glk_main() {
3966 assert(gms_startup_called && !gms_main_called);
3967 gms_main_called = true;
3968
3969 /* Call the interpreter main function. */
3970 gms_main();
3971 }
3972
write(const char * fmt,...)3973 void Magnetic::write(const char *fmt, ...) {
3974 va_list ap;
3975 va_start(ap, fmt);
3976 Common::String s = Common::String::format(fmt, ap);
3977 va_end(ap);
3978 glk_put_buffer(s.c_str(), s.size());
3979 }
3980
writeChar(char c)3981 void Magnetic::writeChar(char c) {
3982 glk_put_char(c);
3983 }
3984
script_write(type8 c)3985 void Magnetic::script_write(type8 c) {
3986 if (log_on == 2) {
3987 if (_log1) {
3988 _log1->writeByte(c);
3989 }
3990 }
3991 }
3992
transcript_write(type8 c)3993 void Magnetic::transcript_write(type8 c) {
3994 if (_log2) {
3995 _log2->writeByte(c);
3996 }
3997 }
3998
3999 } // End of namespace Magnetic
4000 } // End of namespace Glk
4001