/* 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/language.h" #include "glk/jacl/types.h" #include "glk/jacl/prototypes.h" #include "glk/jacl/csv.h" #include "common/str.h" namespace Glk { namespace JACL { struct flock { short l_type; short l_whence; long l_start; long l_len; long l_pid; }; #define F_DUPFD 0 #define F_GETFD 1 #define F_SETFD 2 #define F_GETFL 3 #define F_SETFL 4 #define F_GETLK 5 #define F_SETLK 6 #define F_SETLKW 7 #define F_RDLCK 0 #define F_WRLCK 1 #define F_UNLCK 2 int fcntl(int __fd, int __cmd, ...) { return 0; } #ifndef strcasestr const char *strcasestr(const char *s, const char *find) { char c, sc; size_t len; if ((c = *find++) != 0) { c = (char)tolower((unsigned char)c); len = strlen(find); do { do { if ((sc = *s++) == 0) return (NULL); } while ((char)tolower((unsigned char)sc) != c); } while (scumm_strnicmp(s, find, len) != 0); s--; } return s; } #endif #define MAX_TRY 10 flock read_lck; int read_fd; flock write_lck; int write_fd; char *url_encode(char *str); char to_hex(char code); const char *location_attributes[] = { "VISITED ", "DARK ", "ON_WATER ", "UNDER_WATER ", "WITHOUT_AIR ", "OUTDOORS ", "MID_AIR ", "TIGHT_ROPE ", "POLLUTED ", "SOLVED ", "MID_WATER ", "DARKNESS ", "MAPPED ", "KNOWN ", NULL }; const char *object_attributes[] = { "CLOSED ", "LOCKED ", "DEAD ", "IGNITABLE ", "WORN ", "CONCEALING ", "LUMINOUS ", "WEARABLE ", "CLOSABLE ", "LOCKABLE ", "ANIMATE ", "LIQUID ", "CONTAINER ", "SURFACE ", "PLURAL ", "FLAMMABLE ", "BURNING ", "LOCATION ", "ON ", "DAMAGED ", "FEMALE ", "POSSESSIVE ", "OUT_OF_REACH ", "TOUCHED ", "SCORED ", "SITTING ", "NPC ", "DONE ", "GAS ", "NO_TAB ", "NOT_IMPORTANT ", NULL }; const char *object_elements[] = { "parent", "capacity", "mass", "bearing", "velocity", "next", "previous", "child", "index", "status", "state", "counter", "points", "class", "x", "y", NULL }; const char *location_elements[] = { "north", "south", "east", "west", "northeast", "northwest", "southeast", "southwest", "up", "down", "in", "out", "points", "class", "x", "y", NULL }; struct csv_parser parser_csv; char in_name[1024]; char out_name[1024]; Common::SeekableReadStream *infile; Common::WriteStream *outfile; int stack = 0; int proxy_stack = 0; int field_no = 0; struct stack_type backup[STACK_SIZE]; struct proxy_type proxy_backup[STACK_SIZE]; struct function_type *resolved_function = NULL; struct string_type *resolved_string = NULL; struct string_type *new_string = NULL; struct string_type *current_cstring = NULL; struct string_type *previous_cstring = NULL; struct cinteger_type *new_cinteger = NULL; struct cinteger_type *current_cinteger = NULL; struct cinteger_type *previous_cinteger = NULL; long bit_mask; extern int encrypted; extern int after_from; extern int last_exact; extern char temp_directory[]; extern char data_directory[]; char csv_buffer[1024]; int resolved_attribute; /* THE ITERATION VARIABLE USED FOR LOOPS */ int *loop_integer = NULL; int *select_integer = NULL; int criterion_value = 0; int criterion_type = 0; int criterion_negate = FALSE; int current_level; int execution_level; int *ask_integer; int new_x; int new_y; int interrupted = FALSE; char string_buffer[2048]; char argument_buffer[1024]; #ifdef GLK extern schanid_t sound_channel[]; extern strid_t game_stream; extern winid_t mainwin; extern winid_t statuswin; extern winid_t current_window; extern strid_t mainstr; extern strid_t statusstr; extern strid_t quotestr; extern strid_t inputstr; int top_of_loop = 0; int top_of_select = 0; int top_of_while = 0; int top_of_iterate = 0; int top_of_update = 0; int top_of_do_loop = 0; #else extern FILE *file; char option_buffer[2024]; int style_stack[100]; int style_index = 0; long top_of_loop = 0; long top_of_select = 0; long top_of_while = 0; long top_of_iterate = 0; long top_of_update = 0; long top_of_do_loop = 0; #endif #ifdef __NDS__ extern int bold_mode; extern int pre_mode; extern int reverse_mode; extern int input_mode; extern int subheader_mode; extern int note_mode; #endif extern char user_id[]; extern char prefix[]; extern char text_buffer[]; extern char chunk_buffer[]; extern const char *word[]; extern char bookmark[]; extern char file_prompt[]; /* CONTAINED IN PARSER.C */ extern int object_list[4][MAX_OBJECTS]; extern int list_size[]; extern int max_size[]; /* CONTAINED IN ENCAPSULATE.C */ extern int quoted[]; extern struct object_type *object[]; extern struct integer_type *integer_table; extern struct integer_type *integer[]; extern struct cinteger_type *cinteger_table; extern struct attribute_type *attribute_table; extern struct string_type *string_table; extern struct string_type *cstring_table; extern struct function_type *function_table; extern struct function_type *executing_function; extern struct command_type *completion_list; extern struct word_type *grammar_table; extern struct synonym_type *synonym_table; extern struct filter_type *filter_table; extern char function_name[]; extern char temp_buffer[]; extern char error_buffer[]; extern char proxy_buffer[]; extern char default_function[]; extern char override_[]; extern int noun[]; extern int wp; extern int start_of_this_command; extern int start_of_last_command; extern int buffer_index; extern int objects; extern int integers; extern int player; extern int oec; extern int *object_element_address; extern int *object_backup_address; extern int walkthru_running; // VALUES FROM LOADER extern int value_resolved; extern Common::WriteStream *transcript; extern char margin_string[]; char integer_buffer[16]; char called_name[1024]; char scope_criterion[24]; const char *output; void terminate(int code) { // FREE ANY EXTRA RAM ALLOCATED BY THE CSV PARSER csv_free(&parser_csv); #ifdef GLK int index; event_t event; // FLUSH THE GLK WINDOW SO THE ERROR GETS DISPLAYED IMMEDIATELY. g_vm->glk_select_poll(&event); /* CLOSE THE SOUND CHANNELS */ for (index = 0; index < 8; index++) { if (sound_channel[index] != NULL) { g_vm->glk_schannel_destroy(sound_channel[index]); } } /* CLOSE THE STREAM */ if (game_stream != NULL) { g_vm->glk_stream_close(game_stream, NULL); } g_vm->glk_exit(); #else if (file != NULL) /* CLOSE THE GAME FILE */ fclose(file); exit(code); #endif } void build_proxy() { int index; proxy_buffer[0] = 0; /* LOOP THROUGH ALL THE PARAMETERS OF THE PROXY COMMAND AND BUILD THE MOVE TO BE ISSUED ON THE PLAYER'S BEHALF */ for (index = 1; word[index] != NULL; index++) { strcat(proxy_buffer, text_of_word(index)); } for (index = 0; index < (int)strlen(proxy_buffer); index++) { if (proxy_buffer[index] == '~') { proxy_buffer[index] = '\"'; } } //printf("--- proxy buffer = \"%s\"\n", proxy_buffer); } void cb1(void *s, size_t i, void *not_used) { struct string_type *resolved_cstring; //sprintf (temp_buffer, "Trying to set field %d to equal %s^", field_no, (const char *) s); //write_text(temp_buffer); sprintf(temp_buffer, "field[%d]", field_no); if ((resolved_cstring = cstring_resolve(temp_buffer)) != NULL) { //write_text("Resolved "); //write_text(temp_buffer); //write_text("^"); strncpy(resolved_cstring->value, (const char *)s, i); resolved_cstring->value[i] = 0; //sprintf(temp_buffer, "Setting field %d to ~%s~^", field_no, (const char *) s); //write_text(temp_buffer); // INCREMENT THE FIELD NUMBER SO THE NEXT ONE GETS STORED IN THE RIGHT CONSTANT field_no++; } else { write_text("Can't resolve "); write_text(temp_buffer); write_text("^"); } } void cb2(int c, void *not_used) { // THE END OF THE RECORD HAS BEEN REACHED, EXPORT THE NUMBER OF FIELDS READ struct cinteger_type *resolved_cinteger; if ((resolved_cinteger = cinteger_resolve("field_count")) != NULL) { resolved_cinteger->value = field_no; } } int execute(const char *funcname) { int index; int counter; int *container; int object_1, object_2; if (g_vm->shouldQuit()) return 0; /* THESE VARIABLE KEEP TRACK OF if AND endif COMMANDS TO DECIDE WHETHER *THE CURRENT LINE OF CODE SHOULD BE EXECUTED OR NOT */ int currentLevel = 0; int executionLevel = 0; /* THESE ARE USED AS FILE POINTER OFFSETS TO RETURN TO FIXED * POINTS IN THE GAME FILE */ #ifdef GLK int before_command = 0; #else long before_command = 0; #endif strncpy(called_name, funcname, 1023); /* GET THE FUNCTION OBJECT BY THE FUNCTION NAME */ resolved_function = function_resolve(called_name); if (resolved_function == NULL) { //printf("--- failed to find %s\n", called_name); return (FALSE); } #ifdef GLK push_stack(g_vm->glk_stream_get_position(game_stream)); if (g_vm->shouldQuit()) return FALSE; #else push_stack(ftell(file)); #endif top_of_loop = 0; top_of_select = 0; top_of_while = 0; top_of_iterate = 0; top_of_update = 0; top_of_do_loop = 0; executing_function = resolved_function; executing_function->call_count++; // CREATE ALL THE PASSED ARGUMENTS AS JACL INTEGER CONSTANTS set_arguments(called_name); // SET function_name TO THE CORE NAME STORED IN THE FUNCTION OBJECT // LEAVING called_name TO CONTAIN THE FULL ARGUMENT LIST strncpy(function_name, executing_function->name, 80); strncpy(cstring_resolve("function_name")->value, executing_function->name, 80); //sprintf(temp_buffer, "--- starting to execute %s^", function_name); //write_text(temp_buffer); // JUMP TO THE POINT IN THE PROCESSED GAME FILE WHERE THIS FUNCTION STARTS #ifdef GLK g_vm->glk_stream_set_position(game_stream, executing_function->position, seekmode_Start); before_command = executing_function->position; (void)glk_get_bin_line_stream(game_stream, text_buffer, (glui32) 1024); #else fseek(file, executing_function->position, SEEK_SET); before_command = executing_function->position; fgets(text_buffer, 1024, file); #endif if (encrypted) jacl_decrypt(text_buffer); while (text_buffer[0] != 125 && !interrupted) { encapsulate(); if (word[0] == NULL); else if (!strcmp(word[0], "endwhile")) { currentLevel--; if (currentLevel < executionLevel) { // THIS ENDWHILE COMMAND WAS BEING EXECUTED, // NOT JUST COUNTED. if (top_of_while == FALSE) { sprintf(error_buffer, NO_WHILE, executing_function->name); log_error(error_buffer, PLUS_STDOUT); } else { #ifdef GLK g_vm->glk_stream_set_position(game_stream, top_of_while, seekmode_Start); #else fseek(file, top_of_while, SEEK_SET); #endif executionLevel = currentLevel; } } } else if (!strcmp(word[0], "enditerate")) { currentLevel--; if (currentLevel < executionLevel) { // THIS ENDITERATE COMMAND WAS BEING EXECUTED, // NOT JUST COUNTED. if (top_of_iterate == FALSE) { sprintf(error_buffer, NO_ITERATE, executing_function->name); log_error(error_buffer, PLUS_STDOUT); } else { #ifdef GLK g_vm->glk_stream_set_position(game_stream, top_of_iterate, seekmode_Start); #else fseek(file, top_of_iterate, SEEK_SET); #endif executionLevel = currentLevel; } } } else if (!strcmp(word[0], "endupdate")) { currentLevel--; if (currentLevel < executionLevel) { // THIS ENDUPDATE COMMAND WAS BEING EXECUTED, // NOT JUST COUNTED. if (top_of_update == FALSE) { sprintf(error_buffer, NO_UPDATE, executing_function->name); log_error(error_buffer, PLUS_STDOUT); } else { #ifdef GLK g_vm->glk_stream_set_position(game_stream, top_of_update, seekmode_Start); #else fseek(file, top_of_update, SEEK_SET); #endif executionLevel = currentLevel; } } } else if (!strcmp(word[0], "print") && currentLevel != executionLevel) { // SKIP THIS BLOCK OF PLAIN TEXT UNTIL IT FINDS A // LINE THAT STARTS WITH A '.' OR A '}' #ifdef GLK (void)glk_get_bin_line_stream(game_stream, text_buffer, (glui32) 1024); #else fgets(text_buffer, 1024, file); #endif if (encrypted) jacl_decrypt(text_buffer); while (text_buffer[0] != '.') { if (text_buffer[0] == '}') { // HIT THE END OF THE FUNCTION, JUST BAIL OUT return (exit_function(TRUE)); } // GET THE NEXT LINE #ifdef GLK (void)glk_get_bin_line_stream(game_stream, text_buffer, (glui32) 1024); #else fgets(text_buffer, 1024, file); #endif if (encrypted) jacl_decrypt(text_buffer); } } else if (!strcmp(word[0], "endif")) { currentLevel--; if (currentLevel < executionLevel) { /* THIS SHOULD NEVER HAPPEN */ executionLevel = currentLevel; } } else if (!strcmp(word[0], "endall")) { currentLevel = 0; executionLevel = 0; } else if (!strcmp(word[0], "else")) { if (currentLevel == executionLevel) { executionLevel--; } else if (currentLevel == executionLevel + 1) { executionLevel++; } } else if (currentLevel == executionLevel) { if (!strcmp(word[0], "look")) { // THIS IS JUST HERE FOR BACKWARDS COMPATIBILITY object[HERE]->attributes &= ~1L; look_around(); } else if (!strcmp(word[0], "repeat")) { #ifdef GLK top_of_do_loop = g_vm->glk_stream_get_position(game_stream); #else top_of_do_loop = ftell(file); #endif } else if (!strcmp(word[0], "until")) { if (word[3] == NULL) { /* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */ noproprun(); return (exit_function(TRUE)); } else { if (top_of_do_loop == FALSE) { sprintf(error_buffer, NO_REPEAT, executing_function->name); log_error(error_buffer, PLUS_STDOUT); } else if (!condition()) { #ifdef GLK g_vm->glk_stream_set_position(game_stream, top_of_do_loop, seekmode_Start); #else fseek(file, top_of_do_loop, SEEK_SET); #endif } } } else if (!strcmp(word[0], "untilall")) { if (word[3] == NULL) { /* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */ noproprun(); return (exit_function(TRUE)); } else { if (top_of_do_loop == FALSE) { sprintf(error_buffer, NO_REPEAT, executing_function->name); log_error(error_buffer, PLUS_STDOUT); } else if (!and_condition()) { #ifdef GLK g_vm->glk_stream_set_position(game_stream, top_of_do_loop, seekmode_Start); #else fseek(file, top_of_do_loop, SEEK_SET); #endif } } } else if (!strcmp(word[0], "iterate")) { int i; // A NEW iterate LOOP MEANS STARTING BACK AT THE FIRST FIELD field_no = 0; currentLevel++; /* THIS LOOP COMES BACK TO THE START OF THE LINE CURRENTLY EXECUTING, NOT THE LINE AFTER */ top_of_iterate = before_command; // infile REMAINS OPEN DURING THE ITERATION, ONLY NEEDS // OPENING THE FIRST TIME if (infile == NULL) { strcpy(temp_buffer, data_directory); strcat(temp_buffer, prefix); strcat(temp_buffer, "-"); strcat(temp_buffer, text_of_word(1)); strcat(temp_buffer, ".csv"); infile = File::openForReading(temp_buffer); if (word[2] != NULL && !strcmp(word[2], "skip_header")) { assert(infile); infile->read(csv_buffer, 1024); } } if (infile == NULL) { sprintf(error_buffer, "Failed to open file %s\n", temp_buffer); log_error(error_buffer, LOG_ONLY); infile = NULL; } else { if (word[1] == NULL) { /* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */ noproprun(); return (exit_function(TRUE)); } else { // IF THERE IS ANOTHER RECORD TO READ FROM THE CSV FILE THEN // SET THE field[] CONSTANTS AND INCREMENT THE executionLevel infile->read(csv_buffer, 1024); if (infile->pos() < infile->size()) { i = strlen(csv_buffer); //sprintf (temp_buffer, "Read ~%s~ with %d bytes.^", csv_buffer, i); //write_text(temp_buffer); if (csv_parse(&parser_csv, csv_buffer, i, cb1, cb2, (void *) NULL) != (uint)i) { sprintf(error_buffer, "Error parsing file: %s\n", csv_strerror(csv_error(&parser_csv))); log_error(error_buffer, PLUS_STDOUT); delete infile; infile = NULL; } else { // A LINE HAS BEEN SUCCESSFULLY READ, EXECUTE THE CONTENTS OF THE LOOP executionLevel++; } } else { delete infile; infile = NULL; } } } } else if (!strcmp(word[0], "update")) { int i; // SET UP THE RECORD LOCKING STRUCTURE, THE ADDRESS OF WHICH // IS PASSED TO THE fcntl() SYSTEM CALL write_lck.l_type = F_WRLCK; // SETTING A WRITE LOCK write_lck.l_whence = 0; // OFFSET l_start FROM BEGINNING OF FILE write_lck.l_start = 0LL; write_lck.l_len = 0LL; // UNTIL THE END OF THE FILE ADDRESS SPACE read_lck.l_type = F_RDLCK; // SETTING A READ LOCK read_lck.l_whence = 0; // OFFSET l_start FROM BEGINNING OF FILE read_lck.l_start = 0LL; read_lck.l_len = 0LL; // UNTIL THE END OF THE FILE ADDRESS SPACE // A NEW iterate LOOP MEANS STARTING BACK AT THE FIRST FIELD field_no = 0; currentLevel++; // THIS LOOP COMES BACK TO THE START OF THE LINE CURRENTLY // EXECUTING, NOT THE LINE AFTER top_of_update = before_command; // infile REMAINS OPEN DURING THE ITERATION, ONLY NEEDS // OPENING THE FIRST TIME if (infile == NULL) { strcpy(in_name, data_directory); strcat(in_name, prefix); strcat(in_name, "-"); strcat(in_name, text_of_word(1)); strcat(in_name, ".csv"); infile = File::openForReading(in_name); } if (outfile == NULL) { // OPEN A TEMPORARY OUTPUT FILE TO WRITE THE MODIFICATIONS TO strcpy(out_name, data_directory); strcat(out_name, prefix); strcat(out_name, "-"); strcat(out_name, text_of_word(1)); strcat(out_name, "-"); strcat(out_name, user_id); strcat(out_name, ".csv"); outfile = File::openForWriting(out_name); } if (infile == NULL) { sprintf(error_buffer, "Failed to open input CSV file ~%s\n", in_name); log_error(error_buffer, LOG_ONLY); if (outfile != NULL) { delete outfile; outfile = NULL; } return (exit_function(TRUE)); } else { if (outfile == NULL) { sprintf(error_buffer, "Failed to open output CSV file ~%s~\n", out_name); log_error(error_buffer, LOG_ONLY); if (infile != NULL) { delete infile; infile = NULL; } return (exit_function(TRUE)); } else { #ifdef FILE_CTL int tryCtr = 0; write_fd = fileno(outfile); // ATTEMPT LOCKING OUTPUT FILE MAX_TRY TIMES BEFORE GIVING UP. while (fcntl(write_fd, F_SETLK, &write_lck) < 0) { if (errno == EAGAIN || errno == EACCES) { // THERE MIGHT BE OTHER ERROR CASES IN WHICH // USERS MIGHT TRY AGAIN if (++tryCtr < MAX_TRY) { jacl_sleep(1000); continue; } sprintf(error_buffer, "File busy unable to get lock on output file.\n"); log_error(error_buffer, PLUS_STDOUT); return (exit_function(TRUE)); } } tryCtr = 0; read_fd = fileno(infile); // ATTEMPT LOCKING OUTPUT FILE MAX_TRY TIMES BEFORE GIVING UP. while (fcntl(read_fd, F_SETLK, &read_lck) < 0) { if (errno == EAGAIN || errno == EACCES) { // THERE MIGHT BE OTHER ERROR CASES IN WHICH // USERS MIGHT TRY AGAIN if (++tryCtr < MAX_TRY) { jacl_sleep(1000); continue; } sprintf(error_buffer, "File busy unable to get lock on input file.\n"); log_error(error_buffer, PLUS_STDOUT); return (exit_function(TRUE)); } } #endif if (word[1] == NULL) { /* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */ noproprun(); return (exit_function(TRUE)); } else { // IF THERE IS ANOTHER RECORD TO READ FROM THE CSV FILE THEN // SET THE field[] CONSTANTS AND INCREMENT THE executionLevel infile->read(csv_buffer, 1024); if (infile->pos() < infile->size()) { i = strlen(csv_buffer); if (csv_parse(&parser_csv, csv_buffer, i, cb1, cb2, (int *) &field_no) != (uint)i) { sprintf(error_buffer, "Error parsing file: %s\n", csv_strerror(csv_error(&parser_csv))); log_error(error_buffer, PLUS_STDOUT); read_lck.l_type = F_UNLCK; // SETTING A READ LOCK fcntl(read_fd, F_SETLK, &read_lck); delete infile; infile = NULL; } else { // A LINE HAS BEEN SUCCESSFULLY READ, EXECUTE THE CONTENTS OF THE LOOP executionLevel++; } } else { write_lck.l_type = F_UNLCK; // REMOVE THE WRITE LOCK fcntl(write_fd, F_SETLK, &write_lck); delete outfile; read_lck.l_type = F_UNLCK; // REMOVE THE READ LOCK fcntl(read_fd, F_SETLK, &read_lck); delete infile; rename(out_name, in_name); outfile = NULL; infile = NULL; } } } } } else if (!strcmp(word[0], "while")) { currentLevel++; /* THIS LOOP COMES BACK TO THE START OF THE LINE CURRENTLY EXECUTING, NOT THE LINE AFTER */ top_of_while = before_command; if (word[3] == NULL) { /* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */ noproprun(); return (exit_function(TRUE)); } else if (condition()) { executionLevel++; } } else if (!strcmp(word[0], "whileall")) { currentLevel++; /* THIS LOOP COMES BACK TO THE START OF THE LINE CURRENTLY EXECUTING, NOT THE LINE AFTER */ top_of_while = before_command; if (word[3] == NULL) { /* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */ noproprun(); return (exit_function(TRUE)); } else if (and_condition()) { executionLevel++; } } else if (!strcmp(word[0], "loop")) { /* THE LOOP COMMAND LOOPS ONCE FOR EACH DEFINED * OBJECT (FOREACH) */ #ifdef GLK top_of_loop = g_vm->glk_stream_get_position(game_stream); #else top_of_loop = ftell(file); #endif if (word[1] == NULL) { // IF NONE IS SUPPLIED DEFAULT TO noun3 loop_integer = &noun[2]; } else { // STORE THE CONTAINER TO PUT THE CURRENT OBJECT IN loop_integer = container_resolve(word[1]); // IF THE SUPPLIED CONTAINER CAN'T BE RESOLVED // DEFAULT TO noun3 if (loop_integer == NULL) loop_integer = &noun[2]; } // SET THE VALUE OF THE LOOP INDEX TO POINT TO THE FIRST OBJECT *loop_integer = 1; } else if (!strcmp(word[0], "endloop")) { if (top_of_loop == FALSE) { sprintf(error_buffer, NO_LOOP, executing_function->name); log_error(error_buffer, PLUS_STDOUT); } else { *loop_integer += 1; if (*loop_integer > objects) { top_of_loop = FALSE; *loop_integer = 0; } else { #ifdef GLK g_vm->glk_stream_set_position(game_stream, top_of_loop, seekmode_Start); #else fseek(file, top_of_loop, SEEK_SET); #endif } } } else if (!strcmp(word[0], "select")) { /* THE SELECT COMMAND LOOPS ONCE FOR EACH DEFINED * OBJECT THAT MATCHES THE SUPPLIED CRITERION */ #ifdef GLK top_of_select = g_vm->glk_stream_get_position(game_stream); #else top_of_select = ftell(file); #endif if (word[1] == NULL) { /* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */ noproprun(); return (exit_function(TRUE)); } else if (word[2] == NULL) { // IF NONE IS SUPPLIED DEFAULT TO noun3 select_integer = &noun[2]; } else { // STORE THE CONTAINER TO PUT THE CURRENT OBJECT IN select_integer = container_resolve(word[2]); // IF THE SUPPLIED CONTAINER CAN'T BE RESOLVED // DEFAULT TO noun3 if (select_integer == NULL) { select_integer = &noun[2]; } } // SET THE VALUE OF THE SELECT INDEX TO ONE BEFORE THE // FIRST OBJECT. THE NEXT FUNCTION AUTOMATICALLY INCREMENTS // THE INDEX BY ONE AT THE START OF THE WHILE LOOP. *select_integer = 0; if (word[1][0] == '!') { criterion_negate = TRUE; strcpy(argument_buffer, &word[1][1]); } else { criterion_negate = FALSE; strcpy(argument_buffer, word[1]); } // DETERMINE THE CRITERION FOR SELETION if (!strcmp(argument_buffer, "*held") || !strcmp(argument_buffer, "*here") || !strcmp(argument_buffer, "*anywhere") || !strcmp(argument_buffer, "*present")) { criterion_type = CRI_SCOPE; strncpy(scope_criterion, argument_buffer, 20); } else if ((criterion_value = attribute_resolve(argument_buffer))) { criterion_type = CRI_ATTRIBUTE; } else if ((criterion_value = user_attribute_resolve(argument_buffer))) { criterion_type = CRI_USER_ATTRIBUTE; } else { // USE VALUE OF AS A CATCH ALL IF IT IS NOT AN ATTRIBUTE OR SCOPE criterion_value = value_of(argument_buffer); if (value_resolved) { criterion_type = CRI_PARENT; } else { // CAN'T RESOLVE CRITERION criterion_type = CRI_NONE; } } if (criterion_type != CRI_NONE) { if (select_next() == FALSE) { *select_integer = 0; top_of_select = 0; } } else { *select_integer = 0; } if (*select_integer == 0) { // THERE ARE NO MATCHING OBJECTS SO JUMP TO THE endselect #ifdef GLK (void)glk_get_bin_line_stream(game_stream, text_buffer, (glui32) 1024); #else fgets(text_buffer, 1024, file); #endif if (encrypted) jacl_decrypt(text_buffer); while (text_buffer[0] != '}') { encapsulate(); if (word[0] != NULL && !strcmp(word[0], "endselect")) { break; } #ifdef GLK (void)glk_get_bin_line_stream(game_stream, text_buffer, (glui32) 1024); #else fgets(text_buffer, 1024, file); #endif } } } else if (!strcmp(word[0], "endselect")) { if (top_of_select == FALSE) { sprintf(error_buffer, NO_LOOP, executing_function->name); log_error(error_buffer, PLUS_STDOUT); } else { if (select_next(/* select_integer, criterion_type, criterion_value, scope_criterion */)) { #ifdef GLK g_vm->glk_stream_set_position(game_stream, top_of_select, seekmode_Start); #else fseek(file, top_of_select, SEEK_SET); #endif } else { *select_integer = 0; top_of_select = 0; } } } else if (!strcmp(word[0], "break")) { currentLevel++; executionLevel--; #ifdef GLK } else if (!strcmp(word[0], "cursor")) { if (word[2] == NULL) { /* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */ noproprun(0); return (exit_function(TRUE)); } else { if (current_window == statuswin) { g_vm->glk_window_move_cursor(statuswin, value_of(word[1], TRUE), value_of(word[2], TRUE)); } else { log_error(BAD_CURSOR, PLUS_STDOUT); } } } else if (!strcmp(word[0], "stop")) { int channel; if (SOUND_SUPPORTED->value) { /* SET THE CHANNEL TO STOP, IF SUPPLIED */ if (word[1] == NULL) { channel = 0; } else { channel = value_of(word[1], TRUE); /* SANITY CHECK THE CHANNEL SELECTED */ if (channel < 0 || channel > 7) { channel = 0; } } g_vm->glk_schannel_stop(sound_channel[channel]); } } else if (!strcmp(word[0], "volume")) { int channel, volume; if (SOUND_SUPPORTED->value) { /* SET THE CHANNEL TO STOP, IF SUPPLIED */ if (word[2] == NULL) { channel = 0; } else { channel = value_of(word[2], TRUE); /* SANITY CHECK THE CHANNEL SELECTED */ if (channel < 0 || channel > 7) { channel = 0; } } if (word[1] == NULL) { /* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */ noproprun(); return (exit_function(TRUE)); } else { volume = value_of(word[1], TRUE); /* SANITY CHECK THE CHANNEL SELECTED */ if (volume < 0) { volume = 0; } if (volume > 100) { volume = 100; } /* STORE A COPY OF THE CURRENT VOLUME FOR ACCESS * FROM JACL CODE */ sprintf(temp_buffer, "volume[%d]", channel); cinteger_resolve(temp_buffer)->value = volume; /* NOW SCALE THE 0-100 VOLUME TO THE 0-65536 EXPECTED * BY Glk */ volume = volume * 655; /* SET THE VOLUME */ g_vm->glk_schannel_set_volume(sound_channel[channel], (glui32) volume); } } } else if (!strcmp(word[0], "timer")) { if (TIMER_SUPPORTED->value && TIMER_ENABLED->value) { if (word[1] == NULL) { /* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */ noproprun(); return (exit_function(TRUE)); } else { index = value_of(word[1], TRUE); /* DON'T ALLOW NEGATIVE VALUES, BUT NO UPPER LIMIT */ if (index < 0) index = 0; /* SET THE GLK TIMER */ g_vm->glk_request_timer_events((glui32) index); /* EXPOSE THE CURRENT VALUE THROUGH A JACL CONSTANT SO THAT GAME CODE CAN READ THE IT */ cinteger_resolve("timer")->value = index; } } } else if (!strcmp(word[0], "sound")) { int channel; glui32 repeats; if (SOUND_SUPPORTED->value && SOUND_ENABLED->value) { /* SET THE CHANNEL TO USE, IF SUPPLIED */ if (word[2] == NULL) { channel = 0; } else { channel = value_of(word[2], TRUE); /* SANITY CHECK THE CHANNEL SELECTED */ if (channel < 0 || channel > 7) { channel = 0; } } /* SET THE NUMBER OF REPEATS, IF SUPPLIED */ if (word[3] == NULL) { repeats = 1; } else { repeats = value_of(word[3], TRUE); } if (word[1] == NULL) { /* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */ noproprun(); return (exit_function(TRUE)); } else { if (g_vm->glk_schannel_play_ext(sound_channel[channel], (glui32) value_of(word[1], TRUE), repeats, channel + 1) == 0) { /* THE CHANNEL NUMBER IS PASSED SO THAT THE SOUND * NOTIFICATION EVENT CAN USE THE INFORMATION * IT HAS 1 ADDED TO IT SO THAT IT IS A NON-ZERO * NUMBER AND THE EVENT IS ACTIVATED */ sprintf(error_buffer, "Unable to play sound: %ld", value_of(word[1], FALSE)); log_error(error_buffer, PLUS_STDERR); } } } } else if (!strcmp(word[0], "image")) { if (GRAPHICS_SUPPORTED->value && GRAPHICS_ENABLED->value) { if (word[1] == NULL) { /* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */ noproprun(); return (exit_function(TRUE)); } else { if (g_vm->glk_image_draw(mainwin, (glui32) value_of(word[1], TRUE), imagealign_InlineDown, 0) == 0) { sprintf(error_buffer, "Unable to draw image: %ld", value_of(word[1], FALSE)); log_error(error_buffer, PLUS_STDERR); } } } } else if (!strcmp(word[0], "askstring") || !strcmp(word[0], "getstring")) { if (word[1] == NULL) { /* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */ noproprun(0); return (exit_function(TRUE)); } else { /* GET A POINTER TO THE STRING BEING MODIFIED */ if ((resolved_string = string_resolve(word[1])) == NULL) { unkstrrun(word[1]); return (exit_function(TRUE)); } // PROMPT THE USER TO INPUT A STRING AND STORE IT IN THE // RESOLVED VARIABLE get_string(resolved_string->value); } } else if (!strcmp(word[0], "asknumber") || !strcmp(word[0], "getnumber")) { int low, high; int insist = FALSE; /* THE ONLY DIFFERENCE WITH THE getnumber COMMAND IS THAT * IT INSISTS THE PLAYER GIVES A LEGAL RESPONSE */ if (!strcmp(word[0], "getnumber")) { insist = TRUE; } if (word[3] != NULL) { ask_integer = container_resolve(word[1]); if (ask_integer == NULL) { unkvarrun(word[1]); return (exit_function(TRUE)); } low = value_of(word[2], TRUE); high = value_of(word[3], TRUE); if (high == -1 || low == -1) { return (exit_function(TRUE)); } *ask_integer = get_number(insist, low, high); } else { /* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */ noproprun(); return (exit_function(TRUE)); } } else if (!strcmp(word[0], "getyesorno")) { if (word[1] != NULL) { ask_integer = container_resolve(word[1]); if (ask_integer == NULL) { unkvarrun(word[1]); return (exit_function(TRUE)); } *ask_integer = get_yes_or_no(); } else { /* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */ noproprun(); return (exit_function(TRUE)); } } else if (!strcmp(word[0], "clear")) { if (!walkthru_running) { g_vm->glk_window_clear(current_window); } } else if (!strcmp(word[0], "terminate")) { terminate(0); return 0; } else if (!strcmp(word[0], "more")) { if (word[1] == NULL) { more("[MORE]"); } else { more(word[1]); } } else if (!strcmp(word[0], "style")) { /* THIS COMMAND IS USED TO OUTPUT ANSI CODES OR SET GLK * STREAM STYLES */ if (word[1] == NULL) { /* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */ noproprun(); return (exit_function(TRUE)); } else { if (!strcmp(word[1], "bold") || !strcmp(word[1], "emphasised")) { g_vm->glk_set_style(style_Emphasized); } else if (!strcmp(word[1], "note")) { g_vm->glk_set_style(style_Note); } else if (!strcmp(word[1], "input")) { g_vm->glk_set_style(style_Input); } else if (!strcmp(word[1], "header")) { g_vm->glk_set_style(style_Header); } else if (!strcmp(word[1], "subheader")) { g_vm->glk_set_style(style_Subheader); } else if (!strcmp(word[1], "reverse") || !strcmp(word[1], "inverse")) { if (current_window == mainwin) { g_vm->glk_set_style(style_User2); } else { g_vm->glk_set_style(style_User1); } } else if (!strcmp(word[1], "pre") || !strcmp(word[1], "preformatted")) { g_vm->glk_set_style(style_Preformatted); } else if (!strcmp(word[1], "normal")) { g_vm->glk_set_style(style_Normal); } } } else if (!strcmp(word[0], "flush")) { } else if (!strcmp(word[0], "hyperlink")) { /* OUTPUT LINK TEXT AS PLAIN TEXT UNDER Glk */ if (word[2] == NULL) { noproprun(); pop_stack(); return (TRUE); } else { write_text(text_of_word(1)); } #else #ifdef __NDS__ } else if (!strcmp(word[0], "flush")) { jflush(); } else if (!strcmp(word[0], "cursor")) { if (word[2] == NULL) { /* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */ noproprun(0); return (exit_function(TRUE)); } else { printf("\x1b[%d;%dH", (int) value_of(word[1], TRUE), (int) value_of(word[2], TRUE)); } } else if (!strcmp(word[0], "stop")) { } else if (!strcmp(word[0], "volume")) { } else if (!strcmp(word[0], "timer")) { } else if (!strcmp(word[0], "sound")) { } else if (!strcmp(word[0], "image")) { } else if (!strcmp(word[0], "askstring") || !strcmp(word[0], "getstring")) { if (word[1] == NULL) { /* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */ noproprun(0); return (exit_function(TRUE)); } else { /* GET A POINTER TO THE STRING BEING MODIFIED */ if ((resolved_string = string_resolve(word[1])) == NULL) { unkstrrun(word[1]); return (exit_function(TRUE)); } // PROMPT THE USER TO INPUT A STRING AND STORE IT IN THE // RESOLVED VARIABLE get_string(resolved_string->value); } } else if (!strcmp(word[0], "asknumber") || !strcmp(word[0], "getnumber")) { int low, high; int insist = FALSE; /* THE ONLY DIFFERENCE WITH THE getnumber COMMAND IS THAT * IT INSISTS THE PLAYER GIVES A LEGAL RESPONSE */ if (!strcmp(word[0], "getnumber")) { insist = TRUE; } if (word[3] != NULL) { ask_integer = container_resolve(word[1]); if (ask_integer == NULL) { unkvarrun(word[1]); return (exit_function(TRUE)); } low = value_of(word[2], TRUE); high = value_of(word[3], TRUE); if (high == -1 || low == -1) { return (exit_function(TRUE)); } *ask_integer = get_number(insist, low, high); } else { /* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */ noproprun(); return (exit_function(TRUE)); } } else if (!strcmp(word[0], "getyesorno")) { if (word[1] != NULL) { ask_integer = container_resolve(word[1]); if (ask_integer == NULL) { unkvarrun(word[1]); return (exit_function(TRUE)); } *ask_integer = get_yes_or_no(); } else { /* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */ noproprun(); return (exit_function(TRUE)); } } else if (!strcmp(word[0], "clear")) { clrscrn(); } else if (!strcmp(word[0], "terminate")) { terminate(0); return; } else if (!strcmp(word[0], "more")) { if (word[1] == NULL) { more("[MORE]"); } else { more(word[1]); } } else if (!strcmp(word[0], "style")) { /* THIS COMMAND IS USED TO OUTPUT ANSI CODES OR SET GLK * STREAM STYLES */ if (word[1] == NULL) { /* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */ noproprun(); return (exit_function(TRUE)); } else { if (!strcmp(word[1], "bold") || !strcmp(word[1], "emphasised")) { printf("\x1b[37;1m"); // SET TO BRIGHT WHITE bold_mode = TRUE; } else if (!strcmp(word[1], "note")) { printf("\x1b[34;1m"); // SET TO BRIGHT BLUE note_mode = TRUE; } else if (!strcmp(word[1], "input")) { printf("\x1b[32;0m"); // SET TO DIM GREEN input_mode = TRUE; } else if (!strcmp(word[1], "header")) { printf("\x1b[37;0m"); // SET TO DIM WHITE } else if (!strcmp(word[1], "subheader")) { printf("\x1b[33;1m"); // SET TO BRIGHT YELLOW subheader_mode = TRUE; } else if (!strcmp(word[1], "reverse") || !strcmp(word[1], "inverse")) { printf("\x1b[7m"); // SET TO DIM WHITE reverse_mode = TRUE; } else if (!strcmp(word[1], "pre") || !strcmp(word[1], "preformatted")) { printf("\x1b[37;0m"); // SET TO DIM WHITE pre_mode = TRUE; } else if (!strcmp(word[1], "normal")) { printf("\x1b[37;0m"); // SET TO DIM WHITE bold_mode = FALSE; pre_mode = FALSE; reverse_mode = FALSE; input_mode = FALSE; subheader_mode = FALSE; note_mode = FALSE; } } } else if (!strcmp(word[0], "hyperlink")) { /* OUTPUT LINK TEXT AS PLAIN TEXT UNDER Glk */ if (word[2] == NULL) { noproprun(); pop_stack(); return (TRUE); } else { write_text(text_of_word(1)); } #else /* HERE STARTS THE CGIJACL-ONLY FUNCTIONS */ } else if (!strcmp(word[0], "option")) { /* USED TO ADD AN OPTION TO AN HTML LIST */ if (word[1] == NULL) { noproprun(); pop_stack(); return (TRUE); } else { index = value_of(word[1]); if (word[2] != NULL) { sprintf(option_buffer, "