void nh_describe_pos(int x, int y, struct nh_desc_buf *bufs) { int monid = dbuf_get_mon(x, y); bufs->bgdesc[0] = '\0'; bufs->trapdesc[0] = '\0'; bufs->objdesc[0] = '\0'; bufs->mondesc[0] = '\0'; bufs->invisdesc[0] = '\0'; bufs->effectdesc[0] = '\0'; bufs->objcount = -1; if (!program_state.game_running || !api_entry_checkpoint()) return; describe_bg(x, y, level->locations[x][y].mem_bg, bufs->bgdesc); if (level->locations[x][y].mem_trap) strcpy(bufs->trapdesc, trapexplain[level->locations[x][y].mem_trap - 1]); bufs->objcount = describe_object(x, y, level->locations[x][y].mem_obj - 1, bufs->objdesc); describe_mon(x, y, monid - 1, bufs->mondesc); if (level->locations[x][y].mem_invis) strcpy(bufs->invisdesc, invisexplain); if (u.uswallow && (x != u.ux || y != u.uy)) { /* all locations when swallowed other than the hero are the monster */ sprintf(bufs->effectdesc, "interior of %s", Blind ? "a monster" : a_monnam(u.ustuck)); } api_exit(); }
boolean nh_exit_game(int exit_type) { boolean log_disabled = iflags.disable_log; if (!api_entry_checkpoint()) { /* not sure anything in here can actually call panic */ iflags.disable_log = log_disabled; return TRUE; /* terminate was called, so exit is successful */ } program_state.forced_exit = TRUE; /* clean up after viewing a game replay */ if (program_state.viewing) nh_view_replay_finish(); xmalloc_cleanup(); iflags.disable_log = TRUE; if (program_state.game_running) { switch (exit_type) { case EXIT_REQUEST_SAVE: dosave(); /* will ask "really save?" and, if 'y', eventually call terminate. */ break; case EXIT_FORCE_SAVE: dosave0(TRUE); terminate(); break; case EXIT_REQUEST_QUIT: done2(); break; case EXIT_FORCE_QUIT: done(QUIT); break; /* not reached */ case EXIT_PANIC: /* freeing things should be safe */ freedynamicdata(); dlb_cleanup(); panic("UI problem."); break; } iflags.disable_log = log_disabled; api_exit(); return FALSE; } iflags.disable_log = log_disabled; /* calling terminate() will get us out of nested contexts safely, eg: * UI_cmdloop -> nh_command -> UI_update_screen (problem happens here) -> nh_exit_game * will jump all the way back to UI_cmdloop */ terminate(); api_exit(); /* not reached */ return TRUE; }
boolean nh_set_option(const char *name, union nh_optvalue value, boolean isstring) { boolean rv; if (!api_entry_checkpoint()) return FALSE; rv = set_option(name, value, isstring); api_exit(); return rv; }
void nh_lib_init(const struct nh_window_procs *procs, char **paths) { int i; if (!api_entry_checkpoint()) /* not sure anything in here can actually call panic */ return; windowprocs = *procs; for (i = 0; i < PREFIX_COUNT; i++) fqn_prefix[i] = strdup(paths[i]); u.uhp = 1; /* prevent RIP on early quits */ init_opt_struct(); turntime = 0; current_timezone = get_tz_offset(); api_exit(); }
boolean nh_start_game(int fd, const char *name, int irole, int irace, int igend, int ialign, enum nh_game_modes playmode) { unsigned int seed = 0; if (!api_entry_checkpoint()) return FALSE; /* init failed; programmer error! */ if (fd == -1 || !name || !*name) goto err_out; if (!program_state.restoring) { turntime = (unsigned long long)time(NULL); seed = turntime ^ get_seedval(); /* initialize the random number generator */ mt_srand(seed); } /* else: turntime and rng seeding are done in logreplay.c */ startup_common(name, playmode); if (!validrole(irole) || !validrace(irole, irace) || !validgend(irole, irace, igend) || !validalign(irole, irace, ialign)) goto err_out; u.initrole = irole; u.initrace = irace; u.initgend = igend; u.initalign = ialign; /* write out a new logfile header "NHGAME ..." with all the initial details */ log_init(); log_newgame(fd, turntime, seed, playmode); newgame(); was_on_elbereth = !sengr_at("Elbereth", u.ux, u.uy); /* force botl update later */ wd_message(); api_exit(); return TRUE; err_out: api_exit(); return FALSE; }
struct nh_topten_entry * nh_get_topten(int *out_len, char *statusbuf, const char * volatile player, int top, int around, boolean own) { struct toptenentry *ttlist, newtt; struct nh_topten_entry *score_list; boolean game_inited = (wiz1_level.dlevel != 0); boolean game_complete = game_inited && moves && program_state.gameover; int rank = -1; /* index of the completed game in the topten list */ int fd, i, j, sel_count; boolean *selected, off_list = FALSE; statusbuf[0] = '\0'; *out_len = 0; if (!api_entry_checkpoint()) return NULL; if (!game_inited) { /* If nh_get_topten() isn't called after a game, we never went through initialization. */ dlb_init(); init_dungeons(); } if (!player) { if (game_complete) player = plname; else player = ""; } fd = open_datafile(RECORD, O_RDONLY, SCOREPREFIX); ttlist = read_topten(fd, TTLISTLEN); close(fd); if (!ttlist) { strcpy(statusbuf, "Cannot open record file!"); api_exit(); return NULL; } /* find the rank of a completed game in the score list */ if (game_complete && !strcmp(player, plname)) { fill_topten_entry(&newtt, end_how); /* find this entry in the list */ for (i = 0; i < TTLISTLEN && validentry(ttlist[i]); i++) if (!memcmp(&ttlist[i], &newtt, sizeof (struct toptenentry))) rank = i; if (wizard || discover) sprintf(statusbuf, "Since you were in %s mode, your game was not " "added to the score list.", wizard ? "wizard" : "discover"); else if (rank >= 0 && rank < 10) sprintf(statusbuf, "You made the top ten list!"); else if (rank) sprintf(statusbuf, "You reached the %d%s place on the score list.", rank + 1, ordin(rank + 1)); } /* select scores for display */ sel_count = 0; selected = calloc(TTLISTLEN, sizeof (boolean)); for (i = 0; i < TTLISTLEN && validentry(ttlist[i]); i++) { if (top == -1 || i < top) selected[i] = TRUE; if (own && !strcmp(player, ttlist[i].name)) selected[i] = TRUE; if (rank != -1 && rank - around <= i && i <= rank + around) selected[i] = TRUE; if (selected[i]) sel_count++; } if (game_complete && sel_count == 0) { /* didn't make it onto the list and nothing else is selected */ ttlist[0] = newtt; selected[0] = TRUE; sel_count++; off_list = TRUE; } score_list = xmalloc(sel_count * sizeof (struct nh_topten_entry)); memset(score_list, 0, sel_count * sizeof (struct nh_topten_entry)); *out_len = sel_count; j = 0; for (i = 0; i < TTLISTLEN && validentry(ttlist[i]); i++) { if (selected[i]) fill_nh_score_entry(&ttlist[i], &score_list[j++], i + 1, i == rank); } if (off_list) { score_list[0].rank = -1; score_list[0].highlight = TRUE; } if (!game_inited) { free_dungeon(); dlb_cleanup(); } free(selected); free(ttlist); api_exit(); return score_list; }
/* command wrapper function: make sure the game is able to run commands, perform * logging and generate reasonable return values for api clients with no access * to internal state */ int nh_command(const char *cmd, int rep, struct nh_cmd_arg *arg) { int cmdidx, cmdresult, pre_moves; unsigned int pre_rngstate; if (!program_state.game_running) return ERR_GAME_NOT_RUNNING; cmdidx = get_command_idx(cmd); if (program_state.viewing && (cmdidx < 0 || !(cmdlist[cmdidx].flags & CMD_NOTIME))) return ERR_COMMAND_FORBIDDEN; /* */ if (!api_entry_checkpoint()) { /* terminate() in end.c will arrive here */ if (program_state.panicking) return GAME_PANICKED; if (!program_state.gameover) return GAME_SAVED; if (program_state.forced_exit) return ERR_FORCED_EXIT; return GAME_OVER; } /* if the game is being restored, turntime is set in restore_read_command */ turntime = time(NULL); log_command(cmdidx, rep, arg); pre_rngstate = mt_nextstate(); pre_moves = moves; /* do the deed. command_input returns -1 if the command completed normally */ cmdresult = command_input(cmdidx, rep, arg); /* make sure we actually want this command to be logged */ if (cmdidx >= 0 && (cmdlist[cmdidx].flags & CMD_NOTIME) && pre_rngstate == mt_nextstate() && pre_moves == moves) log_revert_command(); /* nope, cut it out of the log */ else log_command_result(); /* log the result */ api_exit(); /* no unsafe operations after this point */ if (cmdresult != -1) return cmdresult; /* * performing a command can put the game into several different states: * - the command completes immediately: a simple move or an attack etc * multi == 0, occupation == NULL * - if a count is given, the command will (usually) take count turns * multi == count (> 0), occupation == NULL * - the command may cause a delay: for ex. putting on or removing armor * multi == -delay (< 0), occupation == NULL * multi is incremented in you_moved * - the command may take multiple moves, and require a callback to be * run for each move. example: forcing a lock * multi >= 0, occupation == callback */ if (multi >= 0 && occupation) return OCCUPATION_IN_PROGRESS; else if (multi > 0) return MULTI_IN_PROGRESS; else if (multi < 0) return POST_ACTION_DELAY; return READY_FOR_INPUT; }
enum nh_restore_status nh_restore_game(int fd, struct nh_window_procs *rwinprocs, boolean force_replay) { int playmode, irole, irace, igend, ialign; char namebuf[PL_NSIZ]; /* some compilers can't cope with the fact that all subsequent stores to error * are not dead, but become important if the error handler longjumps back * volatile is required to prevent invalid optimization based on that wrong * assumption. */ volatile enum nh_restore_status error = GAME_RESTORED; if (fd == -1) return ERR_BAD_ARGS; switch (nh_get_savegame_status(fd, NULL)) { case LS_INVALID: return ERR_BAD_FILE; case LS_DONE: return ERR_GAME_OVER; case LS_CRASHED: force_replay = TRUE; break; case LS_IN_PROGRESS: return ERR_IN_PROGRESS; case LS_SAVED: break; /* default, everything is A-OK */ } if (!api_entry_checkpoint()) goto error_out; error = ERR_BAD_FILE; replay_set_logfile(fd); /* store the fd and try to get a lock or exit */ replay_begin(); program_state.restoring = TRUE; iflags.disable_log = TRUE; /* don't log any of the commands, they're already in the log */ /* Read the log header for this game. */ replay_read_newgame(&turntime, &playmode, namebuf, &irole, &irace, &igend, &ialign); /* set special windowprocs which will autofill requests for user input * with data from the log file */ replay_setup_windowprocs(rwinprocs); startup_common(namebuf, playmode); u.initrole = irole; u.initrace = irace; u.initgend = igend; u.initalign = ialign; if (!force_replay) { error = ERR_RESTORE_FAILED; replay_run_cmdloop(TRUE, FALSE, TRUE); replay_jump_to_endpos(); if (!dorecover_fd(fd)) { replay_undo_jump_to_endpos(); goto error_out2; } replay_undo_jump_to_endpos(); wd_message(); program_state.game_running = 1; post_init_tasks(); } else { replay_run_cmdloop(TRUE, TRUE, FALSE); /* option setup only */ newgame(); /* try replaying instead */ error = ERR_REPLAY_FAILED; replay_run_cmdloop(FALSE, FALSE, TRUE); replay_sync_save(); } /* restore standard window procs */ replay_restore_windowprocs(); program_state.restoring = FALSE; iflags.disable_log = FALSE; /* clean up data used for replay */ replay_end(); log_truncate(); log_init(); /* must be called before we start writing to the log */ /* info might not have reached the ui while alternate window procs were set */ doredraw(); /* nh_start_game() does this via newgame(), but since this function doesn't * call newgame(), we have to do it here instead. */ notify_levelchange(NULL); bot(); flush_screen(); was_on_elbereth = !sengr_at("Elbereth", u.ux, u.uy); /* force botl update later */ welcome(FALSE); realtime_messages(TRUE, TRUE); update_inventory(); api_exit(); return GAME_RESTORED; error_out2: api_exit(); error_out: replay_restore_windowprocs(); program_state.restoring = FALSE; iflags.disable_log = FALSE; replay_end(); unlock_fd(fd); if (error == ERR_RESTORE_FAILED) { raw_printf("Restore failed. Attempting to replay instead.\n"); error = nh_restore_game(fd, rwinprocs, TRUE); } return error; }