Implemented garglk_set_program_name(), garglk_set_program_info(), garglk_set_story_name()
[rodin/chimara.git] / interpreters / frotz / glkmisc.c
1 /******************************************************************************
2  *                                                                            *
3  * Copyright (C) 2006-2009 by Tor Andersson.                                  *
4  *                                                                            *
5  * This file is part of Gargoyle.                                             *
6  *                                                                            *
7  * Gargoyle is free software; you can redistribute it and/or modify           *
8  * it under the terms of the GNU General Public License as published by       *
9  * the Free Software Foundation; either version 2 of the License, or          *
10  * (at your option) any later version.                                        *
11  *                                                                            *
12  * Gargoyle 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 Gargoyle; if not, write to the Free Software                    *
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA *
20  *                                                                            *
21  *****************************************************************************/
22
23 /* glkstuff.c -- non-screen related glk stuff */
24
25 #include "glkfrotz.h"
26
27 #define VERSION "2.43"
28
29 f_setup_t f_setup;
30
31 int curr_status_ht = 0;
32 int mach_status_ht = 0;
33
34 winid_t gos_upper = NULL;
35 winid_t gos_lower = NULL;
36 winid_t gos_curwin = NULL;
37
38 int gos_linepending = 0;
39 char *gos_linebuf = NULL;
40 winid_t gos_linewin = NULL;
41
42 schanid_t gos_channel = NULL;
43
44 #define INFORMATION ""\
45 "An interpreter for Infocom and other Z-Machine games.\n"\
46 "Complies with standard 1.0 of Graham Nelson's specification.\n"\
47 "Plays Z-code versions 1-5 and 8.\n"\
48 "\n"\
49 "Syntax: frotz [options] story-file\n"\
50 "    -a   watch attribute setting\n"\
51 "    -A   watch attribute testing\n"\
52 "    -i   ignore fatal errors\n"\
53 "    -I # interpreter number\n"\
54 "    -o   watch object movement\n"\
55 "    -O   watch object locating\n"\
56 "    -P   alter piracy opcode\n"\
57 "    -Q   use old-style save format\n"\
58 "    -s # random number seed value\n"\
59 "    -S # transscript width\n"\
60 "    -t   set Tandy bit\n"\
61 "    -u # slots for multiple undo\n"\
62 "    -x   expand abbreviations g/x/z\n"
63
64 /* A unix-like getopt, but with the names changed to avoid any problems.  */
65 static int zoptind = 1;
66 static int zoptopt = 0;
67 static char *zoptarg = NULL;
68 static int zgetopt (int argc, char *argv[], const char *options)
69 {
70         static int pos = 1;
71         const char *p;
72         if (zoptind >= argc || argv[zoptind][0] != '-' || argv[zoptind][1] == 0)
73                 return EOF;
74         zoptopt = argv[zoptind][pos++];
75         zoptarg = NULL;
76         if (argv[zoptind][pos] == 0)
77         {
78                 pos = 1;
79                 zoptind++;
80         }
81         p = strchr (options, zoptopt);
82         if (zoptopt == ':' || p == NULL)
83         {
84                 fputs ("illegal option -- ", stderr);
85                 goto error;
86         }
87         else if (p[1] == ':')
88         {
89                 if (zoptind >= argc) {
90                         fputs ("option requires an argument -- ", stderr);
91                         goto error;
92                 } else {
93                         zoptarg = argv[zoptind];
94                         if (pos != 1)
95                                 zoptarg += pos;
96                         pos = 1; zoptind++;
97                 }
98         }
99         return zoptopt;
100 error:
101         fputc (zoptopt, stderr);
102         fputc ('\n', stderr);
103         return '?';
104 }
105
106 static int user_random_seed = -1;
107 static int user_tandy_bit = 0;
108 static char *graphics_filename = NULL;
109
110 void os_process_arguments(int argc, char *argv[]) 
111 {
112         int c;
113
114 #ifdef GARGLK
115         garglk_set_program_name("Frotz " VERSION);
116         garglk_set_program_info(
117                         "Glk Frotz " VERSION "\n"
118                         "Original Frotz by Stefan Jokisch\n"
119                         "Unix port by Jim Dunleavy and David Griffith\n"
120                         "Glk port by Tor Andersson\n");
121 #endif
122
123         /* Parse the options */
124         do {
125                 c = zgetopt(argc, argv, "aAiI:oOPQs:S:tu:xZ:");
126                 switch (c)
127                 {
128                         case 'a': f_setup.attribute_assignment = 1; break;
129                         case 'A': f_setup.attribute_testing = 1; break;
130                         case 'i': f_setup.ignore_errors = 1; break;
131                         case 'I': f_setup.interpreter_number = atoi(zoptarg); break;
132                         case 'o': f_setup.object_movement = 1; break;
133                         case 'O': f_setup.object_locating = 1; break;
134                         case 'P': f_setup.piracy = 1; break;
135                         case 'Q': f_setup.save_quetzal = 0; break;
136                         case 's': user_random_seed = atoi(zoptarg); break;
137                         case 'S': f_setup.script_cols = atoi(zoptarg); break;
138                         case 't': user_tandy_bit = 1; break;
139                         case 'u': f_setup.undo_slots = atoi(zoptarg); break;
140                         case 'x': f_setup.expand_abbreviations = 1; break;
141                         case 'Z': f_setup.err_report_mode = atoi(zoptarg);
142                                           if ((f_setup.err_report_mode < ERR_REPORT_NEVER) ||
143                                                           (f_setup.err_report_mode > ERR_REPORT_FATAL))
144                                                   f_setup.err_report_mode = ERR_DEFAULT_REPORT_MODE;
145                                           break;
146                 }
147         } while (c != EOF);
148
149         if (((argc - zoptind) != 1) && ((argc - zoptind) != 2))
150         {
151                 winid_t win;
152                 char buf[256];
153                 win = glk_window_open(0, 0, 0, wintype_TextBuffer, 0);
154                 glk_set_window(win);
155                 glk_put_string("FROTZ V" VERSION " -- Glk 0.6.1 interface.\n");
156                 glk_put_string(INFORMATION);
157                 sprintf(buf,
158                                 "    -Z # error checking mode (default = %d)\n"
159                                 "         %d = don't report errors.  "
160                                 "%d = report first error.\n"
161                                 "         %d = report all errors.  "
162                                 "%d = exit after any error.\n",
163                                 ERR_DEFAULT_REPORT_MODE, ERR_REPORT_NEVER,
164                                 ERR_REPORT_ONCE, ERR_REPORT_ALWAYS, ERR_REPORT_FATAL);
165                 glk_put_string(buf);
166                 glk_exit();
167         }
168         else
169         {
170                 char *s;
171
172                 story_name = argv[zoptind++];
173                 if (zoptind < argc)
174                         graphics_filename = argv[zoptind++];
175
176                 #ifdef GARGLK
177                 s = strrchr(story_name, '\\');
178                 if (!s) s = strrchr(story_name, '/');
179                 garglk_set_story_name(s ? s + 1 : story_name);
180                 #endif
181         }
182 }
183
184 void os_init_screen(void)
185 {
186         glui32 width, height;
187
188         /*
189          * Init glk stuff
190          */
191
192         glk_stylehint_set(wintype_TextGrid, style_User1, stylehint_ReverseColor, 1);
193
194
195         gos_lower = glk_window_open(0, 0, 0, wintype_TextGrid, 0);
196         if (!gos_lower)
197                 gos_lower = glk_window_open(0, 0, 0, wintype_TextBuffer, 0);
198         glk_window_get_size(gos_lower, &width, &height);
199         glk_window_close(gos_lower, NULL);
200
201         gos_lower = glk_window_open(0, 0, 0, wintype_TextBuffer, 0);
202         gos_upper = glk_window_open(gos_lower,
203                         winmethod_Above | winmethod_Fixed,
204                         0,
205                         wintype_TextGrid, 0);
206
207         gos_channel = NULL;
208
209         glk_set_window(gos_lower);
210         gos_curwin = gos_lower;
211
212         /*
213          * Icky magic bit setting
214          */
215
216         if (h_version == V3 && user_tandy_bit)
217                 h_config |= CONFIG_TANDY;
218
219         if (h_version == V3 && gos_upper)
220                 h_config |= CONFIG_SPLITSCREEN;
221
222         if (h_version == V3 && !gos_upper)
223                 h_config |= CONFIG_NOSTATUSLINE;
224
225         if (h_version >= V4)
226                 h_config |= CONFIG_BOLDFACE | CONFIG_EMPHASIS |
227                         CONFIG_FIXED | CONFIG_TIMEDINPUT;
228
229         if (h_version >= V5)
230                 h_flags &= ~(GRAPHICS_FLAG | MOUSE_FLAG | MENU_FLAG);
231
232         if ((h_version >= 5) && (h_flags & SOUND_FLAG))
233                 h_flags |= SOUND_FLAG;
234
235         if ((h_version == 3) && (h_flags & OLD_SOUND_FLAG))
236                 h_flags |= OLD_SOUND_FLAG;
237
238         if ((h_version == 6) && (f_setup.sound != 0)) 
239                 h_config |= CONFIG_SOUND;
240
241         if (h_version >= V5 && (h_flags & UNDO_FLAG))
242                 if (f_setup.undo_slots == 0)
243                         h_flags &= ~UNDO_FLAG;
244
245         h_screen_cols = width;
246         h_screen_rows = height;
247
248         h_screen_height = h_screen_rows;
249         h_screen_width = h_screen_cols;
250
251         h_font_width = 1;
252         h_font_height = 1;
253
254         /* Must be after screen dimensions are computed.  */
255         if (h_version == V6) {
256                 h_flags &= ~GRAPHICS_FLAG;
257         }
258
259         /* Use the ms-dos interpreter number for v6, because that's the
260          * kind of graphics files we understand.  Otherwise, use DEC.  */
261         h_interpreter_number = h_version == 6 ? INTERP_MSDOS : INTERP_DEC_20;
262         if (f_setup.interpreter_number > 0)
263                 h_interpreter_number = f_setup.interpreter_number;
264         h_interpreter_version = 'F';
265         {
266                 /* Set these per spec 8.3.2. */
267                 h_default_foreground = WHITE_COLOUR;
268                 h_default_background = BLACK_COLOUR;
269                 if (h_flags & COLOUR_FLAG) h_flags &= ~COLOUR_FLAG;
270         }
271 }
272
273 int os_random_seed (void)
274 {
275     if (user_random_seed == -1)
276         /* Use the epoch as seed value */
277         return (time(0) & 0x7fff);
278     return user_random_seed;
279 }
280
281 void os_restart_game (int stage) {}
282
283 void os_fatal (char *s)
284 {
285         if (!gos_lower)
286                 gos_lower = glk_window_open(0, 0, 0, wintype_TextBuffer, 0);
287
288         glk_set_window(gos_lower);
289         glk_set_style(style_Normal);
290         glk_put_string("\n\nFatal error: ");
291         glk_put_string(s);
292         glk_put_string("\n");
293         glk_exit();
294 }
295
296 void os_init_setup(void)
297 {
298         f_setup.attribute_assignment = 0;
299         f_setup.attribute_testing = 0;
300         f_setup.context_lines = 0;
301         f_setup.object_locating = 0;
302         f_setup.object_movement = 0;
303         f_setup.left_margin = 0;
304         f_setup.right_margin = 0;
305         f_setup.ignore_errors = 0;
306         f_setup.interpreter_number = 0;
307         f_setup.piracy = 0;
308         f_setup.undo_slots = MAX_UNDO_SLOTS;
309         f_setup.expand_abbreviations = 0;
310         f_setup.script_cols = 80;
311         f_setup.save_quetzal = 1;
312         f_setup.sound = 1;
313         f_setup.err_report_mode = ERR_DEFAULT_REPORT_MODE;
314 }
315
316 void gos_cancel_pending_line(void)
317 {
318         event_t ev;
319         glk_cancel_line_event(gos_linewin, &ev);
320         gos_linebuf[ev.val1] = '\0';
321         gos_linepending = 0;
322 }
323
324 zchar os_read_key (int timeout, bool show_cursor)
325 {
326         event_t ev;
327         winid_t win = gos_curwin ? gos_curwin : gos_lower;
328
329         if (gos_linepending)
330                 gos_cancel_pending_line();
331
332         glk_request_char_event(win);
333         if (timeout != 0)
334                 glk_request_timer_events(timeout * 100);
335
336         while (1)
337         {
338                 glk_select(&ev);
339                 if (ev.type == evtype_Arrange) {
340                         gos_update_height();
341                         gos_update_width();
342                 }
343                 else if (ev.type == evtype_Timer)
344                 {
345                         glk_cancel_char_event(win);
346                         glk_request_timer_events(0);
347                         return ZC_TIME_OUT;
348                 }
349                 else if (ev.type == evtype_CharInput)
350                         break;
351         }
352
353         glk_request_timer_events(0);
354
355         if (gos_upper && mach_status_ht < curr_status_ht)
356                 reset_status_ht();
357         curr_status_ht = 0;
358
359         switch (ev.val1)
360         {
361                 case keycode_Escape: return ZC_ESCAPE;
362                 case keycode_PageUp: return ZC_ARROW_MIN;
363                 case keycode_PageDown: return ZC_ARROW_MAX;
364                 case keycode_Left: return ZC_ARROW_LEFT;
365                 case keycode_Right: return ZC_ARROW_RIGHT;
366                 case keycode_Up: return ZC_ARROW_UP;
367                 case keycode_Down: return ZC_ARROW_DOWN;
368                 case keycode_Return: return ZC_RETURN;
369                 case keycode_Delete: return ZC_BACKSPACE;
370                 case keycode_Tab: return ZC_INDENT;
371                 default:
372                         return ev.val1;
373         }
374 }
375
376 zchar os_read_line (int max, zchar *buf, int timeout, int width, int continued)
377 {
378         event_t ev;
379         winid_t win = gos_curwin ? gos_curwin : gos_lower;
380
381         if (!continued && gos_linepending)
382                 gos_cancel_pending_line(); 
383
384         if (!continued || !gos_linepending)
385         {
386                 glk_request_line_event(win, buf, max, strlen(buf));
387                 if (timeout != 0)
388                         glk_request_timer_events(timeout * 100);
389         }
390
391         gos_linepending = 0;
392
393         while (1)
394         {
395                 glk_select(&ev);
396                 if (ev.type == evtype_Arrange) {
397                         gos_update_height();
398                         gos_update_width();
399                 }
400                 else if (ev.type == evtype_Timer)
401                 {
402                         gos_linewin = win;
403                         gos_linepending = 1;
404                         gos_linebuf = buf;
405                         return ZC_TIME_OUT;
406                 }
407                 else if (ev.type == evtype_LineInput)
408                         break;
409         }
410
411         glk_request_timer_events(0);
412         buf[ev.val1] = '\0';
413
414         if (gos_upper && mach_status_ht < curr_status_ht)
415                 reset_status_ht();
416         curr_status_ht = 0;
417
418         return ZC_RETURN;
419 }
420
421 zword os_read_mouse(void)
422 {
423         /* NOT IMPLEMENTED */
424         return 0;
425 }
426
427 static glui32 flag2usage(int flag)
428 {
429         switch (flag)
430         {       
431                 case FILE_RESTORE:
432                         return fileusage_SavedGame | fileusage_BinaryMode;
433                 case FILE_SAVE:
434                         return fileusage_SavedGame | fileusage_BinaryMode;
435                 case FILE_SCRIPT:
436                         return fileusage_Transcript | fileusage_TextMode;
437                 case FILE_PLAYBACK:
438                         return fileusage_InputRecord | fileusage_TextMode;
439                 case FILE_RECORD:
440                         return fileusage_InputRecord | fileusage_TextMode;
441                 case FILE_LOAD_AUX:
442                         return fileusage_Data | fileusage_BinaryMode;
443                 case FILE_SAVE_AUX:
444                         return fileusage_Data | fileusage_BinaryMode;
445         }
446         return 0;
447 }
448
449 static glui32 flag2mode(int flag)
450 {
451         switch (flag)
452         {       
453         case FILE_RESTORE:
454                 return filemode_Read;
455         case FILE_SAVE:
456                 return filemode_Write;
457         case FILE_SCRIPT:
458                 return filemode_ReadWrite;      /* append really, but with erase option */
459         case FILE_PLAYBACK:
460                 return filemode_Read;
461         case FILE_RECORD:
462                 return filemode_Write;
463         case FILE_LOAD_AUX:
464                 return filemode_Read;
465         case FILE_SAVE_AUX:
466                 return filemode_Write;
467         }
468         return filemode_ReadWrite;
469 }
470
471 strid_t frotzopenprompt(int flag)
472 {
473         frefid_t fref;
474         strid_t stm;
475         glui32 gusage = flag2usage(flag);
476         glui32 gmode = flag2mode(flag);
477
478         fref = glk_fileref_create_by_prompt(gusage, gmode, 0);
479         if (fref == NULL)
480                 return NULL;
481
482         stm = glk_stream_open_file(fref, gmode, 0);
483
484         glk_fileref_destroy(fref);
485
486         return stm;
487 }
488
489 strid_t frotzopen(char *filename, int flag)
490 {
491         frefid_t fref;
492         strid_t stm;
493         glui32 gusage = flag2usage(flag);
494         glui32 gmode = flag2mode(flag);
495
496         fref = glk_fileref_create_by_name(gusage, filename, 0);
497         if (!fref)
498                 return NULL;
499
500         stm = glk_stream_open_file(fref, gmode, 0);
501
502         glk_fileref_destroy(fref);
503
504         return stm;
505 }
506