scummvm/engines/glk/jacl/jacl_main.cpp

1553 lines
38 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#include "glk/jacl/jacl.h"
#include "glk/jacl/csv.h"
#include "glk/jacl/types.h"
#include "glk/jacl/language.h"
#include "glk/jacl/prototypes.h"
#include "glk/jacl/version.h"
namespace Glk {
namespace JACL {
int convert_to_utf32(unsigned char *text);
uint status_width, status_height;
schanid_t sound_channel[8] = { NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL
};
event_t *cancelled_event;
extern struct csv_parser parser_csv;
extern char text_buffer[];
extern const char *word[];
extern short int quoted[];
extern short int punctuated[];
extern int wp;
extern int custom_error;
extern int interrupted;
extern int jpp_error;
extern int it;
extern int them[];
extern int her;
extern int him;
extern int oops_word;
#ifdef WINGLK
struct string_type *resolved_string;
#endif
char include_directory[81] = "\0";
char temp_directory[81] = "\0";
char data_directory[81] = "\0";
char special_prompt[81] = "\n: \0";
char file_prompt[5] = ": \0";
char bookmark[81] = "\0";
char walkthru[81] = "\0";
char function_name[81];
extern char default_function[84];
char override_[81];
char temp_buffer[1024];
char error_buffer[1024];
unsigned char chunk_buffer[4096];
#ifndef NOUNICODE
glui32 chunk_buffer_uni[4096];
#endif
char proxy_buffer[1024];
char oops_buffer[1024];
char oopsed_current[1024];
char last_command[1024];
const char *blank_command = "blankjacl\0";
const char *current_command = (const char *)NULL;
char command_buffer[1024];
#ifndef NOUNICODE
glui32 command_buffer_uni[1024];
#endif
char players_command[1024];
int walkthru_running = FALSE;
int start_of_last_command;
int start_of_this_command;
int objects, integers, functions, strings;
int jpp_error = FALSE;
/* A STREAM FOR THE GAME FILE, WHEN IT'S OPEN. */
strid_t game_stream = NULL;
/* THE STREAM FOR OPENING UP THE ARCHIVE CONTAINING GRAPHICS AND SOUND */
strid_t blorb_stream;
/* A FILE REFERENCE FOR THE TRANSCRIPT FILE. */
static frefid_t script_fref = NULL;
/* A STREAM FOR THE TRANSCRIPT FILE, WHEN IT'S OPEN. */
static strid_t script_stream = NULL;
int noun[4];
int player = 0;
int noun3_backup;
int player_backup = 0;
int variable_contents;
int oec;
int *object_element_address,
*object_backup_address;
short int spaced = TRUE;
int delay = 0;
/* START OF GLK STUFF */
/* POINTERS TO THE GLK WINDOWS */
winid_t mainwin = NULL;
winid_t statuswin = NULL;
winid_t promptwin = NULL;
winid_t inputwin = NULL;
winid_t current_window = NULL;
/* POINTERS TO THE WINDOWS STREAMS */
strid_t mainstr = NULL;
strid_t statusstr = NULL;
strid_t promptstr = NULL;
strid_t inputstr = NULL;
/* END OF GLK STUFF */
char user_id[] = "local";
char prefix[81] = "\0";
char blorb[81] = "\0";
char game_path[256] = "\0";
char game_file[256] = "\0";
char processed_file[256] = "\0";
struct object_type *object[MAX_OBJECTS];
struct integer_type *integer_table = NULL;
struct cinteger_type *cinteger_table = NULL;
struct window_type *window_table = NULL;
struct attribute_type *attribute_table = NULL;
struct string_type *string_table = NULL;
struct string_type *cstring_table = NULL;
struct function_type *function_table = NULL;
struct function_type *executing_function = NULL;
struct command_type *completion_list = NULL;
struct word_type *grammar_table = NULL;
struct synonym_type *synonym_table = NULL;
struct filter_type *filter_table = NULL;
// Forward declarations
static void word_check();
static void version_info();
void glk_main() {
int index;
override_[0] = 0;
/* ALLOC AN EVENT TO STORE A CANCELLED EVENT IN */
if ((cancelled_event = (event_t *) malloc(sizeof(event_t))) == NULL)
outofmem();
/* CREATE style_User1 FOR USE IN THE STATUS LINE */
g_vm->glk_stylehint_set(wintype_TextGrid, style_User1, stylehint_ReverseColor, 1);
g_vm->glk_stylehint_set(wintype_TextBuffer, style_User2, stylehint_ReverseColor, 1);
/* OPEN THE MAIN WINDOW THE GLK WINDOWS */
mainwin = g_vm->glk_window_open(0, 0, 0, wintype_TextBuffer, 1);
if (!mainwin) {
/* IT'S POSSIBLE THAT THE MAIN WINDOW FAILED TO OPEN. THERE's
* NOTHING WE CAN DO WITHOUT IT, SO EXIT. */
return;
} else {
/* GET A REFERENCE TO mainwin's STREAM */
mainstr = g_vm->glk_window_get_stream(mainwin);
}
/* SET THE CURRENT OUTPUT STREAM TO PRINT TO IT. */
jacl_set_window(mainwin);
/* OPEN A THIRD WINDOW: A TEXT GRID, BELOW THE MAIN WINDOW, ONE LINE
* HIGH. THIS IS THE WINDOW TO DISPLAY THE COMMAND PROMPT IN */
//promptwin = g_vm->glk_window_open(mainwin, winmethod_Below | winmethod_Fixed,
// 3, wintype_TextBuffer, 0);
/* SET THIS TO DETERMINE THE SYTEM OF INPUT TO USE */
//inputwin = promptwin;
inputwin = mainwin;
if (jpp_error) {
/* THERE WAS AN ERROR DURING PREPROCESSING. NOW THAT THERE IS AN
* OPEN GLK WINDOW, OUTPUT THE ERROR MESSAGE AND EXIT */
log_error(error_buffer, FALSE);
terminate(200);
return;
}
// INTIALISE THE CSV PARSER
csv_init(&parser_csv, CSV_APPEND_NULL);
/* NO PREPROCESSOR ERRORS, LOAD THE GAME FILE */
read_gamefile();
execute("+bootstrap");
// OPEN A SECOND WINDOW: A TEXT GRID, ABOVE THE MAIN WINDOW, ONE LINE
// HIGH. IT IS POSSIBLE THAT THIS WILL FAIL ALSO, BUT WE ACCEPT THAT.
statuswin = g_vm->glk_window_open(mainwin, winmethod_Above | winmethod_Fixed,
0, wintype_TextGrid, 0);
// GET A REFERENCE TO statuswin's STREAM
if (statuswin != NULL) {
statusstr = g_vm->glk_window_get_stream(statuswin);
}
#ifdef WINGLK
if ((resolved_string = cstring_resolve("game_title")) != NULL) {
wing_vm->glk_window_set_title(resolved_string->value);
} else {
sprintf(temp_buffer, "JACL v%d.%d.%d ", J_VERSION, J_RELEASE, J_BUILD);
wing_vm->glk_window_set_title(temp_buffer);
}
#endif
if (SOUND_SUPPORTED->value) {
/* CREATE THE EIGHT SOUND CHANNELS */
for (index = 0; index < 8; index++) {
sound_channel[index] = g_vm->glk_schannel_create(0);
}
}
jacl_set_window(mainwin);
execute("+intro");
if (object[2] == NULL) {
log_error(CANT_RUN, PLUS_STDERR);
terminate(43);
return;
}
/* DUMMY RETRIEVE OF 'HERE' FOR TESTING OF GAME STATE */
get_here();
eachturn();
/* TOP OF COMMAND LOOP */
while (!g_vm->shouldQuit()) {
int gotline;
event_t ev;
custom_error = FALSE;
jacl_set_window(mainwin);
execute("+bottom");
status_line();
if (current_command != NULL) {
strcpy(last_command, current_command);
}
if (inputwin == promptwin) {
g_vm->glk_window_clear(promptwin);
jacl_set_window(inputwin);
}
// If loading a savegame from the launcher, do it now
if (g_vm->loadingSavegame()) {
// Load the game
if (g_vm->loadLauncherSavegame()) {
// Do a look action
const uint32 LOOK[5] = { 'l', 'o', 'o', 'k', 0 };
Common::copy(LOOK, LOOK + 5, command_buffer_uni);
ev.val1 = 4;
} else {
continue;
}
} else {
/* OUTPUT THE CUSTOM COMMAND PROMPT */
write_text(string_resolve("command_prompt")->value);
#ifdef NOUNICODE
g_vm->glk_request_line_event(inputwin, command_buffer, 255, 0);
#else
g_vm->glk_request_line_event_uni(inputwin, command_buffer_uni, 255, 0);
#endif
jacl_set_window(inputwin);
gotline = FALSE;
while (!gotline) {
/* GRAB AN EVENT. */
g_vm->glk_select(&ev);
if (g_vm->shouldQuit())
return;
switch (ev.type) {
case evtype_LineInput:
if (ev.window == inputwin) {
gotline = TRUE;
jacl_set_window(mainwin);
/* REALLY THE EVENT CAN *ONLY* BE FROM MAINWIN,
* BECAUSE WE NEVER REQUEST LINE INPUT FROM THE
* STATUS WINDOW. BUT WE DO A PARANOIA TEST,
* BECAUSE COMMANDBUF IS ONLY FILLED IF THE LINE
* EVENT COMES FROM THE MAINWIN REQUEST. IF THE
* LINE EVENT COMES FROM ANYWHERE ELSE, WE IGNORE
* IT. */
}
break;
case evtype_SoundNotify:
/* A SOUND HAS FINISHED PLAYING CALL +sound_finished
* WITH THE RESOUCE NUMBER AS THE FIRST ARGUMENT
* AND THE CHANNEL NUMBER AS THE SECOND ARGUMENT */
sprintf(temp_buffer, "+sound_finished<%d<%d", (int) ev.val1, (int) ev.val2 - 1);
execute(temp_buffer);
break;
case evtype_Timer:
/* A TIMER EVENT IS TRIGGERED PERIODICALLY IF THE GAME
* REQUESTS THEM. THIS SIMPLY EXECUTES THE FUNCTION
* +timer WHICH IS LIKE +eachturn EXCEPT IT DOESN'T
* WAIT FOR THE PLAYER TO TYPE A COMMAND */
jacl_set_window(mainwin);
execute("+timer");
break;
case evtype_Arrange:
/* WINDOWS HAVE CHANGED SIZE, SO WE HAVE TO REDRAW THE
* STATUS WINDOW. */
status_line();
break;
default:
break;
}
}
}
// THE PLAYER'S INPUT WILL BE UTF-32. CONVERT IT TO UTF-8 AND NULL TERMINATE IT
#ifndef NOUNICODE
convert_to_utf8(command_buffer_uni, ev.val1);
#endif
current_command = command_buffer;
/* SET ALL THE OUTPUT TO GO TO mainwin NOW THE COMMAND HAS BEEN READ */
if (inputwin == promptwin) {
jacl_set_window(mainwin);
write_text(string_resolve("command_prompt")->value);
g_vm->glk_set_style(style_Input);
write_text(current_command);
g_vm->glk_set_style(style_Normal);
write_text("^");
}
execute("+top");
index = 0;
if (*current_command) {
while (*(current_command + index) && index < 1024) {
if (*(current_command + index) == '\r' || *(current_command + index) == '\n') {
break;
} else {
text_buffer[index] = *(current_command + index);
index++;
}
}
}
text_buffer[index] = 0;
if (text_buffer[0] == 0) {
/* NO COMMAND WAS SPECIFIED, FILL THE COMMAND IN AS 'blankjacl'
* FOR THE GAME TO PROCESS AS DESIRED */
strcpy(text_buffer, "blankjacl");
current_command = blank_command;
}
command_encapsulate();
jacl_truncate();
index = 0;
/* SET THE INTEGER INTERRUPTED TO FALSE. IF THIS IS SET TO
* TRUE BY ANY COMMAND, FURTHER PROCESSING WILL STOP */
INTERRUPTED->value = FALSE;
interrupted = FALSE;
if (word[0] != NULL) {
if (strcmp(word[0], "undo")) {
/* COMMAND DOES NOT EQUAL undo */
save_game_state();
}
if (word[0][0] == '*') {
if (script_stream) {
write_text(cstring_resolve("COMMENT_RECORDED")->value);
} else {
write_text(cstring_resolve("COMMENT_IGNORED")->value);
}
} else {
/* COMMAND IS NOT NULL, START PROCESSING IT */
preparse();
}
} else {
/* NO COMMAND WAS SPECIFIED, FILL THE COMMAND IN AS 'blankjacl'
* FOR THE GAME TO PROCESS AS DESIRED */
strcpy(text_buffer, "blankjacl");
command_encapsulate();
preparse();
}
}
}
void preparse() {
int position;
// THE INTERRUPTED VARIABLE IS USED TO STOP LATER ACTIONS IN A COMMAND
// IF ANY ONE
while (word[wp] != NULL && INTERRUPTED->value == FALSE) {
//printf("--- preparse %s\n", word[wp]);
// PROCESS THE CURRENT COMMAND
// CREATE THE command STRINGS FROM THIS POINT ONWARDS SO THE VERB OF
// THE CURRENT COMMAND IS ALWAYS command[0].
clear_cstring("command");
position = wp;
while (word[position] != NULL && strcmp(word[position], cstring_resolve("THEN_WORD")->value)) {
add_cstring("command", word[position]);
position++;
};
// PROCESS THE COMMAND
word_check();
/* THE PREVIOUS COMMAND HAS FINISHED, LOOK FOR ANOTHER COMMAND */
while (word[wp] != NULL) {
if (word[wp] != NULL && !strcmp(word[wp], cstring_resolve("THEN_WORD")->value)) {
wp++;
break;
}
wp++;
}
}
}
void word_check() {
int index;
/* REMEMBER THE START OF THIS COMMAND TO SUPPORT 'oops' AND 'again' */
start_of_this_command = wp;
//printf("--- command starts at %d\n", start_of_this_command);
/* START CHECKING THE PLAYER'S COMMAND FOR SYSTEM COMMANDS */
if (!strcmp(word[wp], cstring_resolve("QUIT_WORD")->value) || !strcmp(word[wp], "q")) {
if (execute("+quit_game") == FALSE) {
TIME->value = FALSE;
write_text(cstring_resolve("SURE_QUIT")->value);
if (get_yes_or_no()) {
newline();
execute("+score");
terminate(0);
return;
} else {
write_text(cstring_resolve("RETURN_GAME")->value);
}
}
} else if (!strcmp(word[wp], cstring_resolve("RESTART_WORD")->value)) {
if (execute("+restart_game") == FALSE) {
TIME->value = FALSE;
write_text(cstring_resolve("SURE_RESTART")->value);
if (get_yes_or_no()) {
write_text(cstring_resolve("RESTARTING")->value);
restart_game();
g_vm->glk_window_clear(current_window);
execute("+intro");
eachturn();
} else {
write_text(cstring_resolve("RETURN_GAME")->value);
}
}
} else if (!strcmp(word[wp], cstring_resolve("UNDO_WORD")->value)) {
if (execute("+undo_move") == FALSE) {
undoing();
}
} else if (!strcmp(word[wp], cstring_resolve("OOPS_WORD")->value) || !strcmp(word[wp], "o")) {
//printf("--- oops word is %d\n", oops_word);
if (word[++wp] != NULL) {
if (oops_word == -1) {
if (TOTAL_MOVES->value == 0) {
write_text(cstring_resolve("NO_MOVES")->value);
TIME->value = FALSE;
} else {
write_text(cstring_resolve("CANT_CORRECT")->value);
TIME->value = FALSE;
}
} else {
strcpy(oops_buffer, word[wp]);
strcpy(text_buffer, last_command);
command_encapsulate();
//printf("--- trying to replace %s with %s\n", word[oops_word], oops_buffer);
jacl_truncate();
word[oops_word] = (char *)&oops_buffer;
/* BUILD A PLAIN STRING REPRESENTING THE NEW COMMAND */
oopsed_current[0] = 0;
index = 0;
while (word[index] != NULL) {
if (oopsed_current[0] != 0) {
strcat(oopsed_current, " ");
}
strcat(oopsed_current, word[index]);
index++;
}
current_command = oopsed_current;
//printf("--- current command is: %s\n", current_command);
/* PROCESS THE FIXED COMMAND ONLY */
wp = start_of_last_command;
word_check();
}
} else {
write_text(cstring_resolve("BAD_OOPS")->value);
TIME->value = FALSE;
}
} else if (!strcmp(word[wp], cstring_resolve("AGAIN_WORD")->value) || !strcmp(word[wp], "g")) {
if (TOTAL_MOVES->value == 0) {
write_text(cstring_resolve("NO_MOVES")->value);
TIME->value = FALSE;
} else if (last_command[0] == 0) {
write_text(cstring_resolve("NOT_CLEVER")->value);
TIME->value = FALSE;
} else {
strcpy(text_buffer, last_command);
current_command = last_command;
command_encapsulate();
jacl_truncate();
//printf("--- command started at %d\n", start_of_last_command);
wp = start_of_last_command;
word_check();
}
} else if (!strcmp(word[wp], cstring_resolve("SCRIPT_WORD")->value) || !strcmp(word[wp], "transcript")) {
scripting();
} else if (!strcmp(word[wp], cstring_resolve("UNSCRIPT_WORD")->value)) {
if (!script_stream) {
write_text(cstring_resolve("SCRIPTING_ALREADY_OFF")->value);
} else {
/* Close the file. */
g_vm->glk_put_string_stream(script_stream, "\nEND OF A TRANSCRIPT\n");
g_vm->glk_stream_close(script_stream, NULL);
write_text(cstring_resolve("SCRIPTING_OFF")->value);
script_stream = NULL;
}
} else if (!strcmp(word[wp], cstring_resolve("WALKTHRU_WORD")->value)) {
walking_thru();
} else if (!strcmp(word[wp], cstring_resolve("INFO_WORD")->value) || !strcmp(word[wp], "version")) {
version_info();
write_text("you can redistribute it and/or modify it under the ");
write_text("terms of the GNU General Public License as published by ");
write_text("the Free Software Foundation; either version 2 of the ");
write_text("License, or any later version.^^");
write_text("This program is distributed in the hope that it will be ");
write_text("useful, but WITHOUT ANY WARRANTY; without even the ");
write_text("implied warranty of MERCHANTABILITY or FITNESS FOR A ");
write_text("PARTICULAR PURPOSE. See the GNU General Public License ");
write_text("for more details.^^");
write_text("You should have received a copy of the GNU General ");
write_text("Public License along with this program; if not, write ");
write_text("to the Free Software Foundation, Inc., 675 Mass Ave, ");
write_text("Cambridge, MA 02139, USA.^^");
sprintf(temp_buffer, "OBJECTS DEFINED: %d^", objects);
write_text(temp_buffer);
TIME->value = FALSE;
} else {
/* NO WORD HAS BEEN MARKED AS AN ERROR YET*/
oops_word = -1;
/* THIS IS NOT A SYSTEM COMMAND, CALL parser TO PROCESS THE COMMAND */
parser();
}
start_of_last_command = start_of_this_command;
}
void version_info() {
char buffer[80];
sprintf(buffer, "JACL Interpreter v%d.%d.%d ", J_VERSION, J_RELEASE,
J_BUILD);
write_text(buffer);
sprintf(buffer, "/ %d object.^", MAX_OBJECTS);
write_text(buffer);
write_text("Copyright (c) 1992-2010 Stuart Allen.^^");
}
void save_game_state() {
/* THIS FUNCTION MAKES AN IN-MEMORY COPY OF THE GAME STATE AFTER EACH
* OF THE PLAYER'S COMMANDS SO THE 'undo' COMMAND CAN BE USED */
int index,
counter;
struct integer_type *current_integer = integer_table;
struct function_type *current_function = function_table;
do {
current_function->call_count_backup = current_function->call_count;
current_function = current_function->next_function;
} while (current_function != NULL);
do {
current_integer->value_backup = current_integer->value;
current_integer = current_integer->next_integer;
} while (current_integer != NULL);
for (index = 1; index <= objects; index++) {
if (object[index]->nosave)
continue;
for (counter = 0; counter < 16; counter++) {
object[index]->integer_backup[counter] =
object[index]->integer[counter];
}
object[index]->attributes_backup = object[index]->attributes;
object[index]->user_attributes_backup = object[index]->user_attributes;
}
player_backup = player;
noun3_backup = noun[3];
}
int save_interaction() {
if (g_vm->saveGame().getCode() == Common::kNoError) {
return (TRUE);
} else {
write_text(cstring_resolve("CANT_SAVE")->value);
return (FALSE);
}
}
void restore_game_state() {
/* THIS FUNCTION IS CALLED AS A RESULT OF THE PLAYER USING THE 'undo'
* COMMAND */
int index,
counter;
struct integer_type *current_integer = integer_table;
struct function_type *current_function = function_table;
do {
current_function->call_count = current_function->call_count_backup;
current_function = current_function->next_function;
} while (current_function != NULL);
do {
current_integer->value = current_integer->value_backup;
current_integer = current_integer->next_integer;
} while (current_integer != NULL);
for (index = 1; index <= objects; index++) {
if (object[index]->nosave)
continue;
for (counter = 0; counter < 16; counter++)
object[index]->integer[counter] =
object[index]->integer_backup[counter];
object[index]->attributes = object[index]->attributes_backup;
object[index]->user_attributes = object[index]->user_attributes_backup;
}
player = player_backup;
noun[3] = noun3_backup;
write_text(cstring_resolve("MOVE_UNDONE")->value);
object[HERE]->attributes &= ~1;
execute("+top");
execute("+look_around");
execute("+bottom");
TIME->value = FALSE;
}
void write_text(const char *string_buffer) {
int index, length;
if (g_vm->loadingSavegame())
return;
if (!strcmp(string_buffer, "tilde")) {
g_vm->glk_put_string("~");
return;
} else if (!strcmp(string_buffer, "caret")) {
g_vm->glk_put_string("^");
return;
}
length = strlen(string_buffer);
for (index = 0; index < length; index++) {
if (*(string_buffer + index) == '^') {
chunk_buffer[index] = '\n';
} else if (*(string_buffer + index) == '~') {
chunk_buffer[index] = '\"';
} else {
chunk_buffer[index] = *(string_buffer + index);
}
}
chunk_buffer[index] = 0;
/* PRINT THE CONTENTS OF string_buffer */
#ifdef NOUNICODE
g_vm->glk_put_string(chunk_buffer);
#else
chunk_buffer_uni[(glui32) convert_to_utf32(chunk_buffer)] = 0;
g_vm->glk_put_string_uni(chunk_buffer_uni);
#endif
}
void jacl_sleep(unsigned int mseconds) {
g_system->delayMillis(mseconds);
}
void status_line() {
int cursor, index;
winid_t pair_window;
if (!statuswin) {
return;
} else {
// THERE IS AN EXISTING STATUS WINDOW, MAKE SURE A NEW SIZE HASN'T BEEN
// REQUESTED
g_vm->glk_window_get_size(statuswin, &status_width, &status_height);
if (status_height != (uint)integer_resolve("status_window")->value) {
// HEIGHT HAS CHANGED, UPDATE THE WINDOW
pair_window = g_vm->glk_window_get_parent(statuswin);
g_vm->glk_window_set_arrangement(pair_window, winmethod_Above | winmethod_Fixed, integer_resolve("status_window")->value, statuswin);
g_vm->glk_window_get_size(statuswin, &status_width, &status_height);
}
}
if (status_height == 0) {
// THE STATUS WINDOW CAN'T BE CLOSED, ONLY SET TO HAVE A HEIGHT OF ZERO
return;
}
jacl_set_window(statuswin);
g_vm->glk_window_clear(statuswin);
if (execute("+update_status_window") == FALSE) {
g_vm->glk_set_style(style_User1);
/* DISPLAY THE INVERSE STATUS LINE AT THE TOP OF THE SCREEN */
for (index = 0; index < (int)status_width; index++) {
temp_buffer[index] = ' ';
}
temp_buffer[index] = 0;
write_text(temp_buffer);
/* PRINT THE LOCATION'S TITLE ON THE LEFT. */
g_vm->glk_window_move_cursor(statuswin, 1, 0);
write_text(sentence_output(HERE, TRUE));
/* BUILD THE SCORE/ MOVES STRING */
temp_buffer[0] = 0;
sprintf(temp_buffer, "Score: %d Moves: %d", SCORE->value, TOTAL_MOVES->value);
cursor = status_width - strlen(temp_buffer);
cursor--;
g_vm->glk_window_move_cursor(statuswin, cursor, 0);
write_text(temp_buffer);
}
jacl_set_window(mainwin);
}
void newline() {
/* START A NEW LINE ON THE SCREEN */
write_text("\n");
}
void more(const char *message) {
jacl_set_window(inputwin);
if (inputwin == promptwin) {
g_vm->glk_window_clear(promptwin);
newline();
}
g_vm->glk_set_style(style_Emphasized);
write_text(message);
g_vm->glk_set_style(style_Normal);
(void)get_key();
if (inputwin == mainwin) newline();
}
int get_key() {
event_t ev;
g_vm->glk_request_char_event(inputwin);
while (!g_vm->shouldQuit()) {
g_vm->glk_select(&ev);
switch (ev.type) {
case evtype_CharInput:
if (ev.window == inputwin) {
return (ev.val1);
}
break;
default:
break;
}
}
return 0;
}
int get_number(int insist, int low, int high) {
char *cx;
char commandbuf[256];
int response;
int gotline;
event_t ev;
status_line();
sprintf(temp_buffer, cstring_resolve("TYPE_NUMBER")->value, low, high);
/* THIS LOOP IS IDENTICAL TO THE MAIN COMMAND LOOP IN g_vm->glk_main(). */
while (1) {
if (inputwin == promptwin) {
g_vm->glk_window_clear(promptwin);
jacl_set_window(inputwin);
}
write_text(temp_buffer);
jacl_set_window(mainwin);
g_vm->glk_request_line_event(inputwin, commandbuf, 255, 0);
gotline = FALSE;
while (!gotline && g_vm->shouldQuit()) {
g_vm->glk_select(&ev);
switch (ev.type) {
case evtype_LineInput:
if (ev.window == inputwin) {
gotline = TRUE;
}
break;
case evtype_Arrange:
status_line();
break;
default:
break;
}
}
commandbuf[ev.val1] = '\0';
for (cx = commandbuf; *cx == ' '; cx++) { };
if (validate(cx)) {
response = atoi(cx);
if (response >= low && response <= high) {
return (response);
}
}
if (!insist) {
return (-1);
} else {
write_text(cstring_resolve("INVALID_SELECTION")->value);
}
}
}
void get_string(char *string_buffer) {
char *cx;
char commandbuf[256];
int gotline;
event_t ev;
status_line();
/* THIS LOOP IS IDENTICAL TO THE MAIN COMMAND LOOP IN g_vm->glk_main(). */
if (inputwin == promptwin) {
g_vm->glk_window_clear(promptwin);
jacl_set_window(inputwin);
}
jacl_set_window(mainwin);
g_vm->glk_request_line_event(inputwin, commandbuf, 255, 0);
gotline = FALSE;
while (!gotline && !g_vm->shouldQuit()) {
g_vm->glk_select(&ev);
switch (ev.type) {
case evtype_LineInput:
if (ev.window == inputwin) {
gotline = TRUE;
}
break;
case evtype_Arrange:
status_line();
break;
default:
break;
}
}
commandbuf[ev.val1] = '\0';
for (cx = commandbuf; *cx == ' '; cx++) { };
// COPY UP TO 255 BYTES OF THE ENTERED TEXT INTO THE SUPPLIED STRING
strncpy(string_buffer, cx, 255);
}
int get_yes_or_no() {
char *cx;
char commandbuf[256];
int gotline;
event_t ev;
status_line();
/* THIS LOOP IS IDENTICAL TO THE MAIN COMMAND LOOP IN g_vm->glk_main(). */
while (1) {
if (inputwin == promptwin) {
g_vm->glk_window_clear(promptwin);
jacl_set_window(inputwin);
}
write_text(cstring_resolve("YES_OR_NO")->value);
jacl_set_window(mainwin);
g_vm->glk_request_line_event(inputwin, commandbuf, 255, 0);
gotline = FALSE;
while (!gotline && !g_vm->shouldQuit()) {
g_vm->glk_select(&ev);
switch (ev.type) {
case evtype_LineInput:
if (ev.window == inputwin) {
gotline = TRUE;
}
break;
case evtype_Arrange:
status_line();
break;
default:
break;
}
}
commandbuf[ev.val1] = '\0';
for (cx = commandbuf; *cx == ' '; cx++) { };
// PUSH THE FIRST NON-SPACE CHARACTER TO LOWER FOR COMPARISON
// WITH CONSTANT
*cx = tolower(*cx);
if (*cx == cstring_resolve("YES_WORD")->value[0]) {
return TRUE;
} else if (*cx == cstring_resolve("NO_WORD")->value[0]) {
return FALSE;
}
}
}
char get_character(const char *message) {
char *cx;
char commandbuf[256];
int gotline;
event_t ev;
status_line();
/* THIS LOOP IS IDENTICAL TO THE MAIN COMMAND LOOP IN g_vm->glk_main(). */
while (!g_vm->shouldQuit()) {
if (inputwin == promptwin) {
g_vm->glk_window_clear(promptwin);
jacl_set_window(inputwin);
}
write_text(message);
g_vm->glk_request_line_event(inputwin, commandbuf, 255, 0);
jacl_set_window(mainwin);
gotline = FALSE;
while (!gotline && !g_vm->shouldQuit()) {
g_vm->glk_select(&ev);
switch (ev.type) {
case evtype_LineInput:
if (ev.window == inputwin) {
gotline = TRUE;
}
break;
case evtype_Arrange:
status_line();
break;
default:
break;
}
}
commandbuf[ev.val1] = '\0';
for (cx = commandbuf; *cx == ' '; cx++) { };
return (*cx);
}
return '\0';
}
strid_t open_glk_file(uint usage, uint mode, char *filename) {
frefid_t file_reference;
strid_t stream_reference;
file_reference = g_vm->glk_fileref_create_by_name(usage, filename, 0);
if (file_reference) {
stream_reference = g_vm->glk_stream_open_file(file_reference, (FileMode)mode, 0);
if (stream_reference) {
/* WE'RE DONE WITH THE FILE REFERENCE NOW THAT THE STREAM
* HAS BEEN SUCCESSFULLY OPENED */
g_vm->glk_fileref_destroy(file_reference);
return (stream_reference);
}
}
return (strid_t) NULL;
}
void scripting() {
if (script_stream) {
write_text(cstring_resolve("SCRIPTING_ALREADY_ON")->value);
return;
}
/* IF WE'VE TURNED ON SCRIPTING BEFORE, USE THE SAME FILE REFERENCE;
* OTHERWISE, PROMPT THE PLAYER FOR A FILE. */
if (!script_fref) {
script_fref = g_vm->glk_fileref_create_by_prompt(
fileusage_Transcript | fileusage_TextMode,
filemode_WriteAppend, 0);
if (!script_fref) {
write_text(cstring_resolve("CANT_WRITE_SCRIPT")->value);
return;
}
}
/* OPEN THE TRANSCRIPT FILE */
script_stream = g_vm->glk_stream_open_file(script_fref, filemode_WriteAppend, 0);
if (!script_stream) {
write_text(cstring_resolve("CANT_WRITE_SCRIPT")->value);
return;
}
write_text(cstring_resolve("SCRIPTING_ON")->value);
g_vm->glk_window_set_echo_stream(mainwin, script_stream);
g_vm->glk_put_string_stream(script_stream, "TRANSCRIPT OF: ");
g_vm->glk_put_string_stream(script_stream, cstring_resolve("game_title")->value);
g_vm->glk_put_string_stream(script_stream, "\n");
}
void undoing() {
if (TOTAL_MOVES->value && strcmp(last_command, cstring_resolve("UNDO_WORD")->value)) {
restore_game_state();
} else {
write_text(cstring_resolve("NO_UNDO")->value);
TIME->value = FALSE;
}
}
void walking_thru() {
int result, index;
int length;
char script_line[81];
/* A FILE REFERENCE FOR THE WALKTHRU FILE. */
frefid_t walkthru_fref = NULL;
/* A STREAM FOR THE WALKTHRU FILE, WHEN IT'S OPEN. */
strid_t walkthru_stream = NULL;
walkthru_fref = g_vm->glk_fileref_create_by_prompt(fileusage_Data | fileusage_TextMode, filemode_Read, 0);
if (!walkthru_fref) {
write_text(cstring_resolve("ERROR_READING_WALKTHRU")->value);
return;
}
/* OPEN THE WALKTHRU FILE */
walkthru_stream = g_vm->glk_stream_open_file(walkthru_fref, filemode_Read, 0);
if (!walkthru_stream) {
write_text(cstring_resolve("ERROR_READING_WALKTHRU")->value);
return;
}
walkthru_running = TRUE;
/* ISSUE ALL THE COMMANDS STORE IN THE WALKTHRU FILE */
/* WE'RE DONE WITH THE FILE REFERENCE NOW THAT THE STREAM
* HAS BEEN SUCCESSFULLY OPENED */
g_vm->glk_fileref_destroy(walkthru_fref);
result = g_vm->glk_get_line_stream(walkthru_stream, text_buffer, (glui32) 80);
/* SET TO LOWER CASE AND STRIP NEWLINES */
length = strlen(text_buffer);
for (index = 0; index < length; index++) {
if (text_buffer[index] == '\r' ||
text_buffer[index] == '\n') {
text_buffer[index] = 0;
break;
}
}
strcpy(script_line, text_buffer);
while (result && INTERRUPTED->value == FALSE) {
/* THERE COULD BE A LOT OF PROCESSING GOING ON HERE BEFORE GETTING
* TO THE NEXT EVENT LOOP SO CALL g_vm->glk_tick AFTER EACH LINE READ */
g_vm->glk_tick();
command_encapsulate();
jacl_truncate();
if (word[0] != NULL) {
custom_error = FALSE;
execute("+bottom");
write_text(string_resolve("command_prompt")->value);
g_vm->glk_set_style(style_Input);
write_text(script_line);
newline();
g_vm->glk_set_style(style_Normal);
execute("+top");
preparse();
}
result = g_vm->glk_get_line_stream(walkthru_stream, text_buffer, (glui32) 80);
/* SET TO LOWER CASE AND STRIP NEWLINES */
length = strlen(text_buffer);
for (index = 0; index < length; index++) {
if (text_buffer[index] == '\r' ||
text_buffer[index] == '\n') {
text_buffer[index] = 0;
break;
}
}
strcpy(script_line, text_buffer);
}
/* CLOSE THE STREAM */
g_vm->glk_stream_close(walkthru_stream, NULL);
/* FINISH UP */
walkthru_running = FALSE;
}
int restore_interaction() {
if (g_vm->loadGame().getCode() != Common::kNoError) {
write_text(cstring_resolve("CANT_RESTORE")->value);
return (FALSE);
} else {
return (TRUE);
}
}
glui32 glk_get_bin_line_stream(strid_t file_stream, char *buffer, glui32 max_length) {
int character = 0;
register int index = 0;
character = g_vm->glk_get_char_stream(file_stream);
while (character != -1 && index < (int) max_length) {
*(buffer + index) = (char) character;
index++;
if (character == (int) '\n' ||
character == (int) '\r') {
break;
}
character = g_vm->glk_get_char_stream(file_stream);
};
*(buffer + index) = 0;
return ((glui32) index);
}
void jacl_set_window(winid_t new_window) {
current_window = new_window;
g_vm->glk_set_window(new_window);
}
#ifdef READLINE
char **command_completion(const char *text, int start, int end) {
/* READLINE TAB COMPLETION CODE */
char **options;
options = (const char **) NULL;
if (start == 0)
options = completion_matches(text, verb_generator);
else
options = completion_matches(text, object_generator);
return (options);
}
#endif
const char *object_generator(const char *text, int state) {
static int len;
static struct command_type *now;
struct command_type *to_send;
struct name_type *current_name = (struct name_type *) NULL;
/* IF THIS IS A NEW WORD TO COMPLETE, INITIALIZE NOW. THIS INCLUDES
SAVING THE LENGTH OF TEXT FOR EFFICIENCY, AND INITIALIZING THE INDEX
VARIABLE TO 0. */
if (!state) {
/* BUILD THE LIST */
int index;
completion_list = NULL;
/* LOOP THROUGH ALL THE OBJECTS AND SEE IF THEY ARE IN
THE CURRENT LOCATION */
for (index = 1; index <= objects; index++) {
if (parent_of(HERE, index, UNRESTRICT) && !(object[index]->attributes & NO_TAB)) {
/* LOOP THROUGH ALL THE OBJECTS NAMES AND
THEM TO THE COMPLETION LIST */
current_name = object[index]->first_name;
while (current_name) {
add_word(current_name->name);
current_name = current_name->next_name;
}
}
}
now = completion_list;
len = strlen(text);
}
while (now != NULL) {
if (!strncmp(text, now->word, len)) {
to_send = now;
now = now->next;
return ((const char *)to_send->word);
}
now = now->next;
}
return (const char *)NULL;
}
const char *verb_generator(const char *text, int state) {
static int len;
static struct command_type *now;
struct command_type *to_send;
struct word_type *pointer;
/* IF THIS IS A NEW WORD TO COMPLETE, INITIALIZE NOW. THIS INCLUDES
SAVING THE LENGTH OF TEXT FOR EFFICIENCY, AND INITIALIZING THE INDEX
VARIABLE TO 0. */
if (!state) {
/* BUILD THE LIST */
completion_list = NULL;
pointer = grammar_table;
while (pointer != NULL) {
add_word(pointer->word);
pointer = pointer->next_sibling;
}
add_word("walkthru");
now = completion_list;
len = strlen(text);
}
while (now != NULL) {
if (!strncmp(text, now->word, len)) {
to_send = now;
now = now->next;
/* MALLOC A COPY AND RETURN A POINTER TO THE COPY */
return ((const char *)to_send->word);
}
now = now->next;
}
return (const char *)NULL;
}
/* ADD A COPY OF STRING TO A LIST OF STRINGS IF IT IS NOT
ALREADY IN THE LIST. THIS IS FOR THE USE OF READLINE */
void add_word(const char *newWord) {
static struct command_type *current_word = NULL;
struct command_type *previous_word = NULL;
/* DON'T ADD WORDS SUCH AS *present TO THE LIST*/
if (*newWord == '*')
return;
if (current_word != NULL)
previous_word = current_word;
current_word = (struct command_type *) malloc(sizeof(struct command_type));
if (current_word != NULL) {
if (completion_list == NULL) {
completion_list = current_word;
}
strncpy(current_word->word, newWord, 40);
current_word->word[40] = 0;
current_word->next = NULL;
if (previous_word != NULL) {
previous_word->next = current_word;
}
}
}
void convert_to_utf8(glui32 *text, int len) {
int i, k;
i = 0;
k = 0;
/*convert UTF-32 to UTF-8 */
while (i < len) {
if (text[i] < 0x80) {
command_buffer[k] = text[i];
k++;
} else if (text[i] < 0x800) {
command_buffer[k ] = (0xC0 | ((text[i] & 0x7C0) >> 6));
command_buffer[k + 1] = (0x80 | (text[i] & 0x03F));
k = k + 2;
} else if (text[i] < 0x10000) {
command_buffer[k ] = (0xE0 | ((text[i] & 0xF000) >> 12));
command_buffer[k + 1] = (0x80 | ((text[i] & 0x0FC0) >> 6));
command_buffer[k + 2] = (0x80 | (text[i] & 0x003F));
k = k + 3;
} else if (text[i] < 0x200000) {
command_buffer[k ] = (0xF0 | ((text[i] & 0x1C0000) >> 18));
command_buffer[k + 1] = (0x80 | ((text[i] & 0x03F000) >> 12));
command_buffer[k + 2] = (0x80 | ((text[i] & 0x000FC0) >> 6));
command_buffer[k + 3] = (0x80 | (text[i] & 0x00003F));
k = k + 4;
} else {
command_buffer[k] = '?';
k++;
}
i++;
}
/* null-terminated string */
command_buffer[k] = '\0';
}
#ifndef NOUNICODE
int convert_to_utf32(unsigned char *text) {
int text_len;
int rlen;
if (!text) {
return 0;
}
text_len = strlen((const char *)text);
if (!text_len) {
return 0;
}
rlen = (int) parse_utf8(text, text_len, chunk_buffer_uni, text_len);
return (rlen);
}
glui32 parse_utf8(unsigned char *buf, glui32 buflen, glui32 *out, glui32 outlen) {
glui32 pos = 0;
glui32 outpos = 0;
glui32 res;
glui32 val0, val1, val2, val3;
while (outpos < outlen) {
if (pos >= buflen)
break;
val0 = buf[pos++];
if (val0 < 0x80) {
res = val0;
out[outpos++] = res;
continue;
}
if ((val0 & 0xe0) == 0xc0) {
if (pos + 1 > buflen) {
warning("incomplete two-byte character");
break;
}
val1 = buf[pos++];
if ((val1 & 0xc0) != 0x80) {
warning("malformed two-byte character");
break;
}
res = (val0 & 0x1f) << 6;
res |= (val1 & 0x3f);
out[outpos++] = res;
continue;
}
if ((val0 & 0xf0) == 0xe0) {
if (pos + 2 > buflen) {
warning("incomplete three-byte character");
break;
}
val1 = buf[pos++];
val2 = buf[pos++];
if ((val1 & 0xc0) != 0x80) {
warning("malformed three-byte character");
break;
}
if ((val2 & 0xc0) != 0x80) {
warning("malformed three-byte character");
break;
}
res = (((val0 & 0xf) << 12) & 0x0000f000);
res |= (((val1 & 0x3f) << 6) & 0x00000fc0);
res |= (((val2 & 0x3f)) & 0x0000003f);
out[outpos++] = res;
continue;
}
if ((val0 & 0xf0) == 0xf0) {
if ((val0 & 0xf8) != 0xf0) {
warning("malformed four-byte character");
break;
}
if (pos + 3 > buflen) {
warning("incomplete four-byte character");
break;
}
val1 = buf[pos++];
val2 = buf[pos++];
val3 = buf[pos++];
if ((val1 & 0xc0) != 0x80) {
warning("malformed four-byte character");
break;
}
if ((val2 & 0xc0) != 0x80) {
warning("malformed four-byte character");
break;
}
if ((val3 & 0xc0) != 0x80) {
warning("malformed four-byte character");
break;
}
res = (((val0 & 0x7) << 18) & 0x1c0000);
res |= (((val1 & 0x3f) << 12) & 0x03f000);
res |= (((val2 & 0x3f) << 6) & 0x000fc0);
res |= (((val3 & 0x3f)) & 0x00003f);
out[outpos++] = res;
continue;
}
warning("malformed character");
}
return outpos;
}
#endif
} // End of namespace JACL
} // End of namespace Glk