/* File: util.c */ /* Purpose: Angband utilities -BEN- */ #include "angband.h" #ifdef SET_UID #ifdef PRIVATE_USER_PATH /* * Create an ".angband/" directory in the users home directory. * * ToDo: Add error handling. * ToDo: Only create the directories when actually writing files. */ void create_user_dirs(void) { char dirpath[1024]; char subdirpath[1024]; /* Get an absolute path from the filename */ path_parse(dirpath, 1024, PRIVATE_USER_PATH); /* Create the ~/.angband/ directory */ mkdir(dirpath, 0700); /* Build the path to the variant-specific sub-directory */ path_make(subdirpath, dirpath, VERSION_NAME); /* Create the directory */ mkdir(subdirpath, 0700); #ifdef USE_PRIVATE_PATHS /* Build the path to the scores sub-directory */ path_build(dirpath, sizeof(dirpath), subdirpath, "scores"); /* Create the directory */ mkdir(dirpath, 0700); /* Build the path to the savefile sub-directory */ path_build(dirpath, sizeof(dirpath), subdirpath, "bone"); /* Create the directory */ mkdir(dirpath, 0700); /* Build the path to the savefile sub-directory */ path_build(dirpath, sizeof(dirpath), subdirpath, "data"); /* Create the directory */ mkdir(dirpath, 0700); /* Build the path to the savefile sub-directory */ path_build(dirpath, sizeof(dirpath), subdirpath, "save"); /* Create the directory */ mkdir(dirpath, 0700); #endif /* USE_PRIVATE_PATHS */ } #endif /* PRIVATE_USER_PATH */ /* * Hack -- drop permissions */ void safe_setuid_drop(void) { #ifdef SAFE_SETUID #ifdef HAVE_SETEGID if (setegid(getgid()) != 0) { quit("setegid(): cannot set permissions correctly!"); } #else /* HAVE_SETEGID */ #ifdef SAFE_SETUID_POSIX if (setgid(getgid()) != 0) { quit("setgid(): cannot set permissions correctly!"); } #else /* SAFE_SETUID_POSIX */ if (setregid(getegid(), getgid()) != 0) { quit("setregid(): cannot set permissions correctly!"); } #endif /* SAFE_SETUID_POSIX */ #endif /* HAVE_SETEGID */ #endif /* SAFE_SETUID */ } /* * Hack -- grab permissions */ void safe_setuid_grab(void) { #ifdef SAFE_SETUID #ifdef HAVE_SETEGID if (setegid(player_egid) != 0) { quit("setegid(): cannot set permissions correctly!"); } #else /* HAVE_SETEGID */ #ifdef SAFE_SETUID_POSIX if (setgid(player_egid) != 0) { quit("setgid(): cannot set permissions correctly!"); } #else /* SAFE_SETUID_POSIX */ if (setregid(getegid(), getgid()) != 0) { quit("setregid(): cannot set permissions correctly!"); } #endif /* SAFE_SETUID_POSIX */ #endif /* HAVE_SETEGID */ #endif /* SAFE_SETUID */ } /* * Initialise things for multiuser machines * Pay special attention to permisions. */ void init_setuid(void) { /* Default permissions on files */ (void)umask(022); /* Get the user id (?) */ player_uid = getuid(); #ifdef VMS /* Mega-Hack -- Factor group id */ player_uid += (getgid() * 1000); #endif /* VMS */ #ifdef SAFE_SETUID #if defined(HAVE_SETEGID) || defined(SAFE_SETUID_POSIX) /* Save some info for later */ player_euid = geteuid(); player_egid = getegid(); #endif /* defined(HAVE_SETEGID) || defined(SAFE_SETUID_POSIX) */ /* XXX XXX XXX */ #if 0 /* Redundant setting necessary in case root is running the game */ /* If not root or game not setuid the following two calls do nothing */ if (setgid(getegid()) != 0) { quit("setgid(): cannot set permissions correctly!"); } if (setuid(geteuid()) != 0) { quit("setuid(): cannot set permissions correctly!"); } #endif /* 0 */ #endif /* SAFE_SETUID */ /* Drop permissions */ safe_setuid_drop(); /* Get the "user name" as a default player name */ user_name(player_name, player_uid); #ifdef PRIVATE_USER_PATH /* Create a directory for the users files. */ create_user_dirs(); #endif /* PRIVATE_USER_PATH */ } #else /* SET_UID */ void safe_setuid_drop(void) { } void safe_setuid_grab(void) { } void init_setuid(void) { } #endif /* SET_UID */ #ifdef HANDLE_SIGNALS #include /* * Handle signals -- suspend * * Actually suspend the game, and then resume cleanly */ static void handle_signal_suspend(int sig) { /* Disable handler */ (void)signal(sig, SIG_IGN); #ifdef SIGSTOP /* Flush output */ Term_fresh(); /* Suspend the "Term" */ Term_xtra(TERM_XTRA_ALIVE, 0); /* Suspend ourself */ (void)kill(0, SIGSTOP); /* Resume the "Term" */ Term_xtra(TERM_XTRA_ALIVE, 1); /* Redraw the term */ Term_redraw(); /* Flush the term */ Term_fresh(); #endif /* Restore handler */ (void)signal(sig, handle_signal_suspend); } /* * Handle signals -- simple (interrupt and quit) * * This function was causing a *huge* number of problems, so it has * been simplified greatly. We keep a global variable which counts * the number of times the user attempts to kill the process, and * we commit suicide if the user does this a certain number of times. * * We attempt to give "feedback" to the user as he approaches the * suicide thresh-hold, but without penalizing accidental keypresses. * * To prevent messy accidents, we should reset this global variable * whenever the user enters a keypress, or something like that. */ static void handle_signal_simple(int sig) { /* Disable handler */ (void)signal(sig, SIG_IGN); /* Nothing to save, just quit */ if (!character_generated || character_saved) quit(NULL); /* Count the signals */ signal_count++; /* Terminate dead characters */ if (p_ptr->state.is_dead) { /* Mark the savefile */ (void)strcpy(p_ptr->state.died_from, "Abortion"); /* Close stuff */ close_game(); /* Quit */ quit("interrupt"); } /* Allow suicide (after 5) */ else if (signal_count >= 5) { /* Cause of "death" */ (void)strcpy(p_ptr->state.died_from, "Interrupting"); /* Stop playing */ p_ptr->state.playing = FALSE; /* Suicide */ p_ptr->state.is_dead = TRUE; /* Leaving */ p_ptr->state.leaving = TRUE; /* Close stuff */ close_game(); /* Quit */ quit("interrupt"); } /* Give warning (after 4) */ else if (signal_count >= 4) { /* Make a noise */ Term_xtra(TERM_XTRA_NOISE, 0); /* Display the cause */ prtf(0, 0, "Contemplating suicide!"); /* Flush */ Term_fresh(); } /* Give warning (after 2) */ else if (signal_count >= 2) { /* Make a noise */ Term_xtra(TERM_XTRA_NOISE, 0); } /* Restore handler */ (void)signal(sig, handle_signal_simple); } /* * Handle signal -- abort, kill, etc */ static void handle_signal_abort(int sig) { /* Disable handler */ (void)signal(sig, SIG_IGN); /* Nothing to save, just quit */ if (!character_generated || character_saved) quit(NULL); /* Give a warning */ prtf(0, 23, CLR_RED "A gruesome software bug LEAPS out at you!"); /* Message */ put_fstr(45, 23, CLR_RED "Panic save..."); /* Flush output */ Term_fresh(); /* Panic Save */ p_ptr->state.panic_save = 1; /* Panic save */ (void)strcpy(p_ptr->state.died_from, "(panic save)"); /* Forbid suspend */ signals_ignore_tstp(); /* Attempt to save */ if (save_player()) { put_fstr(45, 23, CLR_RED "Panic save succeeded!"); } /* Save failed */ else { put_fstr(45, 23, CLR_RED "Panic save failed!"); } /* Flush output */ Term_fresh(); /* Quit */ quit("software bug"); } /* * Ignore SIGTSTP signals (keyboard suspend) */ void signals_ignore_tstp(void) { #ifdef SIGTSTP (void)signal(SIGTSTP, SIG_IGN); #endif } /* * Handle SIGTSTP signals (keyboard suspend) */ void signals_handle_tstp(void) { #ifdef SIGTSTP (void)signal(SIGTSTP, handle_signal_suspend); #endif } /* * Prepare to handle the relevant signals */ void signals_init(void) { #ifdef SIGHUP (void)signal(SIGHUP, SIG_IGN); #endif #ifdef SIGTSTP (void)signal(SIGTSTP, handle_signal_suspend); #endif #ifdef SIGINT (void)signal(SIGINT, handle_signal_simple); #endif #ifdef SIGQUIT (void)signal(SIGQUIT, handle_signal_simple); #endif #ifdef SIGFPE (void)signal(SIGFPE, handle_signal_abort); #endif #ifdef SIGILL (void)signal(SIGILL, handle_signal_abort); #endif #ifdef SIGTRAP (void)signal(SIGTRAP, handle_signal_abort); #endif #ifdef SIGIOT (void)signal(SIGIOT, handle_signal_abort); #endif #ifdef SIGKILL (void)signal(SIGKILL, handle_signal_abort); #endif #ifdef SIGBUS (void)signal(SIGBUS, handle_signal_abort); #endif #ifdef SIGSEGV (void)signal(SIGSEGV, handle_signal_abort); #endif #ifdef SIGTERM (void)signal(SIGTERM, handle_signal_abort); #endif #ifdef SIGPIPE (void)signal(SIGPIPE, handle_signal_abort); #endif #ifdef SIGEMT (void)signal(SIGEMT, handle_signal_abort); #endif #ifdef SIGDANGER (void)signal(SIGDANGER, handle_signal_abort); #endif #ifdef SIGSYS (void)signal(SIGSYS, handle_signal_abort); #endif #ifdef SIGXCPU (void)signal(SIGXCPU, handle_signal_abort); #endif #ifdef SIGPWR (void)signal(SIGPWR, handle_signal_abort); #endif } #else /* HANDLE_SIGNALS */ /* * Do nothing */ void signals_ignore_tstp(void) { } /* * Do nothing */ void signals_handle_tstp(void) { } /* * Do nothing */ void signals_init(void) { } #endif /* HANDLE_SIGNALS */ #ifdef SET_UID # ifndef HAS_USLEEP /* * For those systems that don't have "usleep()" but need it. * * Fake "usleep()" function grabbed from the inl netrek server -cba */ int usleep(huge usecs) { struct timeval Timer; int nfds = 0; #ifdef FD_SET fd_set *no_fds = NULL; #else int *no_fds = NULL; #endif /* Was: int readfds, writefds, exceptfds; */ /* Was: readfds = writefds = exceptfds = 0; */ /* Paranoia -- No excessive sleeping */ if (usecs > 4000000L) core("Illegal usleep() call"); /* Wait for it */ Timer.tv_sec = (usecs / 1000000L); Timer.tv_usec = (usecs % 1000000L); /* Wait for it */ if (select(nfds, no_fds, no_fds, no_fds, &Timer) < 0) { /* Hack -- ignore interrupts */ if (errno != EINTR) return -1; } /* Success */ return 0; } # endif /* !HAS_USLEEP */ /* * Hack -- External functions */ #ifndef _PWD_H # ifndef HAVE_GETPWUID extern struct passwd *getpwuid(); # endif # ifndef HAVE_GETPWNAM extern struct passwd *getpwnam(); # endif #endif /* _PWD_H */ /* * Find a default user name from the system. */ void user_name(char *buf, int id) { struct passwd *pw; /* Look up the user name */ if ((pw = getpwuid(id))) { /* Get the first 15 characters of the user name */ (void)strncpy(buf, pw->pw_name, 16); buf[15] = '\0'; #ifdef CAPITALIZE_USER_NAME /* Hack -- capitalize the user name */ if (islower(buf[0])) buf[0] = toupper(buf[0]); #endif /* CAPITALIZE_USER_NAME */ return; } /* Oops. Hack -- default to "PLAYER" */ strcpy(buf, "PLAYER"); } #endif /* SET_UID */ /* * Helper function to assert something inside an expression * * (Note that the normal assert macro can only be used * as a statement - which prevents debugging via * function wrappers.) */ bool assert_helper(cptr expr, cptr file, int line, bool result) { if (!result) { signals_ignore_tstp();\ ANG__assert_save;\ ANG__assert_fmt("\n" "Assertion failed:%s\n" "in file %s\n" "on line %d\n\n", expr, file, line); } return (result); } /* * The concept of the "file" routines below (and elsewhere) is that all * file handling should be done using as few routines as possible, since * every machine is slightly different, but these routines always have the * same semantics. * * In fact, perhaps we should use the "path_parse()" routine below to convert * from "canonical" filenames (optional leading tilde's, internal wildcards, * slash as the path seperator, etc) to "system" filenames (no special symbols, * system-specific path seperator, etc). This would allow the program itself * to assume that all filenames are "Unix" filenames, and explicitly "extract" * such filenames if needed (by "path_parse()", or perhaps "path_canon()"). * * Note that "path_temp" should probably return a "canonical" filename. * * Note that "my_fopen()" and "my_open()" and "my_make()" and "my_kill()" * and "my_move()" and "my_copy()" should all take "canonical" filenames. * * Note that "canonical" filenames use a leading "slash" to indicate an absolute * path, and a leading "tilde" to indicate a special directory, and default to a * relative path, but MSDOS uses a leading "drivename plus colon" to indicate the * use of a "special drive", and then the rest of the path is parsed "normally", * and MACINTOSH uses a leading colon to indicate a relative path, and an embedded * colon to indicate a "drive plus absolute path", and finally defaults to a file * in the current working directory, which may or may not be defined. * * We should probably parse a leading "~~/" as referring to "ANGBAND_DIR". (?) */ #ifdef ACORN /* * Most of the "file" routines for "ACORN" should be in "main-acn.c" */ #else /* ACORN */ #ifdef SET_UID /* * Extract a "parsed" path from an initial filename * Normally, we simply copy the filename into the buffer * But leading tilde symbols must be handled in a special way * Replace "~user/" by the home directory of the user named "user" * Replace "~/" by the home directory of the current user */ errr path_parse(char *buf, int max, cptr file) { cptr u, s; struct passwd *pw; char user[128]; /* Assume no result */ buf[0] = '\0'; /* No file? */ if (!file) return (-1); /* File needs no parsing */ if (file[0] != '~') { strncpy(buf, file, max - 1); /* Terminate */ buf[max - 1] = '\0'; return (0); } /* Point at the user */ u = file + 1; /* Look for non-user portion of the file */ s = strstr(u, PATH_SEP); /* Hack -- no long user names */ if (s && (s >= u + sizeof(user))) return (1); /* Extract a user name */ if (s) { int i; for (i = 0; u < s; ++i) user[i] = *u++; user[i] = '\0'; u = user; } /* Look up the "current" user */ if (u[0] == '\0') u = getlogin(); /* Look up a user (or "current" user) */ if (u) pw = getpwnam(u); else pw = getpwuid(getuid()); /* Nothing found? */ if (!pw) return (1); /* Make use of the info */ (void)strncpy(buf, pw->pw_dir, max - 1); /* Append the rest of the filename, if any */ if (s) (void)strncat(buf, s, max - 1); /* Terminate */ buf[max - 1] = '\0'; /* Success */ return (0); } #else /* SET_UID */ /* * Extract a "parsed" path from an initial filename * * This requires no special processing on simple machines, * except for verifying the size of the filename. */ errr path_parse(char *buf, int max, cptr file) { /* Accept the filename */ (void)strnfmt(buf, max, "%s", file); /* Success */ return (0); } #endif /* SET_UID */ #ifndef HAVE_MKSTEMP /* * Hack -- acquire a "temporary" file name if possible * * This filename is always in "system-specific" form. */ static errr path_temp(char *buf, int max) { cptr s; /* * Temp file * * If the following line gives you a compile-time warning, * then turn on the HAVE_MKSTEMP if you have mkstemp(). */ s = tmpnam(NULL); /* Oops */ if (!s) return (-1); /* Format to length */ (void)strnfmt(buf, max, "%s", s); /* Success */ return (0); } #endif /* HAVE_MKSTEMP */ /* * Create a new path by appending a file (or directory) to a path. * * This requires no special processing on simple machines, except * for verifying the size of the filename, but note the ability to * bypass the given "path" with certain special file-names. * * Note that the "file" may actually be a "sub-path", including * a path and a file. * * Note that this function yields a path which must be "parsed" * using the "parse" function above. */ void path_build(char *buf, int max, cptr path, cptr file) { /* Special file */ if (file[0] == '~') { /* Use the file itself */ (void)strnfmt(buf, max, "%s", file); } /* Absolute file, on "normal" systems */ else if (prefix(file, PATH_SEP) && !streq(PATH_SEP, "")) { /* Use the file itself */ (void)strnfmt(buf, max, "%s", file); } /* No path given */ else if (!path[0]) { /* Use the file itself */ (void)strnfmt(buf, max, "%s", file); } /* Path and File */ else { /* Build the new path */ (void)strnfmt(buf, max, "%s%s%s", path, PATH_SEP, file); } } /* * Hack -- replacement for "fopen()" */ FILE *my_fopen(cptr file, cptr mode) { char buf[1024]; FILE *fff; /* Hack -- Try to parse the path */ if (path_parse(buf, 1024, file)) return (NULL); /* Attempt to fopen the file anyway */ fff = fopen(buf, mode); #if defined(MAC_MPW) || defined(MACH_O_CARBON) /* Set file creator and type */ if (fff && strchr(mode, 'w')) fsetfileinfo(buf, _fcreator, _ftype); #endif /* Return open file or NULL */ return (fff); } /* * Hack -- replacement for "fclose()" */ void my_fclose(FILE *fff) { /* Require a file */ if (!fff) return; /* Close, check for error */ (void)fclose(fff); } #endif /* ACORN */ #ifdef HAVE_MKSTEMP FILE *my_fopen_temp(char *buf, int max) { int fd; /* Prepare the buffer for mkstemp */ strncpy(buf, "/tmp/anXXXXXX", max); /* Secure creation of a temporary file */ fd = mkstemp(buf); /* Check the file-descriptor */ if (fd < 0) return (NULL); /* Return a file stream */ return (fdopen(fd, "w")); } #else /* HAVE_MKSTEMP */ FILE *my_fopen_temp(char *buf, int max) { /* Generate a temporary filename */ if (path_temp(buf, max)) return (NULL); /* Open the file */ return (my_fopen(buf, "w")); } #endif /* HAVE_MKSTEMP */ /* * Hack -- replacement for "fgets()" * * Read a string, without a newline, to a file * * Process tabs, strip internal non-printables if told. */ static errr my_fgets_aux(FILE *fff, char *buf, huge n, bool strip) { huge i = 0; char *s; char tmp[1024]; /* Read a line */ if (fgets(tmp, 1024, fff)) { /* Convert weirdness */ for (s = tmp; *s; s++) { /* Handle newline */ if (*s == '\n') { /* Terminate */ buf[i] = '\0'; /* Success */ return (0); } /* Handle tabs */ else if (*s == '\t') { /* Hack -- require room */ if (i + 8 >= n) break; /* Append a space */ buf[i++] = ' '; /* Append some more spaces */ while (!(i % 8)) buf[i++] = ' '; } /* Strip non-printables if asked */ else if(!strip || isprint(*s)) { /* Copy */ buf[i++] = *s; /* Check length */ if (i >= n) break; } } } /* Nothing */ buf[0] = '\0'; /* Failure */ return (1); } /* * Hack -- replacement for "fgets()" * * Read a string, without a newline, to a file * * Process tabs, strip internal non-printables */ errr my_fgets(FILE *fff, char *buf, huge n) { return (my_fgets_aux(fff, buf, n, TRUE)); } /* * Hack -- replacement for "fgets()" * * Read a string, without a newline, to a file * * Process tabs, do not strip internal non-printables */ errr my_raw_fgets(FILE *fff, char *buf, huge n) { return (my_fgets_aux(fff, buf, n, FALSE)); } #ifdef ACORN /* * Most of the "file" routines for "ACORN" should be in "main-acn.c" * * Many of them can be rewritten now that only "fd_open()" and "fd_make()" * and "my_fopen()" should ever create files. */ #else /* ACORN */ /* * Code Warrior is a little weird about some functions */ #ifdef BEN_HACK extern int open(const char *, int, ...); extern int close(int); extern int read(int, void *, unsigned int); extern int write(int, const void *, unsigned int); extern long lseek(int, long, int); #endif /* BEN_HACK */ /* * The Macintosh is a little bit brain-dead sometimes */ #ifdef MACINTOSH # define open(N,F,M) \ ((M), open((char*)(N),F)) # define write(F,B,S) \ write(F,(char*)(B),S) #endif /* MACINTOSH */ /* * Several systems have no "O_BINARY" flag */ #ifndef O_BINARY # define O_BINARY 0 #endif /* O_BINARY */ /* * Hack -- attempt to delete a file */ errr fd_kill(cptr file) { char buf[1024]; /* Hack -- Try to parse the path */ if (path_parse(buf, 1024, file)) return (-1); /* Remove */ (void)remove(buf); /* Assume success XXX XXX XXX */ return (0); } /* * Hack -- attempt to move a file */ errr fd_move(cptr file, cptr what) { char buf[1024]; char aux[1024]; /* Hack -- Try to parse the path */ if (path_parse(buf, 1024, file)) return (-1); /* Hack -- Try to parse the path */ if (path_parse(aux, 1024, what)) return (-1); /* Rename */ (void)rename(buf, aux); /* Assume success XXX XXX XXX */ return (0); } /* * Hack -- attempt to open a file descriptor (create file) * * This function should fail if the file already exists * * Note that we assume that the file should be "binary" */ int fd_make(cptr file, int mode) { char buf[1024]; int fd; /* Hack -- Try to parse the path */ if (path_parse(buf, sizeof(buf), file)) return (-1); #if defined(MACINTOSH) /* Create the file, fail if exists, write-only, binary */ fd = open(buf, O_CREAT | O_EXCL | O_WRONLY | O_BINARY); #else /* Create the file, fail if exists, write-only, binary */ fd = open(buf, O_CREAT | O_EXCL | O_WRONLY | O_BINARY, mode); #endif #if defined(MAC_MPW) || defined(MACH_O_CARBON) /* Set file creator and type */ if (fd >= 0) fsetfileinfo(buf, _fcreator, _ftype); #endif /* Return descriptor */ return (fd); } /* * Hack -- attempt to open a file descriptor (existing file) * * Note that we assume that the file should be "binary" */ int fd_open(cptr file, int flags) { char buf[1024]; /* Hack -- Try to parse the path */ if (path_parse(buf, 1024, file)) return (-1); /* Attempt to open the file */ return (open(buf, flags | O_BINARY, 0)); } /* * Hack -- attempt to lock a file descriptor * * Legal lock types -- F_UNLCK, F_RDLCK, F_WRLCK */ errr fd_lock(int fd, int what) { /* XXX XXX */ what = what ? what : 0; /* Verify the fd */ if (fd < 0) return (-1); #ifdef SET_UID # ifdef USG # if defined(F_ULOCK) && defined(F_LOCK) /* Un-Lock */ if (what == F_UNLCK) { /* Unlock it, Ignore errors */ lockf(fd, F_ULOCK, 0); } /* Lock */ else { /* Lock the score file */ if (lockf(fd, F_LOCK, 0) != 0) return (1); } # endif # else # if defined(LOCK_UN) && defined(LOCK_EX) /* Un-Lock */ if (what == F_UNLCK) { /* Unlock it, Ignore errors */ (void)flock(fd, LOCK_UN); } /* Lock */ else { /* Lock the score file */ if (flock(fd, LOCK_EX) != 0) return (1); } # endif # endif #endif /* Success */ return (0); } /* * Hack -- attempt to seek on a file descriptor */ errr fd_seek(int fd, huge n) { huge p; /* Verify fd */ if (fd < 0) return (-1); /* Seek to the given position */ p = lseek(fd, n, SEEK_SET); /* Failure */ if (p != n) return (1); /* Success */ return (0); } /* * Hack -- attempt to read data from a file descriptor */ errr fd_read(int fd, char *buf, huge n) { /* Verify the fd */ if (fd < 0) return (-1); #ifndef SET_UID /* Read pieces */ while (n >= 16384) { /* Read a piece */ if (read(fd, buf, 16384) != 16384) return (1); /* Shorten the task */ buf += 16384; /* Shorten the task */ n -= 16384; } #endif /* Read the final piece */ if (read(fd, buf, n) != (int)n) return (1); /* Success */ return (0); } /* * Hack -- Attempt to write data to a file descriptor */ errr fd_write(int fd, cptr buf, huge n) { /* Verify the fd */ if (fd < 0) return (-1); #ifndef SET_UID /* Write pieces */ while (n >= 16384) { /* Write a piece */ if (write(fd, buf, 16384) != 16384) return (1); /* Shorten the task */ buf += 16384; /* Shorten the task */ n -= 16384; } #endif /* Write the final piece */ if (write(fd, buf, n) != (int)n) return (1); /* Success */ return (0); } /* * Hack -- attempt to close a file descriptor */ errr fd_close(int fd) { /* Verify the fd */ if (fd < 0) return (-1); /* Close */ (void)close(fd); /* XXX XXX XXX */ return (0); } #endif /* ACORN */ /* This works by masking off the lowest order set bits one at a time */ int count_bits(u32b x) { int n = 0; if (x) { do { n++; } while (0 != (x = x & (x - 1))); } return (n); } /* * Convert a decimal to a single digit hex number */ static char hexify(uint i) { return (hexsym[i % 16]); } /* * Convert a hexidecimal-digit into a decimal */ static int dehex(char c) { if (isdigit(c)) return (D2I(c)); if (islower(c)) return (A2I(c) + 10); if (isupper(c)) return (A2I(tolower(c)) + 10); return (0); } /* * Hack -- convert a printable string into real ascii * * I have no clue if this function correctly handles, for example, * parsing "\xFF" into a (signed) char. Whoever thought of making * the "sign" of a "char" undefined is a complete moron. Oh well. */ void text_to_ascii(char *buf, cptr str) { char *s = buf; /* Analyze the "ascii" string */ while (*str) { /* Backslash codes */ if (*str == '\\') { /* Skip the backslash */ str++; /* Hex-mode XXX */ if (*str == 'x') { *s = 16 * dehex(*++str); *s++ += dehex(*++str); } /* Hack -- simple way to specify "backslash" */ else if (*str == '\\') { *s++ = '\\'; } /* Hack -- simple way to specify "caret" */ else if (*str == '^') { *s++ = '^'; } /* Hack -- simple way to specify "space" */ else if (*str == 's') { *s++ = ' '; } /* Hack -- simple way to specify Escape */ else if (*str == 'e') { *s++ = ESCAPE; } /* Backspace */ else if (*str == 'b') { *s++ = '\b'; } /* Newline */ else if (*str == 'n') { *s++ = '\n'; } /* Return */ else if (*str == 'r') { *s++ = '\r'; } /* Tab */ else if (*str == 't') { *s++ = '\t'; } /* Skip the final char */ str++; } /* Normal Control codes */ else if (*str == '^') { str++; *s++ = (*str++ & 037); } /* Normal chars */ else { *s++ = *str++; } } /* Terminate */ *s = '\0'; } /* * Hack -- convert a string into a printable form */ void ascii_to_text(char *buf, cptr str) { char *s = buf; /* Analyze the "ascii" string */ while (*str) { byte i = (byte)(*str++); if (i == ESCAPE) { *s++ = '\\'; *s++ = 'e'; } else if (i == ' ') { *s++ = '\\'; *s++ = 's'; } else if (i == '\b') { *s++ = '\\'; *s++ = 'b'; } else if (i == '\t') { *s++ = '\\'; *s++ = 't'; } else if (i == '\n') { *s++ = '\\'; *s++ = 'n'; } else if (i == '\r') { *s++ = '\\'; *s++ = 'r'; } else if (i == '^') { *s++ = '\\'; *s++ = '^'; } else if (i == '\\') { *s++ = '\\'; *s++ = '\\'; } else if (i < 32) { *s++ = '^'; *s++ = i + 64; } else if (i < 127) { *s++ = i; } else { *s++ = '\\'; *s++ = 'x'; *s++ = hexify(i / 16); *s++ = hexify(i % 16); } } /* Terminate */ *s = '\0'; } /* * The "macro" package * * Functions are provided to manipulate a collection of macros, each * of which has a trigger pattern string and a resulting action string * and a small set of flags. */ /* * Determine if any macros have ever started with a given character. */ static bool macro__use[256]; /* * Find the macro (if any) which exactly matches the given pattern */ sint macro_find_exact(cptr pat) { int i; /* Nothing possible */ if (!macro__use[(byte)(pat[0])]) { return (-1); } /* Scan the macros */ for (i = 0; i < macro__num; ++i) { /* Skip macros which do not match the pattern */ if (!streq(macro__pat[i], pat)) continue; /* Found one */ return (i); } /* No matches */ return (-1); } /* * Find the first macro (if any) which contains the given pattern */ static sint macro_find_check(cptr pat) { int i; /* Nothing possible */ if (!macro__use[(byte)(pat[0])]) { return (-1); } /* Scan the macros */ for (i = 0; i < macro__num; ++i) { /* Skip macros which do not contain the pattern */ if (!prefix(macro__pat[i], pat)) continue; /* Found one */ return (i); } /* Nothing */ return (-1); } /* * Find the first macro (if any) which contains the given pattern and more */ static sint macro_find_maybe(cptr pat) { int i; /* Nothing possible */ if (!macro__use[(byte)(pat[0])]) { return (-1); } /* Scan the macros */ for (i = 0; i < macro__num; ++i) { /* Skip macros which do not contain the pattern */ if (!prefix(macro__pat[i], pat)) continue; /* Skip macros which exactly match the pattern XXX XXX */ if (streq(macro__pat[i], pat)) continue; /* Found one */ return (i); } /* Nothing */ return (-1); } /* * Find the longest macro (if any) which starts with the given pattern */ static sint macro_find_ready(cptr pat) { int i, t, n = -1, s = -1; /* Nothing possible */ if (!macro__use[(byte)(pat[0])]) { return (-1); } /* Scan the macros */ for (i = 0; i < macro__num; ++i) { /* Skip macros which are not contained by the pattern */ if (!prefix(pat, macro__pat[i])) continue; /* Obtain the length of this macro */ t = strlen(macro__pat[i]); /* Only track the "longest" pattern */ if ((n >= 0) && (s > t)) continue; /* Track the entry */ n = i; s = t; } /* Result */ return (n); } /* * Add a macro definition (or redefinition). * * We should use "act == NULL" to "remove" a macro, but this might make it * impossible to save the "removal" of a macro definition. XXX XXX XXX * * We should consider refusing to allow macros which contain existing macros, * or which are contained in existing macros, because this would simplify the * macro analysis code. XXX XXX XXX * * We should consider removing the "command macro" crap, and replacing it * with some kind of "powerful keymap" ability, but this might make it hard * to change the "roguelike" option from inside the game. XXX XXX XXX */ void macro_add(cptr pat, cptr act) { int n; /* Paranoia -- require data */ if (!pat || !act) return; /* Look for any existing macro */ n = macro_find_exact(pat); /* Replace existing macro */ if (n >= 0) { /* Free the old macro action */ string_free(macro__act[n]); } /* Create a new macro */ else { /* Acquire a new index */ n = macro__num++; /* Save the pattern */ macro__pat[n] = string_make(pat); } /* Save the action */ macro__act[n] = string_make(act); /* Efficiency */ macro__use[(byte)(pat[0])] = TRUE; } /* This is never used. */ #if 0 /* * Initialize the "macro" package */ errr macro_init(void) { /* Macro patterns */ C_MAKE(macro__pat, MACRO_MAX, cptr); /* Macro actions */ C_MAKE(macro__act, MACRO_MAX, cptr); /* Success */ return (0); } #endif /* 0 */ /* * Local variable -- we are inside a "macro action" * * Do not match any macros until "ascii 30" is found. */ static bool parse_macro = FALSE; /* * Local variable -- we are inside a "macro trigger" * * Strip all keypresses until a low ascii value is found. */ static bool parse_under = FALSE; /* * Flush all input chars. Actually, remember the flush, * and do a "special flush" before the next "inkey()". * * This is not only more efficient, but also necessary to make sure * that various "inkey()" codes are not "lost" along the way. */ void flush(void) { /* Do it later */ p_ptr->cmd.inkey_xtra = TRUE; } /* * Helper function called only from "inkey()" * * This function does almost all of the "macro" processing. * * We use the "Term_key_push()" function to handle "failed" macros, as well * as "extra" keys read in while choosing the proper macro, and also to hold * the action for the macro, plus a special "ascii 30" character indicating * that any macro action in progress is complete. Embedded macros are thus * illegal, unless a macro action includes an explicit "ascii 30" character, * which would probably be a massive hack, and might break things. * * Only 500 (0+1+2+...+29+30) milliseconds may elapse between each key in * the macro trigger sequence. If a key sequence forms the "prefix" of a * macro trigger, 500 milliseconds must pass before the key sequence is * known not to be that macro trigger. XXX XXX XXX */ static char inkey_aux(void) { int k = 0, n, p = 0, w = 0; char ch; cptr pat, act; char buf[1024]; /* Wait for a keypress */ (void)(Term_inkey(&ch, TRUE, TRUE)); /* End "macro action" */ if (ch == 30) parse_macro = FALSE; /* Inside "macro action" */ if (ch == 30) return (ch); /* Inside "macro action" */ if (parse_macro) return (ch); /* Inside "macro trigger" */ if (parse_under) return (ch); /* Save the first key, advance */ buf[p++] = ch; buf[p] = '\0'; /* Check for possible macro */ k = macro_find_check(buf); /* No macro pending */ if (k < 0) return (ch); /* Wait for a macro, or a timeout */ while (TRUE) { /* Check for pending macro */ k = macro_find_maybe(buf); /* No macro pending */ if (k < 0) break; /* Check for (and remove) a pending key */ if (0 == Term_inkey(&ch, FALSE, TRUE)) { /* Append the key */ buf[p++] = ch; buf[p] = '\0'; /* Restart wait */ w = 0; } /* No key ready */ else { /* Increase "wait" */ w += 10; /* Excessive delay */ if (w >= 100) break; /* Delay */ Term_xtra(TERM_XTRA_DELAY, w); } } /* Check for available macro */ k = macro_find_ready(buf); /* No macro available */ if (k < 0) { /* Push all the keys back on the queue */ while (p > 0) { /* Push the key, notice over-flow */ if (Term_key_push(buf[--p])) return (0); } /* Wait for (and remove) a pending key */ (void)Term_inkey(&ch, TRUE, TRUE); /* Return the key */ return (ch); } /* Get the pattern */ pat = macro__pat[k]; /* Get the length of the pattern */ n = strlen(pat); /* Push the "extra" keys back on the queue */ while (p > n) { /* Push the key, notice over-flow */ if (Term_key_push(buf[--p])) return (0); } /* Begin "macro action" */ parse_macro = TRUE; /* Push the "end of macro action" key */ if (Term_key_push(30)) return (0); /* Access the macro action */ act = macro__act[k]; /* Get the length of the action */ n = strlen(act); /* Push the macro "action" onto the key queue */ while (n > 0) { /* Push the key, notice over-flow */ if (Term_key_push(act[--n])) return (0); } /* Hack -- Force "inkey()" to call us again */ return (0); } /* * Mega-Hack -- special "inkey_next" pointer. XXX XXX XXX * * This special pointer allows a sequence of keys to be "inserted" into * the stream of keys returned by "inkey()". This key sequence will not * trigger any macros, and cannot be bypassed by the Borg. It is used * in Angband to handle "keymaps". */ static cptr inkey_next = NULL; #ifdef ALLOW_BORG /* * Mega-Hack -- special "inkey_hack" hook. XXX XXX XXX * * This special function hook allows the "Borg" (see elsewhere) to take * control of the "inkey()" function, and substitute in fake keypresses. */ char (*inkey_hack) (int flush_first) = NULL; #endif /* ALLOW_BORG */ /* * Get a keypress from the user. * * This function recognizes a few "global parameters". These are variables * which, if set to TRUE before calling this function, will have an effect * on this function, and which are always reset to FALSE by this function * before this function returns. Thus they function just like normal * parameters, except that most calls to this function can ignore them. * * If "inkey_xtra" is TRUE, then all pending keypresses will be flushed, * and any macro processing in progress will be aborted. This flag is * set by the "flush()" function, which does not actually flush anything * itself, but rather, triggers delayed input flushing via "inkey_xtra". * * If "inkey_scan" is TRUE, then we will immediately return "zero" if no * keypress is available, instead of waiting for a keypress. * * If "inkey_base" is TRUE, then all macro processing will be bypassed. * If "inkey_base" and "inkey_scan" are both TRUE, then this function will * not return immediately, but will wait for a keypress for as long as the * normal macro matching code would, allowing the direct entry of macro * triggers. The "inkey_base" flag is extremely dangerous! * * If "inkey_flag" is TRUE, then we will assume that we are waiting for a * normal command, and we will only show the cursor if "hilite_player" is * TRUE (or if the player is in a store), instead of always showing the * cursor. The various "main-xxx.c" files should avoid saving the game * in response to a "menu item" request unless "inkey_flag" is TRUE, to * prevent savefile corruption. * * If we are waiting for a keypress, and no keypress is ready, then we will * refresh (once) the window which was active when this function was called. * * Note that "back-quote" is automatically converted into "escape" for * convenience on machines with no "escape" key. This is done after the * macro matching, so the user can still make a macro for "backquote". * * Note the special handling of "ascii 30" (ctrl-caret, aka ctrl-shift-six) * and "ascii 31" (ctrl-underscore, aka ctrl-shift-minus), which are used to * provide support for simple keyboard "macros". These keys are so strange * that their loss as normal keys will probably be noticed by nobody. The * "ascii 30" key is used to indicate the "end" of a macro action, which * allows recursive macros to be avoided. The "ascii 31" key is used by * some of the "main-xxx.c" files to introduce macro trigger sequences. * * Hack -- we use "ascii 29" (ctrl-right-bracket) as a special "magic" key, * which can be used to give a variety of "sub-commands" which can be used * any time. These sub-commands could include commands to take a picture of * the current screen, to start/stop recording a macro action, etc. * * If "angband_term[0]" is not active, we will make it active during this * function, so that the various "main-xxx.c" files can assume that input * is only requested (via "Term_inkey()") when "angband_term[0]" is active. * * Mega-Hack -- This function is used as the entry point for clearing the * "signal_count" variable, and of the "character_saved" variable. * * Hack -- Note the use of "inkey_next" to allow "keymaps" to be processed. * * Mega-Hack -- Note the use of "inkey_hack" to allow the "Borg" to steal * control of the keyboard from the user. */ char inkey(void) { int v; char kk; char ch = 0; bool done = FALSE; term *old = Term; /* Hack -- Use the "inkey_next" pointer */ if (inkey_next && *inkey_next && !p_ptr->cmd.inkey_xtra) { /* Get next character, and advance */ ch = *inkey_next++; /* Cancel the various "global parameters" */ p_ptr->cmd.inkey_base = FALSE; p_ptr->cmd.inkey_xtra = FALSE; p_ptr->cmd.inkey_flag = FALSE; p_ptr->cmd.inkey_scan = FALSE; /* Accept result */ return (ch); } /* Forget pointer */ inkey_next = NULL; #ifdef ALLOW_BORG /* Mega-Hack -- Use the special hook */ if (inkey_hack && ((ch = (*inkey_hack) (p_ptr->cmd.inkey_xtra)) != 0)) { /* Cancel the various "global parameters" */ p_ptr->cmd.inkey_base = FALSE; p_ptr->cmd.inkey_xtra = FALSE; p_ptr->cmd.inkey_flag = FALSE; p_ptr->cmd.inkey_scan = FALSE; /* Accept result */ return (ch); } #endif /* ALLOW_BORG */ /* Hack -- handle delayed "flush()" */ if (p_ptr->cmd.inkey_xtra) { /* End "macro action" */ parse_macro = FALSE; /* End "macro trigger" */ parse_under = FALSE; /* Forget old keypresses */ Term_flush(); } /* Access cursor state */ (void)Term_get_cursor(&v); /* Show the cursor if waiting, except sometimes in "command" mode */ if (!p_ptr->cmd.inkey_scan && (!p_ptr->cmd.inkey_flag || hilite_player || character_icky)) { /* Show the cursor */ (void)Term_set_cursor(1); } /* Hack -- Activate main screen */ Term_activate(angband_term[0]); /* Get a key */ while (!ch) { /* Hack -- Handle "inkey_scan" */ if (!p_ptr->cmd.inkey_base && p_ptr->cmd.inkey_scan && (0 != Term_inkey(&kk, FALSE, FALSE))) { break; } /* Hack -- Flush output once when no key ready */ if (!done && (0 != Term_inkey(&kk, FALSE, FALSE))) { /* Hack -- activate proper term */ Term_activate(old); /* Flush output */ Term_fresh(); /* Hack -- activate main screen */ Term_activate(angband_term[0]); /* Mega-Hack -- reset saved flag */ character_saved = FALSE; /* Mega-Hack -- reset signal counter */ signal_count = 0; /* Only once */ done = TRUE; } /* Hack -- Handle "inkey_base" */ if (p_ptr->cmd.inkey_base) { int w = 0; /* Wait forever */ if (!p_ptr->cmd.inkey_scan) { /* Wait for (and remove) a pending key */ if (0 == Term_inkey(&ch, TRUE, TRUE)) { /* Done */ break; } /* Oops */ break; } /* Wait */ while (TRUE) { /* Check for (and remove) a pending key */ if (0 == Term_inkey(&ch, FALSE, TRUE)) { /* Done */ break; } /* No key ready */ else { /* Increase "wait" */ w += 10; /* Excessive delay */ if (w >= 100) break; /* Delay */ Term_xtra(TERM_XTRA_DELAY, w); } } /* Done */ break; } /* Get a key (see above) */ ch = inkey_aux(); /* Handle "control-right-bracket" */ if (ch == 29) { /* Strip this key */ ch = 0; /* Continue */ continue; } /* Treat back-quote as escape */ if (ch == '`') ch = ESCAPE; /* End "macro trigger" */ if (parse_under && (ch <= 32)) { /* Strip this key */ ch = 0; /* End "macro trigger" */ parse_under = FALSE; } /* Handle "control-caret" */ if (ch == 30) { /* Strip this key */ ch = 0; } /* Handle "control-underscore" */ else if (ch == 31) { /* Strip this key */ ch = 0; /* Begin "macro trigger" */ parse_under = TRUE; } /* Inside "macro trigger" */ else if (parse_under) { /* Strip this key */ ch = 0; } } /* Hack -- restore the term */ Term_activate(old); /* Restore the cursor */ (void)Term_set_cursor(v); /* Cancel the various "global parameters" */ p_ptr->cmd.inkey_base = FALSE; p_ptr->cmd.inkey_xtra = FALSE; p_ptr->cmd.inkey_flag = FALSE; p_ptr->cmd.inkey_scan = FALSE; /* Return the keypress */ return (ch); } /* * The "quark" package * * This package is used to reduce the memory usage of object inscriptions. * * We use dynamic string allocation because otherwise it is necessary to * pre-guess the amount of quark activity. We limit the total number of * quarks, but this is much easier to "expand" as needed. XXX XXX XXX * * Two objects with the same inscription will have the same "quark" index. * * Some code uses "zero" to indicate the non-existance of a quark. * * Note that "quark zero" is NULL and should never be "dereferenced". * * ToDo: Automatically resize the array if necessary. */ /* * The number of quarks */ static s16b quark__num; /* * The pointers to the quarks [QUARK_MAX] */ static cptr *quark__str; /* * Refcount for Quarks */ static u16b *quark__ref; /* * Add a new "quark" to the set of quarks. */ s16b quark_add(cptr str) { int i; int posn = 0; /* Look for an existing quark */ for (i = 1; i < quark__num; i++) { /* Test refcount */ if (!quark__ref[i]) continue; /* Check for equality */ if (streq(quark__str[i], str)) { /* Increase refcount */ quark__ref[i]++; return (i); } } /* Look for an empty quark */ for (i = 1; i < quark__num; i++) { if (!quark__ref[i]) { posn = i; break; } } /* Did we fail to find room? */ if (!posn) { /* Paranoia -- Require room */ if (quark__num == QUARK_MAX) { /* Paranoia - no room? */ return (0); } else { /* Use new quark */ posn = quark__num; /* New maximal quark */ quark__num++; } } /* Add a new quark */ quark__str[posn] = string_make(str); /* One use of this quark */ quark__ref[posn] = 1; /* Return the index */ return (posn); } /* * Like quark_add(), but take a format string. */ s16b quark_fmt(cptr str, ...) { va_list vp; char buf[1024]; /* Begin the Varargs Stuff */ va_start(vp, str); /* Format the args, save the length */ (void)vstrnfmt(buf, 1024, str, &vp); /* End the Varargs Stuff */ va_end(vp); /* Quark stuff */ return (quark_add(buf)); } /* * Remove the quark */ void quark_remove(s16b *i) { /* Only need to remove real quarks */ if (!(*i)) return; /* Verify */ if ((*i < 0) || (*i >= quark__num)) return; /* Decrease refcount */ quark__ref[*i]--; /* Deallocate? */ if (!quark__ref[*i]) { string_free(quark__str[*i]); quark__str[*i] = NULL; } /* No longer have a quark here */ *i = 0; } /* * Duplicate a quark */ void quark_dup(s16b i) { /* Verify */ if ((i < 0) || (i >= quark__num)) return; /* Paranoia */ if (!quark__ref[i]) return; /* Increase refcount */ quark__ref[i]++; } /* * This function looks up a quark */ cptr quark_str(s16b i) { cptr q; /* Verify */ if ((i < 0) || (i >= quark__num)) return (NULL); /* Get the quark */ q = quark__str[i]; /* Return the quark */ return (q); } /* * Initialize the "quark" package */ errr quarks_init(void) { /* Quark variables */ C_MAKE(quark__str, QUARK_MAX, cptr); C_MAKE(quark__ref, QUARK_MAX, u16b); quark__num = 1; /* Success */ return (0); } /* * Free the entire "quark" package */ errr quarks_free(void) { int i; /* Free the "quarks" */ for (i = 1; i < quark__num; i++) { /* Paranoia - only try to free existing quarks */ if (quark__str[i]) { string_free(quark__str[i]); } } /* Free the list of "quarks" */ FREE((void *)quark__str); FREE((void *)quark__ref); /* Success */ return (0); } /* * The "message memorization" package. * * Each call to "message_add(s)" will add a new "most recent" message * to the "message recall list", using the contents of the string "s". * * The number of memorized messages is available as "message_num()". * * Old messages can be retrieved by "message_str(age)", where the "age" * of the most recently memorized message is zero, and the oldest "age" * which is available is "message_num() - 1". Messages outside this * range are returned as the empty string. * * The messages are stored in a special manner that maximizes "efficiency", * that is, we attempt to maximize the number of semi-sequential messages * that can be retrieved, given a limited amount of storage space, without * causing the memorization of new messages or the recall of old messages * to be too expensive. * * We keep a buffer of chars to hold the "text" of the messages, more or * less in the order they were memorized, and an array of offsets into that * buffer, representing the actual messages, but we allow the "text" to be * "shared" by two messages with "similar" ages, as long as we never cause * sharing to reach too far back in the the buffer. * * The implementation is complicated by the fact that both the array of * offsets, and the buffer itself, are both treated as "circular arrays" * for efficiency purposes, but the strings may not be "broken" across * the ends of the array. * * When we want to memorize a new message, we attempt to "reuse" the buffer * space by checking for message duplication within the recent messages. * * Otherwise, if we need more buffer space, we grab a full quarter of the * total buffer space at a time, to keep the reclamation code efficient. * * The "message_add()" function is rather "complex", because it must be * extremely efficient, both in space and time, for use with the Borg. */ /* * The next "free" index to use */ static u16b message__next; /* * The index of the oldest message (none yet) */ static u16b message__last; /* * The next "free" offset */ static u16b message__head; /* * The offset to the oldest used char (none yet) */ static u16b message__tail; /* * The array[MESSAGE_MAX] of offsets, by index */ static u16b *message__ptr; /* * The array[MESSAGE_BUF] of chars, by offset */ static char *message__buf; /* * The array[MESSAGE_MAX] of u16b for the types of messages */ static u16b *message__type; /* * Table of colors associated to message-types */ static byte message__color[MSG_MAX]; /* * Wrapper function to get values out of message__color */ byte get_msg_type_color(byte a) { /* Paranoia */ if (a >= MSG_MAX) return TERM_WHITE; /* Return the color */ return (message__color[(int)a]); } /* * How many messages are "available"? */ s16b message_num(void) { /* Determine how many messages are "available" */ return (message__next + MESSAGE_MAX - message__last) % MESSAGE_MAX; } /* * Recall the "text" of a saved message */ cptr message_str(s16b age) { s16b x; s16b o; cptr s; /* Forgotten messages have no text */ if ((age < 0) || (age >= message_num())) return (""); /* Get the "logical" index */ x = (message__next + MESSAGE_MAX - (age + 1)) % MESSAGE_MAX; /* Get the "offset" for the message */ o = message__ptr[x]; /* Get the message text */ s = &message__buf[o]; /* Return the message text */ return (s); } /* * Recall the "type" of a saved message */ u16b message_type(s16b age) { s16b x; /* Forgotten messages have no special color */ if ((age < 0) || (age >= message_num())) return (TERM_WHITE); /* Get the "logical" index */ x = (message__next + MESSAGE_MAX - (age + 1)) % MESSAGE_MAX; /* Return the message type */ return (message__type[x]); } /* * Recall the "color" of a saved message */ byte message_color(s16b age) { return message__color[message_type(age)]; } errr message_color_define(u16b type, byte color) { /* Ignore illegal types */ if (type >= MSG_MAX) return (1); /* Store the color */ message__color[type] = color % 16; /* Success */ return (0); } /* * Add a new message, with great efficiency * * We must ignore long messages to prevent internal overflow, since we * assume that we can always get enough space by advancing "message__tail" * by one quarter the total buffer space. * * We must not attempt to optimize using a message index or buffer space * which is "far away" from the most recent entries, or we will lose a lot * of messages when we "expire" the old message index and/or buffer space. * * We attempt to minimize the use of "string compare" operations in this * function, because they are expensive when used in mass quantities. */ void message_add(cptr str, u16b type) { int m, n, k, i, x, o; char w[1024]; cptr u; char *v; /*** Step 1 -- Analyze the message ***/ /* Hack -- Ignore "non-messages" */ if (!str) return; /* Message length */ n = strlen(str); /* Hack -- Ignore "long" messages */ if (n >= MESSAGE_BUF / 4) return; /*** Step 2 -- Attempt to optimize ***/ /* Limit number of messages to check */ m = message_num(); /* Limit number of messages to check */ k = m / 4; /* Limit number of messages to check */ if (k > 32) k = 32; /* Check previous message */ for (i = message__next; m; m--) { int j = 1; char buf[1024]; char *t; cptr old; /* Back up, wrap if needed */ if (i-- == 0) i = MESSAGE_MAX - 1; /* Index */ o = message__ptr[i]; /* Get the old string */ old = &message__buf[o]; /* Skip small messages */ if (!old) continue; strcpy(buf, old); /* Find multiple */ for (t = buf; *t && (*t != '<'); t++) ; if (*t) { /* Message is too small */ if (strlen(buf) < 6) break; /* Drop the space */ *(t - 1) = '\0'; /* Get multiplier */ j = atoi(++t); } /* Limit the multiplier to 1000 */ if (buf && streq(buf, str) && (j < 1000)) { j++; /* Overwrite */ message__next = i; str = w; /* Write it out */ strnfmt(w, 1024, "%s <%dx>", buf, j); /* Message length */ n = strlen(str); } /* Done */ break; } /* Start just after the most recent message */ i = message__next; /* Check the last few messages for duplication */ for (; k; k--) { u16b q; cptr old; /* Back up, wrap if needed */ if (i-- == 0) i = MESSAGE_MAX - 1; /* Stop before oldest message */ if (i == message__last) break; /* Index */ o = message__ptr[i]; /* Extract "distance" from "head" */ q = (message__head + MESSAGE_BUF - o) % MESSAGE_BUF; /* Do not optimize over large distances */ if (q >= MESSAGE_BUF / 4) continue; /* Get the old string */ old = &message__buf[o]; /* Compare */ if (!streq(old, str)) continue; /* Get the next available message index */ x = message__next; /* Advance 'message__next', wrap if needed */ if (++message__next == MESSAGE_MAX) message__next = 0; /* Kill last message if needed */ if (message__next == message__last) { /* Advance 'message__last', wrap if needed */ if (++message__last == MESSAGE_MAX) message__last = 0; } /* Assign the starting address */ message__ptr[x] = message__ptr[i]; /* Store the message type */ message__type[x] = type; /* Success */ return; } /*** Step 3 -- Ensure space before end of buffer ***/ /* Kill messages, and wrap, if needed */ if (message__head + (n + 1) >= MESSAGE_BUF) { /* Kill all "dead" messages */ for (i = message__last; TRUE; i++) { /* Wrap if needed */ if (i == MESSAGE_MAX) i = 0; /* Stop before the new message */ if (i == message__next) break; /* Get offset */ o = message__ptr[i]; /* Kill "dead" messages */ if (o >= message__head) { /* Track oldest message */ message__last = i + 1; } } /* Wrap "tail" if needed */ if (message__tail >= message__head) message__tail = 0; /* Start over */ message__head = 0; } /*** Step 4 -- Ensure space for actual characters ***/ /* Kill messages, if needed */ if (message__head + (n + 1) > message__tail) { /* Advance to new "tail" location */ message__tail += (MESSAGE_BUF / 4); /* Kill all "dead" messages */ for (i = message__last; TRUE; i++) { /* Wrap if needed */ if (i == MESSAGE_MAX) i = 0; /* Stop before the new message */ if (i == message__next) break; /* Get offset */ o = message__ptr[i]; /* Kill "dead" messages */ if ((o >= message__head) && (o < message__tail)) { /* Track oldest message */ message__last = i + 1; } } } /*** Step 5 -- Grab a new message index ***/ /* Get the next available message index */ x = message__next; /* Advance 'message__next', wrap if needed */ if (++message__next == MESSAGE_MAX) message__next = 0; /* Kill last message if needed */ if (message__next == message__last) { /* Advance 'message__last', wrap if needed */ if (++message__last == MESSAGE_MAX) message__last = 0; } /*** Step 6 -- Insert the message text ***/ /* Assign the starting address */ message__ptr[x] = message__head; /* Inline 'strcpy(message__buf + message__head, str)' */ v = message__buf + message__head; for (u = str; *u;) *v++ = *u++; *v = '\0'; /* Advance the "head" pointer */ message__head += (n + 1); /* Store the message type */ message__type[x] = type; } /* * Initialize the "message" package */ errr messages_init(void) { /* Message variables */ C_MAKE(message__ptr, MESSAGE_MAX, u16b); C_MAKE(message__buf, MESSAGE_BUF, char); C_MAKE(message__type, MESSAGE_MAX, u16b); /* Init the message colors to white */ (void)C_BSET(message__color, TERM_WHITE, MSG_MAX, byte); /* Hack -- No messages yet */ message__tail = MESSAGE_BUF; /* Success */ return (0); } /* * Free the "message" package */ void messages_free(void) { /* Free the messages */ FREE(message__ptr); FREE(message__buf); FREE(message__type); } /* * Hack -- flush */ static void msg_flush(int x) { if (!p_ptr->state.skip_more && !auto_more) { /* Pause for response */ prtf(x, 0, CLR_L_BLUE "-more-"); /* Get an acceptable keypress */ while (1) { int cmd = inkey(); if (cmd == ESCAPE) { /* Skip all the prompt until player's turn */ p_ptr->state.skip_more = TRUE; break; } if (quick_messages) break; if (cmd == ' ') break; if ((cmd == '\n') || (cmd == '\r')) break; bell("Illegal response to a 'more' prompt!"); } } /* Clear the line */ Term_erase(0, 0, 255); } static int message_column = 0; /* * Output a message to the top line of the screen. * * Break long messages into multiple pieces (40-72 chars). * * Allow multiple short messages to "share" the top line. * * Prompt the user to make sure he has a chance to read them. * * These messages are memorized for later reference (see above). * * We could do a "Term_fresh()" to provide "flicker" if needed. * * The global "msg_flag" variable can be cleared to tell us to "erase" any * "pending" messages still on the screen, instead of using "msg_flush()". * This should only be done when the user is known to have read the message. * * We must be very careful about using the "msgf()" functions without * explicitly calling the special "message_flush()" function, since this may * result in the loss of information if the screen is cleared, or if anything * is displayed on the top line. * * Hack -- Note that "msgf(NULL)" will clear the top line * even if no messages are pending. This is probably a hack. */ static void msg_print_aux(u16b type, cptr msg) { int n; char *t; char buf[1024]; byte color = TERM_WHITE; /* Hack -- fake monochrome */ if (!use_color) type = MSG_GENERIC; /* Hack -- Reset */ if (!msg_flag) message_column = 0; /* Message Length */ n = (msg ? strlen(msg) : 0); /* Hack -- flush when requested or needed */ if (message_column && (!msg || ((message_column + n) > 72))) { /* Flush */ msg_flush(message_column); /* Forget it */ msg_flag = FALSE; /* Reset */ message_column = 0; } /* No message */ if (!msg) return; /* Paranoia */ if (n > 1000) return; /* Memorize the message (if legal) */ if (character_generated && !(p_ptr->state.is_dead)) message_add(msg, type); /* Window stuff */ p_ptr->window |= (PW_MESSAGE); /* Copy it */ strcpy(buf, msg); /* Analyze the buffer */ t = buf; /* Get the color of the message (if legal) */ if (message__color) color = message__color[type]; /* HACK -- no "black" messages */ if (color == TERM_DARK) color = TERM_WHITE; /* Split message */ while (n > 72) { char oops; int check, split; /* Default split */ split = 72; /* Find the "best" split point */ for (check = 40; check < 72; check++) { /* Found a valid split point */ if (t[check] == ' ') split = check; } /* Save the split character */ oops = t[split]; /* Split the message */ t[split] = '\0'; /* Display part of the message */ prtf(0, 0, "%s%s", color_seq[color], t); /* Flush it */ msg_flush(split + 1); /* Restore the split character */ t[split] = oops; /* Insert a space */ t[--split] = ' '; /* Prepare to recurse on the rest of "buf" */ t += split; n -= split; } /* Display the tail of the message */ prtf(message_column, 0, "%s%s", color_seq[color], t); /* Remember the message */ msg_flag = TRUE; /* Remember the position */ message_column += n + 1; /* Optional refresh */ if (fresh_after) Term_fresh(); } static u16b current_message_type = MSG_GENERIC; /* * Change the message type, and parse the following * format string. See defines.h for the macro this * is used in. */ void set_message_type(char *buf, uint max, cptr fmt, va_list *vp) { cptr str; /* Unused parameter */ (void)fmt; /* Get the argument - and set the message type */ current_message_type = va_arg(*vp, int); /* Get the string to format with. */ str = va_arg(*vp, cptr); /* Expand the string */ vstrnfmt(buf, max, str, vp); } /* * Display a formatted message, using "vstrnfmt()" and "msg_print()". */ void msgf(cptr fmt, ...) { va_list vp; int i; char buf[1024]; /* Set the message type */ current_message_type = MSG_GENERIC; /* Begin the Varargs Stuff */ va_start(vp, fmt); /* Format the args, save the length */ (void)vstrnfmt(buf, 1024, fmt, &vp); /* End the Varargs Stuff */ va_end(vp); sound(current_message_type); /* Clean the string of '\n' characters */ for (i = 0; buf[i]; i++) { /* Erase carriage returns */ if (buf[i] == '\n') buf[i] = ' '; } /* Display */ msg_print_aux(current_message_type, buf); } /* * Process a message effect * * (The "extra" parameter is currently unused) */ void msg_effect(u16b type, s16b extra) { /* Unused parameters */ (void)type; (void)extra; } /* * Print the queued messages. */ void message_flush(void) { /* Hack -- Reset */ if (!msg_flag) message_column = 0; /* Flush when needed */ if (message_column) { /* Print pending messages */ msg_flush(message_column); /* Forget it */ msg_flag = FALSE; /* Reset */ message_column = 0; } } /* * Check a char for "vowel-hood" */ bool is_a_vowel(int ch) { switch (ch) { case 'a': case 'e': case 'i': case 'o': case 'u': case 'A': case 'E': case 'I': case 'O': case 'U': return (TRUE); } return (FALSE); } /* * Hack -- special buffer to hold the action of the current keymap */ static char request_command_buffer[256]; /* * Request a command from the user. * * Sets p_ptr->cmd.cmd, p_ptr->cmd.dir, p_ptr->cmd.rep, * p_ptr->cmd.arg. May modify p_ptr->cmd.new. * * Note that "caret" ("^") is treated specially, and is used to * allow manual input of control characters. This can be used * on many machines to request repeated tunneling (Ctrl-H) and * on the Macintosh to request "Control-Caret". * * Note that "backslash" is treated specially, and is used to bypass any * keymap entry for the following character. This is useful for macros. * * Note that this command is used both in the dungeon and in * stores, and must be careful to work in both situations. * * Note that "p_ptr->cmd.new" may not work any more. XXX XXX XXX */ void request_command(int shopping) { int i; char cmd; int mode; cptr act; /* Roguelike */ if (rogue_like_commands) { mode = KEYMAP_MODE_ROGUE; } /* Original */ else { mode = KEYMAP_MODE_ORIG; } /* No command yet */ p_ptr->cmd.cmd = 0; /* No "argument" yet */ p_ptr->cmd.arg = 0; /* No "direction" yet */ p_ptr->cmd.dir = 0; /* Get command */ while (1) { /* Hack -- auto-commands */ if (p_ptr->cmd.new) { /* Flush messages */ message_flush(); /* Use auto-command */ cmd = (char)p_ptr->cmd.new; /* Forget it */ p_ptr->cmd.new = 0; } /* Get a keypress in "command" mode */ else { /* Hack -- no flush needed */ msg_flag = FALSE; /* Reset the skip_more flag */ p_ptr->state.skip_more = FALSE; /* Activate "command mode" */ p_ptr->cmd.inkey_flag = TRUE; /* Get a command */ cmd = inkey(); } /* Clear top line */ clear_msg(); /* Command Count */ if (cmd == '0') { int old_arg = p_ptr->cmd.arg; /* Reset */ p_ptr->cmd.arg = 0; /* Begin the input */ prtf(0, 0, "Count: "); /* Get a command count */ while (1) { /* Get a new keypress */ cmd = inkey(); /* Simple editing (delete or backspace) */ if ((cmd == 0x7F) || (cmd == KTRL('H'))) { /* Delete a digit */ p_ptr->cmd.arg = p_ptr->cmd.arg / 10; /* Show current count */ prtf(0, 0, "Count: %d", p_ptr->cmd.arg); } /* Actual numeric data */ else if (cmd >= '0' && cmd <= '9') { /* Stop count at 9999 */ if (p_ptr->cmd.arg >= 1000) { /* Warn */ bell("Invalid repeat count!"); /* Limit */ p_ptr->cmd.arg = 9999; } /* Increase count */ else { /* Incorporate that digit */ p_ptr->cmd.arg = p_ptr->cmd.arg * 10 + D2I(cmd); } /* Show current count */ prtf(0, 0, "Count: %d", p_ptr->cmd.arg); } /* Exit on "unusable" input */ else { break; } } /* Hack -- Handle "zero" */ if (p_ptr->cmd.arg == 0) { /* Default to 99 */ p_ptr->cmd.arg = 99; /* Show current count */ prtf(0, 0, "Count: %d", p_ptr->cmd.arg); } /* Hack -- Handle "old_arg" */ if (old_arg != 0) { /* Restore old_arg */ p_ptr->cmd.arg = old_arg; /* Show current count */ prtf(0, 0, "Count: %d", p_ptr->cmd.arg); } /* Hack -- white-space means "enter command now" */ if ((cmd == ' ') || (cmd == '\n') || (cmd == '\r')) { /* Get a real command */ if (!get_com("Command: ", &cmd)) { /* Clear count */ p_ptr->cmd.arg = 0; /* Continue */ continue; } } } /* Allow "keymaps" to be bypassed */ if (cmd == '\\') { /* Get a real command */ (void)get_com("Command: ", &cmd); /* Hack -- bypass keymaps */ if (!inkey_next) inkey_next = ""; } /* Allow "control chars" to be entered */ if (cmd == '^') { /* Get a new command and controlify it */ if (get_com("Control: ", &cmd)) cmd = KTRL(cmd); } /* Look up applicable keymap */ act = keymap_act[mode][(byte)(cmd)]; /* Apply keymap if not inside a keymap already */ if (act && !inkey_next) { /* Install the keymap (limited buffer size) */ (void)strnfmt(request_command_buffer, 256, "%s", act); /* Start using the buffer */ inkey_next = request_command_buffer; /* Continue */ continue; } /* Paranoia */ if (!cmd) continue; /* Use command */ p_ptr->cmd.cmd = cmd; /* Done */ break; } /* Hack -- Auto-repeat certain commands */ if (p_ptr->cmd.arg <= 0) { /* Hack -- auto repeat certain commands */ if (strchr("TBDoc+", p_ptr->cmd.cmd)) { /* Repeat 99 times */ p_ptr->cmd.arg = 99; } } /* Shopping */ if (shopping == 1) { /* Convert */ switch (p_ptr->cmd.cmd) { /* Command "p" -> "purchase" (get) */ case 'p': p_ptr->cmd.cmd = 'g'; break; /* Command "m" -> "purchase" (get) */ case 'm': p_ptr->cmd.cmd = 'g'; break; /* Command "s" -> "sell" (drop) */ case 's': p_ptr->cmd.cmd = 'd'; break; } } /* Hack -- Scan equipment */ for (i = 0; i < EQUIP_MAX; i++) { cptr s; object_type *o_ptr = &p_ptr->equipment[i]; /* Skip non-objects */ if (!o_ptr->k_idx) continue; /* No inscription */ if (!o_ptr->inscription) continue; /* Obtain the inscription */ s = quark_str(o_ptr->inscription); /* Find a '^' */ s = strchr(s, '^'); /* Process preventions */ while (s) { /* Check the "restriction" character */ if ((s[1] == p_ptr->cmd.cmd) || (s[1] == '*')) { /* Hack -- Verify command */ if (!get_check("Are you sure? ")) { /* Hack -- Use space */ p_ptr->cmd.cmd = ' '; } } /* Find another '^' */ s = strchr(s + 1, '^'); } } /* Hack -- erase the message line. */ clear_msg(); } #define REPEAT_MAX 20 /* Number of chars saved */ static int repeat__cnt = 0; /* Current index */ static int repeat__idx = 0; /* Saved "stuff" */ static int repeat__key[REPEAT_MAX]; void repeat_push(int what) { /* Too many keys */ if (repeat__cnt == REPEAT_MAX) return; /* Push the "stuff" */ repeat__key[repeat__cnt++] = what; /* Prevents us from pulling keys */ ++repeat__idx; } void repeat_clear(void) { /* Start over from the failed pull */ if (repeat__idx) { /* Decrease the number of characters */ --repeat__idx; } /* Set the counter */ repeat__cnt = repeat__idx; } bool repeat_pull(int *what) { /* All out of keys */ if (repeat__idx == repeat__cnt) return (FALSE); /* Grab the next key, advance */ *what = repeat__key[repeat__idx++]; /* Success */ return (TRUE); } void repeat_check(void) { int what; /* Ignore some commands */ if (p_ptr->cmd.cmd == ESCAPE) return; if (p_ptr->cmd.cmd == ' ') return; if (p_ptr->cmd.cmd == '\r') return; if (p_ptr->cmd.cmd == '\n') return; /* Repeat Last Command */ if (p_ptr->cmd.cmd == 'n') { /* Reset */ repeat__idx = 0; /* Get the command */ if (repeat_pull(&what)) { /* Save the command */ p_ptr->cmd.cmd = what; } } /* Start saving new command */ else { /* Reset */ repeat__cnt = 0; repeat__idx = 0; what = p_ptr->cmd.cmd; /* Save this command */ repeat_push(what); } }