mirror of
https://github.com/ganelson/inform.git
synced 2024-07-16 22:14:23 +03:00
2178 lines
63 KiB
Plaintext
2178 lines
63 KiB
Plaintext
B/glut: Glulx Template.
|
|
|
|
@Purpose: To start up the Glk interface for the Glulx virtual machine, and
|
|
provide Glulx-specific printing functions.
|
|
|
|
@-------------------------------------------------------------------------------
|
|
|
|
@p Summary.
|
|
This segment closely parallels "ZMachine.i6t", which provides exactly
|
|
equivalent functionality (indeed, usually the same-named functions and in
|
|
the same order) for the Z-machine VM. This is intended to make the rest of the
|
|
template code independent of the choice of VM, although that is more of an
|
|
ideal than a reality, because there are so many fiddly differences in some
|
|
of the grammar and dictionary tables that it is not really practical for
|
|
the parser (for instance) to call VM-neutral routines to get the data it
|
|
wants out of these arrays.
|
|
|
|
@p Variables and Arrays.
|
|
|
|
@c
|
|
Array gg_event --> 4;
|
|
Array gg_arguments buffer 28;
|
|
Global gg_mainwin = 0;
|
|
Global gg_statuswin = 0;
|
|
Global gg_quotewin = 0;
|
|
Global gg_scriptfref = 0;
|
|
Global gg_scriptstr = 0;
|
|
Global gg_savestr = 0;
|
|
Global gg_commandstr = 0;
|
|
Global gg_command_reading = 0; ! true if gg_commandstr is being replayed
|
|
Global gg_foregroundchan = 0;
|
|
Global gg_backgroundchan = 0;
|
|
|
|
Constant GLK_NULL 0;
|
|
|
|
Constant INPUT_BUFFER_LEN = 260; ! No extra byte necessary
|
|
Constant MAX_BUFFER_WORDS = 20;
|
|
Constant PARSE_BUFFER_LEN = 61;
|
|
|
|
Array buffer buffer INPUT_BUFFER_LEN;
|
|
Array buffer2 buffer INPUT_BUFFER_LEN;
|
|
Array buffer3 buffer INPUT_BUFFER_LEN;
|
|
Array parse --> PARSE_BUFFER_LEN;
|
|
Array parse2 --> PARSE_BUFFER_LEN;
|
|
|
|
@p Infglk.
|
|
This section is a verbatim copy of an invaluable I6 header file originally
|
|
put together by John Cater but now maintained by Andrew Plotkin. The routines
|
|
are convenient to have on hand, and also provide a canonical set of I6 names
|
|
for the many gestalt and other codes.
|
|
|
|
@c
|
|
! infglk.h -- auto-generated by parse_dispatch.py.
|
|
! Generated for Glk API version 0.7.4
|
|
|
|
Constant evtype_Arrange = 5;
|
|
Constant evtype_CharInput = 2;
|
|
Constant evtype_Hyperlink = 8;
|
|
Constant evtype_LineInput = 3;
|
|
Constant evtype_MouseInput = 4;
|
|
Constant evtype_None = 0;
|
|
Constant evtype_Redraw = 6;
|
|
Constant evtype_SoundNotify = 7;
|
|
Constant evtype_Timer = 1;
|
|
Constant evtype_VolumeNotify = 9;
|
|
Constant filemode_Read = 2;
|
|
Constant filemode_ReadWrite = 3;
|
|
Constant filemode_Write = 1;
|
|
Constant filemode_WriteAppend = 5;
|
|
Constant fileusage_BinaryMode = 0;
|
|
Constant fileusage_Data = 0;
|
|
Constant fileusage_InputRecord = 3;
|
|
Constant fileusage_SavedGame = 1;
|
|
Constant fileusage_TextMode = 256;
|
|
Constant fileusage_Transcript = 2;
|
|
Constant fileusage_TypeMask = 15;
|
|
Constant gestalt_CharInput = 1;
|
|
Constant gestalt_CharOutput = 3;
|
|
Constant gestalt_CharOutput_ApproxPrint = 1;
|
|
Constant gestalt_CharOutput_CannotPrint = 0;
|
|
Constant gestalt_CharOutput_ExactPrint = 2;
|
|
Constant gestalt_DateTime = 20;
|
|
Constant gestalt_DrawImage = 7;
|
|
Constant gestalt_Graphics = 6;
|
|
Constant gestalt_GraphicsCharInput = 23;
|
|
Constant gestalt_GraphicsTransparency = 14;
|
|
Constant gestalt_HyperlinkInput = 12;
|
|
Constant gestalt_Hyperlinks = 11;
|
|
Constant gestalt_LineInput = 2;
|
|
Constant gestalt_LineInputEcho = 17;
|
|
Constant gestalt_LineTerminatorKey = 19;
|
|
Constant gestalt_LineTerminators = 18;
|
|
Constant gestalt_MouseInput = 4;
|
|
Constant gestalt_ResourceStream = 22;
|
|
Constant gestalt_Sound = 8;
|
|
Constant gestalt_Sound2 = 21;
|
|
Constant gestalt_SoundMusic = 13;
|
|
Constant gestalt_SoundNotify = 10;
|
|
Constant gestalt_SoundVolume = 9;
|
|
Constant gestalt_Timer = 5;
|
|
Constant gestalt_Unicode = 15;
|
|
Constant gestalt_UnicodeNorm = 16;
|
|
Constant gestalt_Version = 0;
|
|
Constant imagealign_InlineCenter = 3;
|
|
Constant imagealign_InlineDown = 2;
|
|
Constant imagealign_MarginLeft = 4;
|
|
Constant imagealign_MarginRight = 5;
|
|
Constant imagealign_InlineUp = 1;
|
|
Constant keycode_Delete = 4294967289;
|
|
Constant keycode_Down = 4294967291;
|
|
Constant keycode_End = 4294967283;
|
|
Constant keycode_Escape = 4294967288;
|
|
Constant keycode_Func1 = 4294967279;
|
|
Constant keycode_Func10 = 4294967270;
|
|
Constant keycode_Func11 = 4294967269;
|
|
Constant keycode_Func12 = 4294967268;
|
|
Constant keycode_Func2 = 4294967278;
|
|
Constant keycode_Func3 = 4294967277;
|
|
Constant keycode_Func4 = 4294967276;
|
|
Constant keycode_Func5 = 4294967275;
|
|
Constant keycode_Func6 = 4294967274;
|
|
Constant keycode_Func7 = 4294967273;
|
|
Constant keycode_Func8 = 4294967272;
|
|
Constant keycode_Func9 = 4294967271;
|
|
Constant keycode_Home = 4294967284;
|
|
Constant keycode_Left = 4294967294;
|
|
Constant keycode_MAXVAL = 28;
|
|
Constant keycode_PageDown = 4294967285;
|
|
Constant keycode_PageUp = 4294967286;
|
|
Constant keycode_Return = 4294967290;
|
|
Constant keycode_Right = 4294967293;
|
|
Constant keycode_Tab = 4294967287;
|
|
Constant keycode_Unknown = 4294967295;
|
|
Constant keycode_Up = 4294967292;
|
|
Constant seekmode_Current = 1;
|
|
Constant seekmode_End = 2;
|
|
Constant seekmode_Start = 0;
|
|
Constant style_Alert = 5;
|
|
Constant style_BlockQuote = 7;
|
|
Constant style_Emphasized = 1;
|
|
Constant style_Header = 3;
|
|
Constant style_Input = 8;
|
|
Constant style_NUMSTYLES = 11;
|
|
Constant style_Normal = 0;
|
|
Constant style_Note = 6;
|
|
Constant style_Preformatted = 2;
|
|
Constant style_Subheader = 4;
|
|
Constant style_User1 = 9;
|
|
Constant style_User2 = 10;
|
|
Constant stylehint_BackColor = 8;
|
|
Constant stylehint_Indentation = 0;
|
|
Constant stylehint_Justification = 2;
|
|
Constant stylehint_NUMHINTS = 10;
|
|
Constant stylehint_Oblique = 5;
|
|
Constant stylehint_ParaIndentation = 1;
|
|
Constant stylehint_Proportional = 6;
|
|
Constant stylehint_ReverseColor = 9;
|
|
Constant stylehint_Size = 3;
|
|
Constant stylehint_TextColor = 7;
|
|
Constant stylehint_Weight = 4;
|
|
Constant stylehint_just_Centered = 2;
|
|
Constant stylehint_just_LeftFlush = 0;
|
|
Constant stylehint_just_LeftRight = 1;
|
|
Constant stylehint_just_RightFlush = 3;
|
|
Constant winmethod_Above = 2;
|
|
Constant winmethod_Below = 3;
|
|
Constant winmethod_Border = 0;
|
|
Constant winmethod_BorderMask = 256;
|
|
Constant winmethod_DirMask = 15;
|
|
Constant winmethod_DivisionMask = 240;
|
|
Constant winmethod_Fixed = 16;
|
|
Constant winmethod_Left = 0;
|
|
Constant winmethod_NoBorder = 256;
|
|
Constant winmethod_Proportional = 32;
|
|
Constant winmethod_Right = 1;
|
|
Constant wintype_AllTypes = 0;
|
|
Constant wintype_Blank = 2;
|
|
Constant wintype_Graphics = 5;
|
|
Constant wintype_Pair = 1;
|
|
Constant wintype_TextBuffer = 3;
|
|
Constant wintype_TextGrid = 4;
|
|
|
|
[ glk_exit _vararg_count;
|
|
! glk_exit()
|
|
@glk 1 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_tick _vararg_count;
|
|
! glk_tick()
|
|
@glk 3 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_gestalt _vararg_count ret;
|
|
! glk_gestalt(uint, uint) => uint
|
|
@glk 4 _vararg_count ret;
|
|
return ret;
|
|
];
|
|
|
|
[ glk_gestalt_ext _vararg_count ret;
|
|
! glk_gestalt_ext(uint, uint, uintarray, arraylen) => uint
|
|
@glk 5 _vararg_count ret;
|
|
return ret;
|
|
];
|
|
|
|
[ glk_window_iterate _vararg_count ret;
|
|
! glk_window_iterate(window, &uint) => window
|
|
@glk 32 _vararg_count ret;
|
|
return ret;
|
|
];
|
|
|
|
[ glk_window_get_rock _vararg_count ret;
|
|
! glk_window_get_rock(window) => uint
|
|
@glk 33 _vararg_count ret;
|
|
return ret;
|
|
];
|
|
|
|
[ glk_window_get_root _vararg_count ret;
|
|
! glk_window_get_root() => window
|
|
@glk 34 _vararg_count ret;
|
|
return ret;
|
|
];
|
|
|
|
[ glk_window_open _vararg_count ret;
|
|
! glk_window_open(window, uint, uint, uint, uint) => window
|
|
@glk 35 _vararg_count ret;
|
|
return ret;
|
|
];
|
|
|
|
[ glk_window_close _vararg_count;
|
|
! glk_window_close(window, &{uint, uint})
|
|
@glk 36 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_window_get_size _vararg_count;
|
|
! glk_window_get_size(window, &uint, &uint)
|
|
@glk 37 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_window_set_arrangement _vararg_count;
|
|
! glk_window_set_arrangement(window, uint, uint, window)
|
|
@glk 38 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_window_get_arrangement _vararg_count;
|
|
! glk_window_get_arrangement(window, &uint, &uint, &window)
|
|
@glk 39 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_window_get_type _vararg_count ret;
|
|
! glk_window_get_type(window) => uint
|
|
@glk 40 _vararg_count ret;
|
|
return ret;
|
|
];
|
|
|
|
[ glk_window_get_parent _vararg_count ret;
|
|
! glk_window_get_parent(window) => window
|
|
@glk 41 _vararg_count ret;
|
|
return ret;
|
|
];
|
|
|
|
[ glk_window_clear _vararg_count;
|
|
! glk_window_clear(window)
|
|
@glk 42 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_window_move_cursor _vararg_count;
|
|
! glk_window_move_cursor(window, uint, uint)
|
|
@glk 43 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_window_get_stream _vararg_count ret;
|
|
! glk_window_get_stream(window) => stream
|
|
@glk 44 _vararg_count ret;
|
|
return ret;
|
|
];
|
|
|
|
[ glk_window_set_echo_stream _vararg_count;
|
|
! glk_window_set_echo_stream(window, stream)
|
|
@glk 45 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_window_get_echo_stream _vararg_count ret;
|
|
! glk_window_get_echo_stream(window) => stream
|
|
@glk 46 _vararg_count ret;
|
|
return ret;
|
|
];
|
|
|
|
[ glk_set_window _vararg_count;
|
|
! glk_set_window(window)
|
|
@glk 47 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_window_get_sibling _vararg_count ret;
|
|
! glk_window_get_sibling(window) => window
|
|
@glk 48 _vararg_count ret;
|
|
return ret;
|
|
];
|
|
|
|
[ glk_stream_iterate _vararg_count ret;
|
|
! glk_stream_iterate(stream, &uint) => stream
|
|
@glk 64 _vararg_count ret;
|
|
return ret;
|
|
];
|
|
|
|
[ glk_stream_get_rock _vararg_count ret;
|
|
! glk_stream_get_rock(stream) => uint
|
|
@glk 65 _vararg_count ret;
|
|
return ret;
|
|
];
|
|
|
|
[ glk_stream_open_file _vararg_count ret;
|
|
! glk_stream_open_file(fileref, uint, uint) => stream
|
|
@glk 66 _vararg_count ret;
|
|
return ret;
|
|
];
|
|
|
|
[ glk_stream_open_memory _vararg_count ret;
|
|
! glk_stream_open_memory(nativechararray, arraylen, uint, uint) => stream
|
|
@glk 67 _vararg_count ret;
|
|
return ret;
|
|
];
|
|
|
|
[ glk_stream_close _vararg_count;
|
|
! glk_stream_close(stream, &{uint, uint})
|
|
@glk 68 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_stream_set_position _vararg_count;
|
|
! glk_stream_set_position(stream, int, uint)
|
|
@glk 69 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_stream_get_position _vararg_count ret;
|
|
! glk_stream_get_position(stream) => uint
|
|
@glk 70 _vararg_count ret;
|
|
return ret;
|
|
];
|
|
|
|
[ glk_stream_set_current _vararg_count;
|
|
! glk_stream_set_current(stream)
|
|
@glk 71 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_stream_get_current _vararg_count ret;
|
|
! glk_stream_get_current() => stream
|
|
@glk 72 _vararg_count ret;
|
|
return ret;
|
|
];
|
|
|
|
[ glk_stream_open_resource _vararg_count ret;
|
|
! glk_stream_open_resource(uint, uint) => stream
|
|
@glk 73 _vararg_count ret;
|
|
return ret;
|
|
];
|
|
|
|
[ glk_fileref_create_temp _vararg_count ret;
|
|
! glk_fileref_create_temp(uint, uint) => fileref
|
|
@glk 96 _vararg_count ret;
|
|
return ret;
|
|
];
|
|
|
|
[ glk_fileref_create_by_name _vararg_count ret;
|
|
! glk_fileref_create_by_name(uint, string, uint) => fileref
|
|
@glk 97 _vararg_count ret;
|
|
return ret;
|
|
];
|
|
|
|
[ glk_fileref_create_by_prompt _vararg_count ret;
|
|
! glk_fileref_create_by_prompt(uint, uint, uint) => fileref
|
|
@glk 98 _vararg_count ret;
|
|
return ret;
|
|
];
|
|
|
|
[ glk_fileref_destroy _vararg_count;
|
|
! glk_fileref_destroy(fileref)
|
|
@glk 99 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_fileref_iterate _vararg_count ret;
|
|
! glk_fileref_iterate(fileref, &uint) => fileref
|
|
@glk 100 _vararg_count ret;
|
|
return ret;
|
|
];
|
|
|
|
[ glk_fileref_get_rock _vararg_count ret;
|
|
! glk_fileref_get_rock(fileref) => uint
|
|
@glk 101 _vararg_count ret;
|
|
return ret;
|
|
];
|
|
|
|
[ glk_fileref_delete_file _vararg_count;
|
|
! glk_fileref_delete_file(fileref)
|
|
@glk 102 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_fileref_does_file_exist _vararg_count ret;
|
|
! glk_fileref_does_file_exist(fileref) => uint
|
|
@glk 103 _vararg_count ret;
|
|
return ret;
|
|
];
|
|
|
|
[ glk_fileref_create_from_fileref _vararg_count ret;
|
|
! glk_fileref_create_from_fileref(uint, fileref, uint) => fileref
|
|
@glk 104 _vararg_count ret;
|
|
return ret;
|
|
];
|
|
|
|
[ glk_put_char _vararg_count;
|
|
! glk_put_char(uchar)
|
|
@glk 128 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_put_char_stream _vararg_count;
|
|
! glk_put_char_stream(stream, uchar)
|
|
@glk 129 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_put_string _vararg_count;
|
|
! glk_put_string(string)
|
|
@glk 130 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_put_string_stream _vararg_count;
|
|
! glk_put_string_stream(stream, string)
|
|
@glk 131 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_put_buffer _vararg_count;
|
|
! glk_put_buffer(nativechararray, arraylen)
|
|
@glk 132 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_put_buffer_stream _vararg_count;
|
|
! glk_put_buffer_stream(stream, nativechararray, arraylen)
|
|
@glk 133 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_set_style _vararg_count;
|
|
! glk_set_style(uint)
|
|
@glk 134 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_set_style_stream _vararg_count;
|
|
! glk_set_style_stream(stream, uint)
|
|
@glk 135 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_get_char_stream _vararg_count ret;
|
|
! glk_get_char_stream(stream) => int
|
|
@glk 144 _vararg_count ret;
|
|
return ret;
|
|
];
|
|
|
|
[ glk_get_line_stream _vararg_count ret;
|
|
! glk_get_line_stream(stream, nativechararray, arraylen) => uint
|
|
@glk 145 _vararg_count ret;
|
|
return ret;
|
|
];
|
|
|
|
[ glk_get_buffer_stream _vararg_count ret;
|
|
! glk_get_buffer_stream(stream, nativechararray, arraylen) => uint
|
|
@glk 146 _vararg_count ret;
|
|
return ret;
|
|
];
|
|
|
|
[ glk_char_to_lower _vararg_count ret;
|
|
! glk_char_to_lower(uchar) => uchar
|
|
@glk 160 _vararg_count ret;
|
|
return ret;
|
|
];
|
|
|
|
[ glk_char_to_upper _vararg_count ret;
|
|
! glk_char_to_upper(uchar) => uchar
|
|
@glk 161 _vararg_count ret;
|
|
return ret;
|
|
];
|
|
|
|
[ glk_stylehint_set _vararg_count;
|
|
! glk_stylehint_set(uint, uint, uint, int)
|
|
@glk 176 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_stylehint_clear _vararg_count;
|
|
! glk_stylehint_clear(uint, uint, uint)
|
|
@glk 177 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_style_distinguish _vararg_count ret;
|
|
! glk_style_distinguish(window, uint, uint) => uint
|
|
@glk 178 _vararg_count ret;
|
|
return ret;
|
|
];
|
|
|
|
[ glk_style_measure _vararg_count ret;
|
|
! glk_style_measure(window, uint, uint, &uint) => uint
|
|
@glk 179 _vararg_count ret;
|
|
return ret;
|
|
];
|
|
|
|
[ glk_select _vararg_count;
|
|
! glk_select(&{uint, window, uint, uint})
|
|
@glk 192 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_select_poll _vararg_count;
|
|
! glk_select_poll(&{uint, window, uint, uint})
|
|
@glk 193 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_request_line_event _vararg_count;
|
|
! glk_request_line_event(window, nativechararray, arraylen, uint)
|
|
@glk 208 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_cancel_line_event _vararg_count;
|
|
! glk_cancel_line_event(window, &{uint, window, uint, uint})
|
|
@glk 209 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_request_char_event _vararg_count;
|
|
! glk_request_char_event(window)
|
|
@glk 210 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_cancel_char_event _vararg_count;
|
|
! glk_cancel_char_event(window)
|
|
@glk 211 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_request_mouse_event _vararg_count;
|
|
! glk_request_mouse_event(window)
|
|
@glk 212 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_cancel_mouse_event _vararg_count;
|
|
! glk_cancel_mouse_event(window)
|
|
@glk 213 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_request_timer_events _vararg_count;
|
|
! glk_request_timer_events(uint)
|
|
@glk 214 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_image_get_info _vararg_count ret;
|
|
! glk_image_get_info(uint, &uint, &uint) => uint
|
|
@glk 224 _vararg_count ret;
|
|
return ret;
|
|
];
|
|
|
|
[ glk_image_draw _vararg_count ret;
|
|
! glk_image_draw(window, uint, int, int) => uint
|
|
@glk 225 _vararg_count ret;
|
|
return ret;
|
|
];
|
|
|
|
[ glk_image_draw_scaled _vararg_count ret;
|
|
! glk_image_draw_scaled(window, uint, int, int, uint, uint) => uint
|
|
@glk 226 _vararg_count ret;
|
|
return ret;
|
|
];
|
|
|
|
[ glk_window_flow_break _vararg_count;
|
|
! glk_window_flow_break(window)
|
|
@glk 232 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_window_erase_rect _vararg_count;
|
|
! glk_window_erase_rect(window, int, int, uint, uint)
|
|
@glk 233 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_window_fill_rect _vararg_count;
|
|
! glk_window_fill_rect(window, uint, int, int, uint, uint)
|
|
@glk 234 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_window_set_background_color _vararg_count;
|
|
! glk_window_set_background_color(window, uint)
|
|
@glk 235 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_schannel_iterate _vararg_count ret;
|
|
! glk_schannel_iterate(schannel, &uint) => schannel
|
|
@glk 240 _vararg_count ret;
|
|
return ret;
|
|
];
|
|
|
|
[ glk_schannel_get_rock _vararg_count ret;
|
|
! glk_schannel_get_rock(schannel) => uint
|
|
@glk 241 _vararg_count ret;
|
|
return ret;
|
|
];
|
|
|
|
[ glk_schannel_create _vararg_count ret;
|
|
! glk_schannel_create(uint) => schannel
|
|
@glk 242 _vararg_count ret;
|
|
return ret;
|
|
];
|
|
|
|
[ glk_schannel_destroy _vararg_count;
|
|
! glk_schannel_destroy(schannel)
|
|
@glk 243 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_schannel_create_ext _vararg_count ret;
|
|
! glk_schannel_create_ext(uint, uint) => schannel
|
|
@glk 244 _vararg_count ret;
|
|
return ret;
|
|
];
|
|
|
|
[ glk_schannel_play_multi _vararg_count ret;
|
|
! glk_schannel_play_multi(schannelarray, arraylen, uintarray, arraylen, uint) => uint
|
|
@glk 247 _vararg_count ret;
|
|
return ret;
|
|
];
|
|
|
|
[ glk_schannel_play _vararg_count ret;
|
|
! glk_schannel_play(schannel, uint) => uint
|
|
@glk 248 _vararg_count ret;
|
|
return ret;
|
|
];
|
|
|
|
[ glk_schannel_play_ext _vararg_count ret;
|
|
! glk_schannel_play_ext(schannel, uint, uint, uint) => uint
|
|
@glk 249 _vararg_count ret;
|
|
return ret;
|
|
];
|
|
|
|
[ glk_schannel_stop _vararg_count;
|
|
! glk_schannel_stop(schannel)
|
|
@glk 250 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_schannel_set_volume _vararg_count;
|
|
! glk_schannel_set_volume(schannel, uint)
|
|
@glk 251 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_sound_load_hint _vararg_count;
|
|
! glk_sound_load_hint(uint, uint)
|
|
@glk 252 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_schannel_set_volume_ext _vararg_count;
|
|
! glk_schannel_set_volume_ext(schannel, uint, uint, uint)
|
|
@glk 253 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_schannel_pause _vararg_count;
|
|
! glk_schannel_pause(schannel)
|
|
@glk 254 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_schannel_unpause _vararg_count;
|
|
! glk_schannel_unpause(schannel)
|
|
@glk 255 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_set_hyperlink _vararg_count;
|
|
! glk_set_hyperlink(uint)
|
|
@glk 256 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_set_hyperlink_stream _vararg_count;
|
|
! glk_set_hyperlink_stream(stream, uint)
|
|
@glk 257 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_request_hyperlink_event _vararg_count;
|
|
! glk_request_hyperlink_event(window)
|
|
@glk 258 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_cancel_hyperlink_event _vararg_count;
|
|
! glk_cancel_hyperlink_event(window)
|
|
@glk 259 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_buffer_to_lower_case_uni _vararg_count ret;
|
|
! glk_buffer_to_lower_case_uni(uintarray, arraylen, uint) => uint
|
|
@glk 288 _vararg_count ret;
|
|
return ret;
|
|
];
|
|
|
|
[ glk_buffer_to_upper_case_uni _vararg_count ret;
|
|
! glk_buffer_to_upper_case_uni(uintarray, arraylen, uint) => uint
|
|
@glk 289 _vararg_count ret;
|
|
return ret;
|
|
];
|
|
|
|
[ glk_buffer_to_title_case_uni _vararg_count ret;
|
|
! glk_buffer_to_title_case_uni(uintarray, arraylen, uint, uint) => uint
|
|
@glk 290 _vararg_count ret;
|
|
return ret;
|
|
];
|
|
|
|
[ glk_buffer_canon_decompose_uni _vararg_count ret;
|
|
! glk_buffer_canon_decompose_uni(uintarray, arraylen, uint) => uint
|
|
@glk 291 _vararg_count ret;
|
|
return ret;
|
|
];
|
|
|
|
[ glk_buffer_canon_normalize_uni _vararg_count ret;
|
|
! glk_buffer_canon_normalize_uni(uintarray, arraylen, uint) => uint
|
|
@glk 292 _vararg_count ret;
|
|
return ret;
|
|
];
|
|
|
|
[ glk_put_char_uni _vararg_count;
|
|
! glk_put_char_uni(uint)
|
|
@glk 296 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_put_string_uni _vararg_count;
|
|
! glk_put_string_uni(unicode)
|
|
@glk 297 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_put_buffer_uni _vararg_count;
|
|
! glk_put_buffer_uni(uintarray, arraylen)
|
|
@glk 298 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_put_char_stream_uni _vararg_count;
|
|
! glk_put_char_stream_uni(stream, uint)
|
|
@glk 299 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_put_string_stream_uni _vararg_count;
|
|
! glk_put_string_stream_uni(stream, unicode)
|
|
@glk 300 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_put_buffer_stream_uni _vararg_count;
|
|
! glk_put_buffer_stream_uni(stream, uintarray, arraylen)
|
|
@glk 301 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_get_char_stream_uni _vararg_count ret;
|
|
! glk_get_char_stream_uni(stream) => int
|
|
@glk 304 _vararg_count ret;
|
|
return ret;
|
|
];
|
|
|
|
[ glk_get_buffer_stream_uni _vararg_count ret;
|
|
! glk_get_buffer_stream_uni(stream, uintarray, arraylen) => uint
|
|
@glk 305 _vararg_count ret;
|
|
return ret;
|
|
];
|
|
|
|
[ glk_get_line_stream_uni _vararg_count ret;
|
|
! glk_get_line_stream_uni(stream, uintarray, arraylen) => uint
|
|
@glk 306 _vararg_count ret;
|
|
return ret;
|
|
];
|
|
|
|
[ glk_stream_open_file_uni _vararg_count ret;
|
|
! glk_stream_open_file_uni(fileref, uint, uint) => stream
|
|
@glk 312 _vararg_count ret;
|
|
return ret;
|
|
];
|
|
|
|
[ glk_stream_open_memory_uni _vararg_count ret;
|
|
! glk_stream_open_memory_uni(uintarray, arraylen, uint, uint) => stream
|
|
@glk 313 _vararg_count ret;
|
|
return ret;
|
|
];
|
|
|
|
[ glk_stream_open_resource_uni _vararg_count ret;
|
|
! glk_stream_open_resource_uni(uint, uint) => stream
|
|
@glk 314 _vararg_count ret;
|
|
return ret;
|
|
];
|
|
|
|
[ glk_request_char_event_uni _vararg_count;
|
|
! glk_request_char_event_uni(window)
|
|
@glk 320 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_request_line_event_uni _vararg_count;
|
|
! glk_request_line_event_uni(window, uintarray, arraylen, uint)
|
|
@glk 321 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_set_echo_line_event _vararg_count;
|
|
! glk_set_echo_line_event(window, uint)
|
|
@glk 336 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_set_terminators_line_event _vararg_count;
|
|
! glk_set_terminators_line_event(window, uintarray, arraylen)
|
|
@glk 337 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_current_time _vararg_count;
|
|
! glk_current_time(&{int, uint, int})
|
|
@glk 352 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_current_simple_time _vararg_count ret;
|
|
! glk_current_simple_time(uint) => int
|
|
@glk 353 _vararg_count ret;
|
|
return ret;
|
|
];
|
|
|
|
[ glk_time_to_date_utc _vararg_count;
|
|
! glk_time_to_date_utc(&{int, uint, int}, &{int, int, int, int, int, int, int, int})
|
|
@glk 360 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_time_to_date_local _vararg_count;
|
|
! glk_time_to_date_local(&{int, uint, int}, &{int, int, int, int, int, int, int, int})
|
|
@glk 361 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_simple_time_to_date_utc _vararg_count;
|
|
! glk_simple_time_to_date_utc(int, uint, &{int, int, int, int, int, int, int, int})
|
|
@glk 362 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_simple_time_to_date_local _vararg_count;
|
|
! glk_simple_time_to_date_local(int, uint, &{int, int, int, int, int, int, int, int})
|
|
@glk 363 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_date_to_time_utc _vararg_count;
|
|
! glk_date_to_time_utc(&{int, int, int, int, int, int, int, int}, &{int, uint, int})
|
|
@glk 364 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_date_to_time_local _vararg_count;
|
|
! glk_date_to_time_local(&{int, int, int, int, int, int, int, int}, &{int, uint, int})
|
|
@glk 365 _vararg_count 0;
|
|
return 0;
|
|
];
|
|
|
|
[ glk_date_to_simple_time_utc _vararg_count ret;
|
|
! glk_date_to_simple_time_utc(&{int, int, int, int, int, int, int, int}, uint) => int
|
|
@glk 366 _vararg_count ret;
|
|
return ret;
|
|
];
|
|
|
|
[ glk_date_to_simple_time_local _vararg_count ret;
|
|
! glk_date_to_simple_time_local(&{int, int, int, int, int, int, int, int}, uint) => int
|
|
@glk 367 _vararg_count ret;
|
|
return ret;
|
|
];
|
|
|
|
@p Rocks.
|
|
These are unique ID codes used to mark resources; think of them as inedible
|
|
cookies.
|
|
|
|
@c
|
|
Constant GG_MAINWIN_ROCK 201;
|
|
Constant GG_STATUSWIN_ROCK 202;
|
|
Constant GG_QUOTEWIN_ROCK 203;
|
|
Constant GG_SAVESTR_ROCK 301;
|
|
Constant GG_SCRIPTSTR_ROCK 302;
|
|
Constant GG_COMMANDWSTR_ROCK 303;
|
|
Constant GG_COMMANDRSTR_ROCK 304;
|
|
Constant GG_SCRIPTFREF_ROCK 401;
|
|
Constant GG_FOREGROUNDCHAN_ROCK 410;
|
|
Constant GG_BACKGROUNDCHAN_ROCK 411;
|
|
|
|
@p Stubs.
|
|
These are I6 library-style entry point routines, not used by I7, but retained
|
|
in case I7 extensions want to do interesting things with Glulx.
|
|
|
|
@c
|
|
#Stub HandleGlkEvent 2;
|
|
#Stub IdentifyGlkObject 4;
|
|
#Stub InitGlkWindow 1;
|
|
|
|
@p Starting Up.
|
|
|VM_Initialise()| is almost the first routine called, except that the
|
|
"starting the virtual machine" activity is allowed to go first; and,
|
|
come to think of it, memory allocation has to be set up before even that,
|
|
and that in turn calls |VM_PreInitialise()| to do the absolute minimum.
|
|
|
|
Arrangements are a little different here from on the Z-machine, because some
|
|
data is retained in the case of a restart.
|
|
|
|
(Many thanks are due to Eliuk Blau, who found several tricky timing errors
|
|
here and elsewhere in the Glulx-specific code. Frankly, I feel like hanging
|
|
a sign on the following routines which reads "Congratulations on bringing
|
|
light to the Dark Room.")
|
|
|
|
@c
|
|
[ VM_PreInitialise res;
|
|
@gestalt 4 2 res; ! Test if this interpreter has Glk...
|
|
if (res == 0) quit; ! ...without which there would be nothing we could do
|
|
|
|
unicode_gestalt_ok = false;
|
|
if (glk_gestalt(gestalt_Unicode, 0))
|
|
unicode_gestalt_ok = true;
|
|
|
|
! Set the VM's I/O system to be Glk.
|
|
@setiosys 2 0;
|
|
];
|
|
|
|
[ VM_Initialise res sty i;
|
|
@gestalt 4 2 res; ! Test if this interpreter has Glk...
|
|
if (res == 0) quit; ! ...without which there would be nothing we could do
|
|
|
|
! First, we must go through all the Glk objects that exist, and see
|
|
! if we created any of them. One might think this strange, since the
|
|
! program has just started running, but remember that the player might
|
|
! have just typed "restart".
|
|
|
|
GGRecoverObjects();
|
|
|
|
! Sound channel initialisation, and RNG fixing, must be done now rather
|
|
! than later in case InitGlkWindow() returns a non-zero value.
|
|
|
|
if (glk_gestalt(gestalt_Sound, 0)) {
|
|
if (gg_foregroundchan == 0)
|
|
gg_foregroundchan = glk_schannel_create(GG_FOREGROUNDCHAN_ROCK);
|
|
if (gg_backgroundchan == 0)
|
|
gg_backgroundchan = glk_schannel_create(GG_BACKGROUNDCHAN_ROCK);
|
|
}
|
|
|
|
#ifdef FIX_RNG;
|
|
@random 10000 i;
|
|
i = -i-2000;
|
|
print "[Random number generator seed is ", i, "]^";
|
|
@setrandom i;
|
|
#endif; ! FIX_RNG
|
|
|
|
res = InitGlkWindow(0);
|
|
if (res ~= 0) return;
|
|
|
|
! Now, gg_mainwin and gg_storywin might already be set. If not, set them.
|
|
|
|
if (gg_mainwin == 0) {
|
|
! Open the story window.
|
|
res = InitGlkWindow(GG_MAINWIN_ROCK);
|
|
if (res == 0) {
|
|
! Left-justify the header style
|
|
glk_stylehint_set(wintype_TextBuffer, style_Header, stylehint_Justification, 0);
|
|
! Try to make emphasized type in italics and not boldface
|
|
glk_stylehint_set(wintype_TextBuffer, style_Emphasized, stylehint_Weight, 0);
|
|
glk_stylehint_set(wintype_TextBuffer, style_Emphasized, stylehint_Oblique, 1);
|
|
gg_mainwin = glk_window_open(0, 0, 0, wintype_TextBuffer, GG_MAINWIN_ROCK);
|
|
}
|
|
if (gg_mainwin == 0) quit; ! If we can't even open one window, give in
|
|
} else {
|
|
! There was already a story window. We should erase it.
|
|
glk_window_clear(gg_mainwin);
|
|
}
|
|
|
|
if (gg_statuswin == 0) {
|
|
res = InitGlkWindow(GG_STATUSWIN_ROCK);
|
|
if (res == 0) {
|
|
statuswin_cursize = statuswin_size;
|
|
for (sty=0: sty<style_NUMSTYLES: sty++)
|
|
glk_stylehint_set(wintype_TextGrid, sty, stylehint_ReverseColor, 1);
|
|
gg_statuswin =
|
|
glk_window_open(gg_mainwin, winmethod_Fixed + winmethod_Above,
|
|
statuswin_cursize, wintype_TextGrid, GG_STATUSWIN_ROCK);
|
|
}
|
|
}
|
|
! It's possible that the status window couldn't be opened, in which case
|
|
! gg_statuswin is now zero. We must allow for that later on.
|
|
|
|
glk_set_window(gg_mainwin);
|
|
|
|
InitGlkWindow(1);
|
|
];
|
|
|
|
[ GGRecoverObjects id;
|
|
! If GGRecoverObjects() has been called, all these stored IDs are
|
|
! invalid, so we start by clearing them all out.
|
|
! (In fact, after a restoreundo, some of them may still be good.
|
|
! For simplicity, though, we assume the general case.)
|
|
gg_mainwin = 0;
|
|
gg_statuswin = 0;
|
|
gg_quotewin = 0;
|
|
gg_scriptfref = 0;
|
|
gg_scriptstr = 0;
|
|
gg_savestr = 0;
|
|
statuswin_cursize = 0;
|
|
gg_foregroundchan = 0;
|
|
gg_backgroundchan = 0;
|
|
#Ifdef DEBUG;
|
|
gg_commandstr = 0;
|
|
gg_command_reading = false;
|
|
#Endif; ! DEBUG
|
|
! Also tell the game to clear its object references.
|
|
IdentifyGlkObject(0);
|
|
|
|
id = glk_stream_iterate(0, gg_arguments);
|
|
while (id) {
|
|
switch (gg_arguments-->0) {
|
|
GG_SAVESTR_ROCK: gg_savestr = id;
|
|
GG_SCRIPTSTR_ROCK: gg_scriptstr = id;
|
|
#Ifdef DEBUG;
|
|
GG_COMMANDWSTR_ROCK: gg_commandstr = id;
|
|
gg_command_reading = false;
|
|
GG_COMMANDRSTR_ROCK: gg_commandstr = id;
|
|
gg_command_reading = true;
|
|
#Endif; ! DEBUG
|
|
default: IdentifyGlkObject(1, 1, id, gg_arguments-->0);
|
|
}
|
|
id = glk_stream_iterate(id, gg_arguments);
|
|
}
|
|
|
|
id = glk_window_iterate(0, gg_arguments);
|
|
while (id) {
|
|
switch (gg_arguments-->0) {
|
|
GG_MAINWIN_ROCK: gg_mainwin = id;
|
|
GG_STATUSWIN_ROCK: gg_statuswin = id;
|
|
GG_QUOTEWIN_ROCK: gg_quotewin = id;
|
|
default: IdentifyGlkObject(1, 0, id, gg_arguments-->0);
|
|
}
|
|
id = glk_window_iterate(id, gg_arguments);
|
|
}
|
|
|
|
id = glk_fileref_iterate(0, gg_arguments);
|
|
while (id) {
|
|
switch (gg_arguments-->0) {
|
|
GG_SCRIPTFREF_ROCK: gg_scriptfref = id;
|
|
default: IdentifyGlkObject(1, 2, id, gg_arguments-->0);
|
|
}
|
|
id = glk_fileref_iterate(id, gg_arguments);
|
|
}
|
|
|
|
if (glk_gestalt(gestalt_Sound, 0)) {
|
|
id = glk_schannel_iterate(0, gg_arguments);
|
|
while (id) {
|
|
switch (gg_arguments-->0) {
|
|
GG_FOREGROUNDCHAN_ROCK: gg_foregroundchan = id;
|
|
GG_BACKGROUNDCHAN_ROCK: gg_backgroundchan = id;
|
|
}
|
|
id = glk_schannel_iterate(id, gg_arguments);
|
|
}
|
|
if (gg_foregroundchan ~= 0) { glk_schannel_stop(gg_foregroundchan); }
|
|
if (gg_backgroundchan ~= 0) { glk_schannel_stop(gg_backgroundchan); }
|
|
}
|
|
|
|
! Tell the game to tie up any loose ends.
|
|
IdentifyGlkObject(2);
|
|
];
|
|
|
|
@p Enable Acceleration.
|
|
This enables use of March 2009 extension to Glulx which optimises the speed
|
|
of Inform-compiled story files by moving the work of I6 veneer routines into
|
|
the interpreter itself. It should have no effect on earlier versions of the
|
|
Glulx VM, which will lack the gestalt for this feature, but nor should it
|
|
do any harm.
|
|
|
|
@c
|
|
[ ENABLE_GLULX_ACCEL_R addr res;
|
|
@gestalt 9 0 res;
|
|
if (res == 0) return;
|
|
addr = #classes_table;
|
|
@accelparam 0 addr;
|
|
@accelparam 1 INDIV_PROP_START;
|
|
@accelparam 2 Class;
|
|
@accelparam 3 Object;
|
|
@accelparam 4 Routine;
|
|
@accelparam 5 String;
|
|
addr = #globals_array + WORDSIZE * #g$self;
|
|
@accelparam 6 addr;
|
|
@accelparam 7 NUM_ATTR_BYTES;
|
|
addr = #cpv__start;
|
|
@accelparam 8 addr;
|
|
@accelfunc 1 Z__Region;
|
|
@accelfunc 2 CP__Tab;
|
|
@accelfunc 3 RA__Pr;
|
|
@accelfunc 4 RL__Pr;
|
|
@accelfunc 5 OC__Cl;
|
|
@accelfunc 6 RV__Pr;
|
|
@accelfunc 7 OP__Pr;
|
|
rfalse;
|
|
];
|
|
|
|
@p Release Number.
|
|
Like all software, IF story files have release numbers to mark revised
|
|
versions being circulated: unlike most software, and partly for traditional
|
|
reasons, the version number is recorded not in some print statement or
|
|
variable but is branded on, so to speak, in a specific memory location
|
|
of the story file header.
|
|
|
|
|VM_Describe_Release()| describes the release and is used as part of the
|
|
"banner", IF's equivalent to a title page.
|
|
|
|
@c
|
|
[ VM_Describe_Release i;
|
|
print "Release ";
|
|
@aloads ROM_GAMERELEASE 0 i;
|
|
print i;
|
|
print " / Serial number ";
|
|
for (i=0 : i<6 : i++) print (char) ROM_GAMESERIAL->i;
|
|
];
|
|
|
|
@p Keyboard Input.
|
|
The VM must provide three routines for keyboard input:
|
|
|
|
(a) |VM_KeyChar()| waits for a key to be pressed and then returns the
|
|
character chosen as a ZSCII character.
|
|
(b) |VM_KeyDelay(N)| waits up to $N/10$ seconds for a key to be pressed,
|
|
returning the ZSCII character if so, or 0 if not.
|
|
(c) |VM_ReadKeyboard(b, t)| reads a whole newline-terminated command
|
|
into the buffer |b|, then parses it into a word stream in the table |t|.
|
|
|
|
There are elaborations to due with mouse clicks, but this isn't the place
|
|
to document all of that.
|
|
|
|
@c
|
|
[ VM_KeyChar win nostat done res ix jx ch;
|
|
jx = ch; ! squash compiler warnings
|
|
if (win == 0) win = gg_mainwin;
|
|
if (gg_commandstr ~= 0 && gg_command_reading ~= false) {
|
|
done = glk_get_line_stream(gg_commandstr, gg_arguments, 31);
|
|
if (done == 0) {
|
|
glk_stream_close(gg_commandstr, 0);
|
|
gg_commandstr = 0;
|
|
gg_command_reading = false;
|
|
! fall through to normal user input.
|
|
} else {
|
|
! Trim the trailing newline
|
|
if (gg_arguments->(done-1) == 10) done = done-1;
|
|
res = gg_arguments->0;
|
|
if (res == '\') {
|
|
res = 0;
|
|
for (ix=1 : ix<done : ix++) {
|
|
ch = gg_arguments->ix;
|
|
if (ch >= '0' && ch <= '9') {
|
|
@shiftl res 4 res;
|
|
res = res + (ch-'0');
|
|
} else if (ch >= 'a' && ch <= 'f') {
|
|
@shiftl res 4 res;
|
|
res = res + (ch+10-'a');
|
|
} else if (ch >= 'A' && ch <= 'F') {
|
|
@shiftl res 4 res;
|
|
res = res + (ch+10-'A');
|
|
}
|
|
}
|
|
}
|
|
jump KCPContinue;
|
|
}
|
|
}
|
|
done = false;
|
|
glk_request_char_event(win);
|
|
while (~~done) {
|
|
glk_select(gg_event);
|
|
switch (gg_event-->0) {
|
|
5: ! evtype_Arrange
|
|
if (nostat) {
|
|
glk_cancel_char_event(win);
|
|
res = $80000000;
|
|
done = true;
|
|
break;
|
|
}
|
|
DrawStatusLine();
|
|
2: ! evtype_CharInput
|
|
if (gg_event-->1 == win) {
|
|
res = gg_event-->2;
|
|
done = true;
|
|
}
|
|
}
|
|
ix = HandleGlkEvent(gg_event, 1, gg_arguments);
|
|
if (ix == 2) {
|
|
res = gg_arguments-->0;
|
|
done = true;
|
|
} else if (ix == -1) done = false;
|
|
}
|
|
if (gg_commandstr ~= 0 && gg_command_reading == false) {
|
|
if (res < 32 || res >= 256 || (res == '\' or ' ')) {
|
|
glk_put_char_stream(gg_commandstr, '\');
|
|
done = 0;
|
|
jx = res;
|
|
for (ix=0 : ix<8 : ix++) {
|
|
@ushiftr jx 28 ch;
|
|
@shiftl jx 4 jx;
|
|
ch = ch & $0F;
|
|
if (ch ~= 0 || ix == 7) done = 1;
|
|
if (done) {
|
|
if (ch >= 0 && ch <= 9) ch = ch + '0';
|
|
else ch = (ch - 10) + 'A';
|
|
glk_put_char_stream(gg_commandstr, ch);
|
|
}
|
|
}
|
|
} else {
|
|
glk_put_char_stream(gg_commandstr, res);
|
|
}
|
|
glk_put_char_stream(gg_commandstr, 10); ! newline
|
|
}
|
|
.KCPContinue;
|
|
return res;
|
|
];
|
|
|
|
[ VM_KeyDelay tenths key done ix;
|
|
glk_request_char_event(gg_mainwin);
|
|
glk_request_timer_events(tenths*100);
|
|
while (~~done) {
|
|
glk_select(gg_event);
|
|
ix = HandleGlkEvent(gg_event, 1, gg_arguments);
|
|
if (ix == 2) {
|
|
key = gg_arguments-->0;
|
|
done = true;
|
|
}
|
|
else if (ix >= 0 && gg_event-->0 == 1 or 2) {
|
|
key = gg_event-->2;
|
|
done = true;
|
|
}
|
|
}
|
|
glk_cancel_char_event(gg_mainwin);
|
|
glk_request_timer_events(0);
|
|
return key;
|
|
];
|
|
|
|
[ VM_ReadKeyboard a_buffer a_table done ix;
|
|
if (gg_commandstr ~= 0 && gg_command_reading ~= false) {
|
|
done = glk_get_line_stream(gg_commandstr, a_buffer+WORDSIZE,
|
|
(INPUT_BUFFER_LEN-WORDSIZE)-1);
|
|
if (done == 0) {
|
|
glk_stream_close(gg_commandstr, 0);
|
|
gg_commandstr = 0;
|
|
gg_command_reading = false;
|
|
}
|
|
else {
|
|
! Trim the trailing newline
|
|
if ((a_buffer+WORDSIZE)->(done-1) == 10) done = done-1;
|
|
a_buffer-->0 = done;
|
|
VM_Style(INPUT_VMSTY);
|
|
glk_put_buffer(a_buffer+WORDSIZE, done);
|
|
VM_Style(NORMAL_VMSTY);
|
|
print "^";
|
|
jump KPContinue;
|
|
}
|
|
}
|
|
done = false;
|
|
glk_request_line_event(gg_mainwin, a_buffer+WORDSIZE, INPUT_BUFFER_LEN-WORDSIZE, 0);
|
|
while (~~done) {
|
|
glk_select(gg_event);
|
|
switch (gg_event-->0) {
|
|
5: ! evtype_Arrange
|
|
DrawStatusLine();
|
|
3: ! evtype_LineInput
|
|
if (gg_event-->1 == gg_mainwin) {
|
|
a_buffer-->0 = gg_event-->2;
|
|
done = true;
|
|
}
|
|
}
|
|
ix = HandleGlkEvent(gg_event, 0, a_buffer);
|
|
if (ix == 2) done = true;
|
|
else if (ix == -1) done = false;
|
|
}
|
|
if (gg_commandstr ~= 0 && gg_command_reading == false) {
|
|
glk_put_buffer_stream(gg_commandstr, a_buffer+WORDSIZE, a_buffer-->0);
|
|
glk_put_char_stream(gg_commandstr, 10); ! newline
|
|
}
|
|
.KPContinue;
|
|
VM_Tokenise(a_buffer,a_table);
|
|
! It's time to close any quote window we've got going.
|
|
if (gg_quotewin) {
|
|
glk_window_close(gg_quotewin, 0);
|
|
gg_quotewin = 0;
|
|
}
|
|
#ifdef ECHO_COMMANDS;
|
|
print "** ";
|
|
for (ix=WORDSIZE: ix<(a_buffer-->0)+WORDSIZE: ix++) print (char) a_buffer->ix;
|
|
print "^";
|
|
#endif; ! ECHO_COMMANDS
|
|
];
|
|
|
|
@p Buffer Functions.
|
|
A "buffer", in this sense, is an array containing a stream of characters
|
|
typed from the keyboard; a "parse buffer" is an array which resolves this
|
|
into individual words, pointing to the relevant entries in the dictionary
|
|
structure. Because each VM has its own format for each of these arrays (not
|
|
to mention the dictionary), we have to provide some standard operations
|
|
needed by the rest of the template as routines for each VM.
|
|
|
|
|VM_CopyBuffer(to, from)| copies one buffer into another.
|
|
|
|
|VM_Tokenise(buff, parse_buff)| takes the text in the buffer |buff| and
|
|
produces the corresponding data in the parse buffer |parse_buff| -- this is
|
|
called tokenisation since the characters are divided into words: in traditional
|
|
computing jargon, such clumps of characters treated syntactically as units
|
|
are called tokens.
|
|
|
|
|LTI_Insert| is documented in the DM4 and the |LTI| prefix stands for
|
|
"Language To Informese": it's used only by translations into non-English
|
|
languages of play, and is not called in the template.
|
|
|
|
@c
|
|
[ VM_CopyBuffer bto bfrom i;
|
|
for (i=0: i<INPUT_BUFFER_LEN: i++) bto->i = bfrom->i;
|
|
];
|
|
|
|
[ VM_PrintToBuffer buf len a b c;
|
|
if (b) {
|
|
if (metaclass(a) == Object && a.#b == WORDSIZE
|
|
&& metaclass(a.b) == String)
|
|
buf-->0 = Glulx_PrintAnyToArray(buf+WORDSIZE, len, a.b);
|
|
else if (metaclass(a) == Routine)
|
|
buf-->0 = Glulx_PrintAnyToArray(buf+WORDSIZE, len, a, b, c);
|
|
else
|
|
buf-->0 = Glulx_PrintAnyToArray(buf+WORDSIZE, len, a, b);
|
|
}
|
|
else if (metaclass(a) == Routine)
|
|
buf-->0 = Glulx_PrintAnyToArray(buf+WORDSIZE, len, a, b, c);
|
|
else
|
|
buf-->0 = Glulx_PrintAnyToArray(buf+WORDSIZE, len, a);
|
|
if (buf-->0 > len) buf-->0 = len;
|
|
return buf-->0;
|
|
];
|
|
|
|
[ VM_Tokenise buf tab
|
|
cx numwords len bx ix wx wpos wlen val res dictlen entrylen;
|
|
len = buf-->0;
|
|
buf = buf+WORDSIZE;
|
|
|
|
! First, split the buffer up into words. We use the standard Infocom
|
|
! list of word separators (comma, period, double-quote).
|
|
|
|
cx = 0;
|
|
numwords = 0;
|
|
while (cx < len) {
|
|
while (cx < len && buf->cx == ' ') cx++;
|
|
if (cx >= len) break;
|
|
bx = cx;
|
|
if (buf->cx == '.' or ',' or '"') cx++;
|
|
else {
|
|
while (cx < len && buf->cx ~= ' ' or '.' or ',' or '"') cx++;
|
|
}
|
|
tab-->(numwords*3+2) = (cx-bx);
|
|
tab-->(numwords*3+3) = WORDSIZE+bx;
|
|
numwords++;
|
|
if (numwords >= MAX_BUFFER_WORDS) break;
|
|
}
|
|
tab-->0 = numwords;
|
|
|
|
! Now we look each word up in the dictionary.
|
|
|
|
dictlen = #dictionary_table-->0;
|
|
entrylen = DICT_WORD_SIZE + 7;
|
|
|
|
for (wx=0 : wx<numwords : wx++) {
|
|
wlen = tab-->(wx*3+2);
|
|
wpos = tab-->(wx*3+3);
|
|
|
|
! Copy the word into the gg_tokenbuf array, clipping to DICT_WORD_SIZE
|
|
! characters and lower case.
|
|
if (wlen > DICT_WORD_SIZE) wlen = DICT_WORD_SIZE;
|
|
cx = wpos - WORDSIZE;
|
|
for (ix=0 : ix<wlen : ix++) gg_tokenbuf->ix = VM_UpperToLowerCase(buf->(cx+ix));
|
|
for (: ix<DICT_WORD_SIZE : ix++) gg_tokenbuf->ix = 0;
|
|
|
|
val = #dictionary_table + WORDSIZE;
|
|
@binarysearch gg_tokenbuf DICT_WORD_SIZE val entrylen dictlen 1 1 res;
|
|
tab-->(wx*3+1) = res;
|
|
}
|
|
];
|
|
|
|
[ LTI_Insert i ch b y;
|
|
|
|
! Protect us from strict mode, as this isn't an array in quite the
|
|
! sense it expects
|
|
b = buffer;
|
|
|
|
! Insert character ch into buffer at point i.
|
|
! Being careful not to let the buffer possibly overflow:
|
|
y = b-->0;
|
|
if (y > INPUT_BUFFER_LEN) y = INPUT_BUFFER_LEN;
|
|
|
|
! Move the subsequent text along one character:
|
|
for (y=y+WORDSIZE : y>i : y--) b->y = b->(y-1);
|
|
b->i = ch;
|
|
|
|
! And the text is now one character longer:
|
|
if (b-->0 < INPUT_BUFFER_LEN) (b-->0)++;
|
|
];
|
|
|
|
@p Dictionary Functions.
|
|
Again, the dictionary structure is differently arranged on the different VMs.
|
|
This is a data structure containing, in compressed form, the text of all the
|
|
words to be recognised by tokenisation (above). In I6 for Glulx, a dictionary
|
|
word is represented at run-time by its record's address in the dictionary.
|
|
|
|
|VM_InvalidDictionaryAddress(A)| tests whether |A| is a valid record address
|
|
in the dictionary data structure. In Glulx, dictionary records might in
|
|
theory be anywhere in the 2 GB or so of possible memory, but we can rule
|
|
out negative addresses. (This allows $-1$, say, to be used as a value meaning
|
|
"not a valid dictionary word".)
|
|
|
|
|VM_DictionaryAddressToNumber(A)| and |VM_NumberToDictionaryAddress(N)|
|
|
convert between word addresses and their run-time representations:
|
|
since, on Glulx, they are the same, these are each the identity function.
|
|
|
|
@c
|
|
[ VM_InvalidDictionaryAddress addr;
|
|
if (addr < 0) rtrue;
|
|
rfalse;
|
|
];
|
|
|
|
[ VM_DictionaryAddressToNumber w; return w; ];
|
|
[ VM_NumberToDictionaryAddress n; return n; ];
|
|
|
|
Array gg_tokenbuf -> DICT_WORD_SIZE;
|
|
|
|
[ GGWordCompare str1 str2 ix jx;
|
|
for (ix=0 : ix<DICT_WORD_SIZE : ix++) {
|
|
jx = (str1->ix) - (str2->ix);
|
|
if (jx ~= 0) return jx;
|
|
}
|
|
return 0;
|
|
];
|
|
|
|
@p SHOWVERB support.
|
|
Further VM-specific tables cover actions and attributes, and these are
|
|
used by the SHOWVERB testing command.
|
|
|
|
@c
|
|
#Ifdef DEBUG;
|
|
[ DebugAction a str;
|
|
if (a >= 4096) { print "<fake action ", a-4096, ">"; return; }
|
|
if (a < 0 || a >= #identifiers_table-->7) print "<invalid action ", a, ">";
|
|
else {
|
|
str = #identifiers_table-->6;
|
|
str = str-->a;
|
|
if (str) print (string) str; else print "<unnamed action ", a, ">";
|
|
}
|
|
];
|
|
|
|
[ DebugAttribute a str;
|
|
if (a < 0 || a >= NUM_ATTR_BYTES*8) print "<invalid attribute ", a, ">";
|
|
else {
|
|
str = #identifiers_table-->4;
|
|
str = str-->a;
|
|
if (str) print (string) str; else print "<unnamed attribute ", a, ">";
|
|
}
|
|
];
|
|
#Endif;
|
|
|
|
@p Command Tables.
|
|
The VM is also generated containing a data structure for the grammar
|
|
produced by I6's |Verb| and |Extend| directives: this is essentially a
|
|
list of command verbs such as DROP or PUSH, together with a list of
|
|
synonyms, and then the grammar for the subsequent commands to be
|
|
recognised by the parser.
|
|
|
|
@c
|
|
[ VM_CommandTableAddress i;
|
|
return (#grammar_table)-->(i+1);
|
|
];
|
|
|
|
[ VM_PrintCommandWords i wd j dictlen entrylen;
|
|
dictlen = #dictionary_table-->0;
|
|
entrylen = DICT_WORD_SIZE + 7;
|
|
for (j=0 : j<dictlen : j++) {
|
|
wd = #dictionary_table + WORDSIZE + entrylen*j;
|
|
if (DictionaryWordToVerbNum(wd) == i)
|
|
print "'", (address) wd, "' ";
|
|
}
|
|
];
|
|
|
|
@p Random Number Generator.
|
|
No routine is needed for extracting a random number, since I6's built-in
|
|
|random| function does that, but it's useful to abstract the process of
|
|
seeding the RNG so that it produces a repeatable sequence of "random"
|
|
numbers from here on: the necessary opcodes are different for the two VMs.
|
|
|
|
@c
|
|
[ VM_Seed_RNG n;
|
|
@setrandom n;
|
|
];
|
|
|
|
@p Memory Allocation.
|
|
This is dynamic memory allocation: something which is never practicable in
|
|
the Z-machine, because the whole address range is already claimed, but which
|
|
is viable on recent revisions of Glulx.
|
|
|
|
@c
|
|
[ VM_AllocateMemory amount i;
|
|
@gestalt 7 0 i;
|
|
if (i == 0) return i;
|
|
@malloc amount i;
|
|
return i;
|
|
];
|
|
|
|
[ VM_FreeMemory address i;
|
|
@gestalt 7 0 i;
|
|
if (i == 0) return;
|
|
@mfree address;
|
|
];
|
|
|
|
@p Audiovisual Resources.
|
|
The Z-machine only barely supports figures and sound effects, so Glulx is
|
|
the preferred VM to choose if they are wanted. Properly speaking, it's not
|
|
Glulx which supports these, but its I/O layer Glk, and implementations
|
|
of Glk are free to support them or not as they please: "cheapglk", a
|
|
dumb terminal version, does not, for instance. We therefore have to
|
|
investigate the "gestalt" to find out.
|
|
|
|
@c
|
|
[ VM_Picture resource_ID;
|
|
if (glk_gestalt(gestalt_Graphics, 0)) {
|
|
glk_image_draw(gg_mainwin, resource_ID, imagealign_InlineCenter, 0);
|
|
} else {
|
|
print "[Picture number ", resource_ID, " here.]^";
|
|
}
|
|
];
|
|
|
|
[ VM_SoundEffect resource_ID;
|
|
if (glk_gestalt(gestalt_Sound, 0)) {
|
|
glk_schannel_play(gg_foregroundchan, resource_ID);
|
|
} else {
|
|
print "[Sound effect number ", resource_ID, " here.]^";
|
|
}
|
|
];
|
|
|
|
@p Typography.
|
|
Glk makes an attempt to present typographic styles as being a matter of
|
|
semantic markup rather than controlling the actual appearance of text:
|
|
the idea is that the story file should want to print something in a
|
|
heading kind of way, and then the interpreter -- guided by the player's
|
|
reading preferences -- might set that in bold, or larger type, or red ink,
|
|
or any combination of the three, or with other effects entirely. This is
|
|
not the place to discuss whether that was a wise decision for Glk to take
|
|
(it really, really, {\it really} wasn't): we can only play along.
|
|
|
|
@c
|
|
[ VM_Style sty;
|
|
switch (sty) {
|
|
NORMAL_VMSTY: glk_set_style(style_Normal);
|
|
HEADER_VMSTY: glk_set_style(style_Header);
|
|
SUBHEADER_VMSTY: glk_set_style(style_Subheader);
|
|
NOTE_VMSTY: glk_set_style(style_Note);
|
|
ALERT_VMSTY: glk_set_style(style_Alert);
|
|
BLOCKQUOTE_VMSTY: glk_set_style(style_BlockQuote);
|
|
INPUT_VMSTY: glk_set_style(style_Input);
|
|
}
|
|
];
|
|
|
|
@p Character Casing.
|
|
The following are the equivalent of |tolower| and |toupper|, the traditional
|
|
C library functions for forcing letters into lower and upper case form, for
|
|
the ZSCII character set. Note that Glulx can also use Unicode characters for
|
|
some purposes (Unicode was a relatively late addition to the Glulx standard),
|
|
and we make good use of this when storing text.
|
|
|
|
@c
|
|
[ VM_UpperToLowerCase c; return glk_char_to_lower(c); ];
|
|
[ VM_LowerToUpperCase c; return glk_char_to_upper(c); ];
|
|
|
|
@p Glulx-Only Printing Routines.
|
|
Partly because of the smallness of the range of representable values in
|
|
the Z-machine, there is little run-time type-checking that can be done:
|
|
for instance a dictionary address cannot be distinguished from a function
|
|
address because they are encoded differently, so that a function address
|
|
(which is packed) could well coincide with that of a dictionary word (which
|
|
is not). On Glulx these restrictions are somewhat lifted, so that it's
|
|
possible to write a routine which can look at a value, work out what it
|
|
must mean, and print it suitably. This is only possible up to a point --
|
|
for instance, it can't distinguish an integer from a function address --
|
|
and in I7 the use of this sort of trick is much less important because
|
|
type-checking in the NI compiler handles the problem much better. Still,
|
|
we retain some Glulx-only features because they are convenient for writing
|
|
external files to disc, for instance, something which the Z-machine can't
|
|
do in any case.
|
|
|
|
|Glulx_PrintAnything| handles strings, functions (with optional arguments),
|
|
objects, object properties (with optional arguments), and dictionary words.
|
|
|
|
|Glulx_PrintAnyToArray| does the same, but the output is sent to a byte
|
|
array in memory. The first two arguments must be the array address and
|
|
length; subsequent arguments are as for |Glulx_PrintAnything|. The return
|
|
value is the number of characters output. If the output is longer than the
|
|
array length given, the extra characters are discarded, so the array does
|
|
not overflow. (However, the return value is the total length of the output,
|
|
including discarded characters.) The character set stored here is ZSCII,
|
|
not Unicode.
|
|
|
|
|Glulx_ChangeAnyToCString| calls |Glulx_PrintAnyToArray| on a particular
|
|
array, then amends the result to make it a C-style string -- that is, a
|
|
sequence of byte-sized characters which are null terminated. The character
|
|
set stored here is once again ZSCII, not Unicode.
|
|
|
|
@c
|
|
! Glulx_PrintAnything() <nothing printed>
|
|
! Glulx_PrintAnything(0) <nothing printed>
|
|
! Glulx_PrintAnything("string"); print (string) "string";
|
|
! Glulx_PrintAnything('word') print (address) 'word';
|
|
! Glulx_PrintAnything(obj) print (name) obj;
|
|
! Glulx_PrintAnything(obj, prop) obj.prop();
|
|
! Glulx_PrintAnything(obj, prop, args...) obj.prop(args...);
|
|
! Glulx_PrintAnything(func) func();
|
|
! Glulx_PrintAnything(func, args...) func(args...);
|
|
|
|
[ Glulx_PrintAnything _vararg_count obj mclass;
|
|
if (_vararg_count == 0) return;
|
|
@copy sp obj;
|
|
_vararg_count--;
|
|
if (obj == 0) return;
|
|
|
|
if (obj->0 == $60) {
|
|
! Dictionary word. Metaclass() can't catch this case, so we do it manually
|
|
print (address) obj;
|
|
return;
|
|
}
|
|
|
|
mclass = metaclass(obj);
|
|
switch (mclass) {
|
|
nothing:
|
|
return;
|
|
String:
|
|
print (string) obj;
|
|
return;
|
|
Routine:
|
|
! Call the function with all the arguments which are already
|
|
! on the stack.
|
|
@call obj _vararg_count 0;
|
|
return;
|
|
Object:
|
|
if (_vararg_count == 0) {
|
|
print (name) obj;
|
|
}
|
|
else {
|
|
! Push the object back onto the stack, and call the
|
|
! veneer routine that handles obj.prop() calls.
|
|
@copy obj sp;
|
|
_vararg_count++;
|
|
@call CA__Pr _vararg_count 0;
|
|
}
|
|
return;
|
|
}
|
|
];
|
|
|
|
[ Glulx_PrintAnyToArray _vararg_count arr arrlen str oldstr len;
|
|
@copy sp arr;
|
|
@copy sp arrlen;
|
|
_vararg_count = _vararg_count - 2;
|
|
|
|
oldstr = glk_stream_get_current();
|
|
str = glk_stream_open_memory(arr, arrlen, 1, 0);
|
|
if (str == 0) return 0;
|
|
|
|
glk_stream_set_current(str);
|
|
|
|
@call Glulx_PrintAnything _vararg_count 0;
|
|
|
|
glk_stream_set_current(oldstr);
|
|
@copy $ffffffff sp;
|
|
@copy str sp;
|
|
@glk $0044 2 0; ! stream_close
|
|
@copy sp len;
|
|
@copy sp 0;
|
|
return len;
|
|
];
|
|
|
|
Constant GG_ANYTOSTRING_LEN 66;
|
|
Array AnyToStrArr -> GG_ANYTOSTRING_LEN+1;
|
|
|
|
[ Glulx_ChangeAnyToCString _vararg_count ix len;
|
|
ix = GG_ANYTOSTRING_LEN-2;
|
|
@copy ix sp;
|
|
ix = AnyToStrArr+1;
|
|
@copy ix sp;
|
|
ix = _vararg_count+2;
|
|
@call Glulx_PrintAnyToArray ix len;
|
|
AnyToStrArr->0 = $E0;
|
|
if (len >= GG_ANYTOSTRING_LEN)
|
|
len = GG_ANYTOSTRING_LEN-1;
|
|
AnyToStrArr->(len+1) = 0;
|
|
return AnyToStrArr;
|
|
];
|
|
|
|
@p The Screen.
|
|
Our generic screen model is that the screen is made up of windows: we tend
|
|
to refer only to two of these, the main window and the status line, but
|
|
others may also exist from time to time. Windows have unique ID numbers:
|
|
the special window ID $-1$ means "all windows" or "the entire screen",
|
|
which usually amounts to the same thing.
|
|
|
|
Screen height and width are measured in characters, with respect to the
|
|
fixed-pitch font used for the status line. The main window normally contains
|
|
variable-pitch text which may even have been kerned, and character dimensions
|
|
make little sense there.
|
|
|
|
@c
|
|
[ VM_ClearScreen window;
|
|
if (window == WIN_ALL or WIN_MAIN) {
|
|
glk_window_clear(gg_mainwin);
|
|
if (gg_quotewin) {
|
|
glk_window_close(gg_quotewin, 0);
|
|
gg_quotewin = 0;
|
|
}
|
|
}
|
|
if (gg_statuswin && window == WIN_ALL or WIN_STATUS) glk_window_clear(gg_statuswin);
|
|
];
|
|
|
|
[ VM_ScreenWidth id;
|
|
id=gg_mainwin;
|
|
if (gg_statuswin && statuswin_current) id = gg_statuswin;
|
|
glk_window_get_size(id, gg_arguments, 0);
|
|
return gg_arguments-->0;
|
|
];
|
|
|
|
[ VM_ScreenHeight;
|
|
glk_window_get_size(gg_mainwin, 0, gg_arguments);
|
|
return gg_arguments-->0;
|
|
];
|
|
|
|
@p Window Colours.
|
|
Our generic screen model is that the screen is made up of windows, each of
|
|
which can have its own foreground and background colours.
|
|
|
|
The colour of individual letters or words of type is not controllable in
|
|
Glulx, to the frustration of many, and so the template layer of I7 has no
|
|
framework for handling this (even though it is controllable on the Z-machine,
|
|
which is greatly superior in this respect).
|
|
|
|
@c
|
|
[ VM_SetWindowColours f b window doclear i fwd bwd swin;
|
|
if (clr_on && f && b) {
|
|
if (window) swin = 5-window; ! 4 for TextGrid, 3 for TextBuffer
|
|
|
|
fwd = MakeColourWord(f);
|
|
bwd = MakeColourWord(b);
|
|
for (i=0 : i<style_NUMSTYLES: i++) {
|
|
if (f == CLR_DEFAULT || b == CLR_DEFAULT) { ! remove style hints
|
|
glk_stylehint_clear(swin, i, stylehint_TextColor);
|
|
glk_stylehint_clear(swin, i, stylehint_BackColor);
|
|
} else {
|
|
glk_stylehint_set(swin, i, stylehint_TextColor, fwd);
|
|
glk_stylehint_set(swin, i, stylehint_BackColor, bwd);
|
|
}
|
|
}
|
|
|
|
! Now re-open the windows to apply the hints
|
|
if (gg_statuswin) glk_window_close(gg_statuswin, 0);
|
|
gg_statuswin = 0;
|
|
|
|
if (doclear || ( window ~= 1 && (clr_fg ~= f || clr_bg ~= b) ) ) {
|
|
glk_window_close(gg_mainwin, 0);
|
|
gg_mainwin = glk_window_open(0, 0, 0, wintype_TextBuffer, GG_MAINWIN_ROCK);
|
|
if (gg_scriptstr ~= 0)
|
|
glk_window_set_echo_stream(gg_mainwin, gg_scriptstr);
|
|
}
|
|
|
|
gg_statuswin =
|
|
glk_window_open(gg_mainwin, winmethod_Fixed + winmethod_Above,
|
|
statuswin_cursize, wintype_TextGrid, GG_STATUSWIN_ROCK);
|
|
if (statuswin_current && gg_statuswin) VM_MoveCursorInStatusLine(); else VM_MainWindow();
|
|
|
|
if (window ~= 2) {
|
|
clr_fgstatus = f;
|
|
clr_bgstatus = b;
|
|
}
|
|
if (window ~= 1) {
|
|
clr_fg = f;
|
|
clr_bg = b;
|
|
}
|
|
}
|
|
];
|
|
|
|
[ VM_RestoreWindowColours; ! used after UNDO: compare I6 patch L61007
|
|
if (clr_on) { ! check colour has been used
|
|
VM_SetWindowColours(clr_fg, clr_bg, 2); ! make sure both sets of variables are restored
|
|
VM_SetWindowColours(clr_fgstatus, clr_bgstatus, 1, true);
|
|
VM_ClearScreen();
|
|
}
|
|
];
|
|
|
|
[ MakeColourWord c;
|
|
if (c > 9) return c;
|
|
c = c-2;
|
|
return $ff0000*(c&1) + $ff00*(c&2 ~= 0) + $ff*(c&4 ~= 0);
|
|
];
|
|
|
|
@p Main Window.
|
|
The part of the screen on which commands and responses are printed, which
|
|
ordinarily occupies almost all of the screen area.
|
|
|
|
|VM_MainWindow()| switches printing back from another window, usually the
|
|
status line, to the main window.
|
|
|
|
@c
|
|
[ VM_MainWindow;
|
|
glk_set_window(gg_mainwin); ! set_window
|
|
statuswin_current=0;
|
|
];
|
|
|
|
@p Status Line.
|
|
Despite the name, the status line need not be a single line at the top of
|
|
the screen: that's only the conventional default arrangement. It can expand
|
|
to become the equivalent of an old-fashioned VT220 terminal, with menus
|
|
and grids and mazes displayed lovingly in character graphics, or it can
|
|
close up to invisibility.
|
|
|
|
|VM_StatusLineHeight(n)| sets the status line to have a height of |n| lines
|
|
of type. (The width of the status line is always the width of the whole
|
|
screen, and the position is always at the top, so the height is the only
|
|
controllable aspect.) The $n=0$ case makes the status line disappear.
|
|
|
|
|VM_MoveCursorInStatusLine(x, y)| switches printing to the status line,
|
|
positioning the "cursor" -- the position at which printing will begin --
|
|
at the given character grid position $(x, y)$. Line 1 represents the top
|
|
line; line 2 is underneath, and so on; columns are similarly numbered from
|
|
1 at the left.
|
|
|
|
@c
|
|
[ VM_StatusLineHeight hgt;
|
|
if (gg_statuswin == 0) return;
|
|
if (hgt == statuswin_cursize) return;
|
|
glk_window_set_arrangement(glk_window_get_parent(gg_statuswin), $12, hgt, 0);
|
|
statuswin_cursize = hgt;
|
|
];
|
|
|
|
[ VM_MoveCursorInStatusLine line column;
|
|
if (gg_statuswin == 0) return;
|
|
glk_set_window(gg_statuswin);
|
|
if (line == 0) { line = 1; column = 1; }
|
|
glk_window_move_cursor(gg_statuswin, column-1, line-1);
|
|
statuswin_current=1;
|
|
];
|
|
|
|
@p Quotation Boxes.
|
|
On the Z-machine, quotation boxes are produced by stretching the status line,
|
|
but on Glulx they usually occupy windows of their own. If it isn't possible
|
|
to create such a window, so that |gg_quotewin| is zero below, the quotation
|
|
text just appears in the main window.
|
|
|
|
@c
|
|
[ Box__Routine maxwid arr ix lines lastnl parwin;
|
|
maxwid = 0; ! squash compiler warning
|
|
lines = arr-->0;
|
|
|
|
if (gg_quotewin == 0) {
|
|
gg_arguments-->0 = lines;
|
|
ix = InitGlkWindow(GG_QUOTEWIN_ROCK);
|
|
if (ix == 0)
|
|
gg_quotewin =
|
|
glk_window_open(gg_mainwin, winmethod_Fixed + winmethod_Above,
|
|
lines, wintype_TextBuffer, GG_QUOTEWIN_ROCK);
|
|
} else {
|
|
parwin = glk_window_get_parent(gg_quotewin);
|
|
glk_window_set_arrangement(parwin, $12, lines, 0);
|
|
}
|
|
|
|
lastnl = true;
|
|
if (gg_quotewin) {
|
|
glk_window_clear(gg_quotewin);
|
|
glk_set_window(gg_quotewin);
|
|
lastnl = false;
|
|
}
|
|
|
|
VM_Style(BLOCKQUOTE_VMSTY);
|
|
for (ix=0 : ix<lines : ix++) {
|
|
print (string) arr-->(ix+1);
|
|
if (ix < lines-1 || lastnl) new_line;
|
|
}
|
|
VM_Style(NORMAL_VMSTY);
|
|
|
|
if (gg_quotewin) glk_set_window(gg_mainwin);
|
|
];
|
|
|
|
@p GlkList Command.
|
|
GLKLIST is a testing command best used by those who understand Glulx and its
|
|
ways: it isn't documented in the I7 manual, because it is pretty inscrutable
|
|
for "real" users, but it's probably worth keeping just the same.
|
|
|
|
@c
|
|
#Ifdef DEBUG;
|
|
[ GlkListSub id val;
|
|
id = glk_window_iterate(0, gg_arguments);
|
|
while (id) {
|
|
print "Window ", id, " (", gg_arguments-->0, "): ";
|
|
val = glk_window_get_type(id);
|
|
switch (val) {
|
|
1: print "pair";
|
|
2: print "blank";
|
|
3: print "textbuffer";
|
|
4: print "textgrid";
|
|
5: print "graphics";
|
|
default: print "unknown";
|
|
}
|
|
val = glk_window_get_parent(id);
|
|
if (val) print ", parent is window ", val;
|
|
else print ", no parent (root)";
|
|
val = glk_window_get_stream(id);
|
|
print ", stream ", val;
|
|
val = glk_window_get_echo_stream(id);
|
|
if (val) print ", echo stream ", val;
|
|
print "^";
|
|
id = glk_window_iterate(id, gg_arguments);
|
|
}
|
|
id = glk_stream_iterate(0, gg_arguments);
|
|
while (id) {
|
|
print "Stream ", id, " (", gg_arguments-->0, ")^";
|
|
id = glk_stream_iterate(id, gg_arguments);
|
|
}
|
|
id = glk_fileref_iterate(0, gg_arguments);
|
|
while (id) {
|
|
print "Fileref ", id, " (", gg_arguments-->0, ")^";
|
|
id = glk_fileref_iterate(id, gg_arguments);
|
|
}
|
|
if (glk_gestalt(gestalt_Sound, 0)) {
|
|
id = glk_schannel_iterate(0, gg_arguments);
|
|
while (id) {
|
|
print "Soundchannel ", id, " (", gg_arguments-->0, ")^";
|
|
id = glk_schannel_iterate(id, gg_arguments);
|
|
}
|
|
}
|
|
];
|
|
|
|
{-testing-command:glklist}
|
|
* -> Glklist;
|
|
#Endif;
|
|
|
|
@p Undo.
|
|
These are really emulations of the Z-machine's conventions on UNDO: Glulx's
|
|
undo opcodes used different result codes while providing essentially the same
|
|
functionality, for reasons which are opaque, but no trouble is caused thereby.
|
|
|
|
@c
|
|
[ VM_Undo result_code;
|
|
@restoreundo result_code;
|
|
return (~~result_code);
|
|
];
|
|
|
|
[ VM_Save_Undo result_code;
|
|
@saveundo result_code;
|
|
if (result_code == -1) { GGRecoverObjects(); return 2; }
|
|
return (~~result_code);
|
|
];
|
|
|
|
@p Quit The Game Rule.
|
|
|
|
@c
|
|
[ QUIT_THE_GAME_R;
|
|
if (actor ~= player) rfalse;
|
|
if ((actor == player) && (untouchable_silence == false))
|
|
QUIT_THE_GAME_RM('A');
|
|
if (YesOrNo()~=0) quit;
|
|
];
|
|
|
|
@p Restart The Game Rule.
|
|
|
|
@c
|
|
[ RESTART_THE_GAME_R;
|
|
if (actor ~= player) rfalse;
|
|
RESTART_THE_GAME_RM('A');
|
|
if (YesOrNo()~=0) {
|
|
@restart;
|
|
RESTART_THE_GAME_RM('B'); new_line;
|
|
}
|
|
];
|
|
|
|
@p Restore The Game Rule.
|
|
|
|
@c
|
|
[ RESTORE_THE_GAME_R res fref;
|
|
if (actor ~= player) rfalse;
|
|
fref = glk_fileref_create_by_prompt($01, $02, 0);
|
|
if (fref == 0) jump RFailed;
|
|
gg_savestr = glk_stream_open_file(fref, $02, GG_SAVESTR_ROCK);
|
|
glk_fileref_destroy(fref);
|
|
if (gg_savestr == 0) jump RFailed;
|
|
@restore gg_savestr res;
|
|
glk_stream_close(gg_savestr, 0);
|
|
gg_savestr = 0;
|
|
.RFailed;
|
|
RESTORE_THE_GAME_RM('A'); new_line;
|
|
];
|
|
|
|
@p Save The Game Rule.
|
|
|
|
@c
|
|
[ SAVE_THE_GAME_R res fref;
|
|
if (actor ~= player) rfalse;
|
|
fref = glk_fileref_create_by_prompt($01, $01, 0);
|
|
if (fref == 0) jump SFailed;
|
|
gg_savestr = glk_stream_open_file(fref, $01, GG_SAVESTR_ROCK);
|
|
glk_fileref_destroy(fref);
|
|
if (gg_savestr == 0) jump SFailed;
|
|
@save gg_savestr res;
|
|
if (res == -1) {
|
|
! The player actually just typed "restore". We first have to recover
|
|
! all the Glk objects; the values in our global variables are all wrong.
|
|
GGRecoverObjects();
|
|
glk_stream_close(gg_savestr, 0); ! stream_close
|
|
gg_savestr = 0;
|
|
RESTORE_THE_GAME_RM('B'); new_line;
|
|
rtrue;
|
|
}
|
|
glk_stream_close(gg_savestr, 0); ! stream_close
|
|
gg_savestr = 0;
|
|
if (res == 0) { SAVE_THE_GAME_RM('B'); new_line; rtrue; }
|
|
.SFailed;
|
|
SAVE_THE_GAME_RM('A'); new_line;
|
|
];
|
|
|
|
@p Verify The Story File Rule.
|
|
This is a fossil now, really, but in the days of Infocom, the 110K story
|
|
file occupying an entire disc was a huge data set: floppy discs were by no
|
|
means a reliable medium, and cheap hardware often used hit-and-miss
|
|
components, as on the notorious Commodore 64 disc controller. If somebody
|
|
experienced an apparent bug in play, it could easily be that he had a
|
|
corrupt disc or was unable to read data of that density. So the VERIFY
|
|
command, which took up to ten minutes on some early computers, would chug
|
|
through the entire story file and compute a checksum, compare it against a
|
|
known result in the header, and determine that the story file could or
|
|
could not properly be read. The Z-machine provided this service as an
|
|
opcode, and so Glulx followed suit.
|
|
|
|
@c
|
|
[ VERIFY_THE_STORY_FILE_R res;
|
|
if (actor ~= player) rfalse;
|
|
@verify res;
|
|
if (res == 0) { VERIFY_THE_STORY_FILE_RM('A'); new_line; rtrue; }
|
|
VERIFY_THE_STORY_FILE_RM('B'); new_line;
|
|
];
|
|
|
|
@p Switch Transcript On Rule.
|
|
|
|
@c
|
|
[ SWITCH_TRANSCRIPT_ON_R;
|
|
if (actor ~= player) rfalse;
|
|
if (gg_scriptstr ~= 0) { SWITCH_TRANSCRIPT_ON_RM('A'); new_line; rtrue; }
|
|
if (gg_scriptfref == 0) {
|
|
gg_scriptfref = glk_fileref_create_by_prompt($102, $05, GG_SCRIPTFREF_ROCK);
|
|
if (gg_scriptfref == 0) jump S1Failed;
|
|
}
|
|
! stream_open_file
|
|
gg_scriptstr = glk_stream_open_file(gg_scriptfref, $05, GG_SCRIPTSTR_ROCK);
|
|
if (gg_scriptstr == 0) jump S1Failed;
|
|
glk_window_set_echo_stream(gg_mainwin, gg_scriptstr);
|
|
SWITCH_TRANSCRIPT_ON_RM('B'); new_line;
|
|
VersionSub();
|
|
return;
|
|
.S1Failed;
|
|
SWITCH_TRANSCRIPT_ON_RM('C'); new_line;
|
|
];
|
|
|
|
@p Switch Transcript Off Rule.
|
|
|
|
@c
|
|
[ SWITCH_TRANSCRIPT_OFF_R;
|
|
if (actor ~= player) rfalse;
|
|
if (gg_scriptstr == 0) { SWITCH_TRANSCRIPT_OFF_RM('A'); new_line; rtrue; }
|
|
SWITCH_TRANSCRIPT_OFF_RM('B'); new_line;
|
|
glk_stream_close(gg_scriptstr, 0); ! stream_close
|
|
gg_scriptstr = 0;
|
|
];
|
|
|
|
@p Announce Story File Version Rule.
|
|
|
|
@c
|
|
[ ANNOUNCE_STORY_FILE_VERSION_R ix;
|
|
if (actor ~= player) rfalse;
|
|
Banner();
|
|
print "Identification number: ";
|
|
for (ix=6: ix <= UUID_ARRAY->0: ix++) print (char) UUID_ARRAY->ix;
|
|
print "^";
|
|
@gestalt 1 0 ix;
|
|
print "Interpreter version ", ix / $10000, ".", (ix & $FF00) / $100,
|
|
".", ix & $FF, " / ";
|
|
@gestalt 0 0 ix;
|
|
print "VM ", ix / $10000, ".", (ix & $FF00) / $100, ".", ix & $FF, " / ";
|
|
print "Library serial number ", (string) LibSerial, "^";
|
|
#Ifdef LanguageVersion;
|
|
print (string) LanguageVersion, "^";
|
|
#Endif; ! LanguageVersion
|
|
ShowExtensionVersions();
|
|
say__p = 1;
|
|
];
|
|
|
|
@p Descend To Specific Action Rule.
|
|
There are 100 or so actions, typically, and this rule is for efficiency's
|
|
sake: rather than perform 100 or so comparisons to see which routine to
|
|
call, we indirect through a jump table. The routines called are the |-Sub|
|
|
routines: thus, for instance, if |action| is |##Wait| then |WaitSub| is
|
|
called. It is essential that this routine not be called for fake actions:
|
|
in I7 use this is guaranteed, since fake actions are not allowed into the
|
|
action machinery at all.
|
|
|
|
Strangely, Glulx's action routines table is numbered in an off-by-one way
|
|
compared to the Z-machine's: hence the |+1|.
|
|
|
|
@c
|
|
[ DESCEND_TO_SPECIFIC_ACTION_R;
|
|
indirect(#actions_table-->(action+1));
|
|
rtrue;
|
|
];
|
|
|
|
@p Veneer.
|
|
|
|
@c
|
|
[ Unsigned__Compare x y;
|
|
@jleu x y ?lesseq;
|
|
return 1;
|
|
.lesseq;
|
|
@jeq x y ?equal;
|
|
return -1;
|
|
.equal;
|
|
return 0;
|
|
];
|
|
|
|
[ RT__ChLDW x y;
|
|
@aload x y sp;
|
|
@return sp;
|
|
];
|
|
|
|
[ RT__ChLDB x y;
|
|
@aloadb x y sp;
|
|
@return sp;
|
|
];
|