/* File: util.c */ /* * Copyright (c) 1997 Ben Harrison, James E. Wilson, Robert A. Koeneke * * This software may be copied and distributed for educational, research, * and not for profit purposes provided that this copyright and statement * are included in all such copies. Other copyrights may also apply. */ #include "angband.h" #ifndef HAS_MEMSET /* * For those systems that don't have "memset()" * * Set the value of each of 'n' bytes starting at 's' to 'c', return 's' * If 'n' is negative, you will erase a whole lot of memory. */ char *memset(char *s, int c, huge n) { char *t; for (t = s; len--; ) *t++ = c; return (s); } #endif #ifndef HAS_STRICMP /* * For those systems that don't have "stricmp()" * * Compare the two strings "a" and "b" ala "strcmp()" ignoring case. */ int stricmp(cptr a, cptr b) { cptr s1, s2; char z1, z2; /* Scan the strings */ for (s1 = a, s2 = b; TRUE; s1++, s2++) { z1 = FORCEUPPER(*s1); z2 = FORCEUPPER(*s2); if (z1 < z2) return (-1); if (z1 > z2) return (1); if (!z1) return (0); } } #endif #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 /* * Hack -- External functions */ extern struct passwd *getpwuid(); extern struct passwd *getpwnam(); /* * 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))) { strcpy(buf, pw->pw_name); buf[16] = '\0'; #ifdef CAPITALIZE_USER_NAME /* Hack -- capitalize the user name */ if (islower(buf[0])) buf[0] = toupper(buf[0]); #endif return; } /* Oops. Hack -- default to "PLAYER" */ strcpy(buf, "PLAYER"); } #endif /* SET_UID */ /* * 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] != '~') { strcpy(buf, file); 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 */ strcpy(buf, pw->pw_dir); /* Append the rest of the filename, if any */ if (s) strcat(buf, s); /* 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 */ strnfmt(buf, max, "%s", file); /* Success */ return (0); } #endif /* SET_UID */ /* * Hack -- acquire a "temporary" file name if possible * * This filename is always in "system-specific" form. */ errr path_temp(char *buf, int max) { cptr s; /* Temp file */ s = tmpnam(NULL); /* Oops */ if (!s) return (-1); /* Format to length */ strnfmt(buf, max, "%s", s); /* Success */ return (0); } /* * 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. */ errr path_build(char *buf, int max, cptr path, cptr file) { /* Special file */ if (file[0] == '~') { /* Use the file itself */ strnfmt(buf, max, "%s", file); } /* Absolute file, on "normal" systems */ else if (prefix(file, PATH_SEP) && !streq(PATH_SEP, "")) { /* Use the file itself */ strnfmt(buf, max, "%s", file); } /* No path given */ else if (!path[0]) { /* Use the file itself */ strnfmt(buf, max, "%s", file); } /* Path and File */ else { /* Build the new path */ strnfmt(buf, max, "%s%s%s", path, PATH_SEP, file); } /* Success */ return (0); } /* * Hack -- replacement for "fopen()" */ FILE *my_fopen(cptr file, cptr mode) { char buf[1024]; /* Hack -- Try to parse the path */ if (path_parse(buf, 1024, file)) return (NULL); /* Attempt to fopen the file anyway */ return (fopen(buf, mode)); } /* * Hack -- replacement for "fclose()" */ errr my_fclose(FILE *fff) { /* Require a file */ if (!fff) return (-1); /* Close, check for error */ if (fclose(fff) == EOF) return (1); /* Success */ return (0); } #endif /* ACORN */ /* * 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) { 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++] = ' '; } /* Handle printables */ else if (isprint(*s)) { /* Copy */ buf[i++] = *s; /* Check length */ if (i >= n) break; } } } /* Nothing */ buf[0] = '\0'; /* Failure */ return (1); } /* * Hack -- replacement for "fputs()" * * Dump a string, plus a newline, to a file * * Perhaps this function should handle internal weirdness. */ errr my_fputs(FILE *fff, cptr buf, huge n) { /* Dump, ignore errors */ (void)fprintf(fff, "%s\n", buf); /* Success */ return (0); } #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 */ /* * 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 copy a file */ errr fd_copy(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); /* Copy XXX XXX XXX */ /* (void)rename(buf, aux); */ /* Assume success XXX XXX XXX */ return (1); } /* * 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]; /* Hack -- Try to parse the path */ if (path_parse(buf, 1024, file)) return (-1); #if defined(MACINTOSH) || defined(WINDOWS) /* Create the file, fail if exists, write-only, binary */ return (open(buf, O_CREAT | O_EXCL | O_WRONLY | O_BINARY)); #else /* Create the file, fail if exists, write-only, binary */ return (open(buf, O_CREAT | O_EXCL | O_WRONLY | O_BINARY, mode)); #endif } /* * 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); #if defined(MACINTOSH) || defined(WINDOWS) /* Attempt to open the file */ return (open(buf, flags | O_BINARY)); #else /* Attempt to open the file */ return (open(buf, flags | O_BINARY, 0)); #endif } /* * Hack -- attempt to lock a file descriptor * * Legal lock types -- F_UNLCK, F_RDLCK, F_WRLCK */ errr fd_lock(int fd, int what) { /* 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) { long p; /* Verify fd */ if (fd < 0) return (-1); /* Seek to the given position */ p = lseek(fd, n, SEEK_SET); /* Failure */ if (p < 0) return (1); /* Failure */ if (p != n) return (1); /* Success */ return (0); } /* * Hack -- attempt to truncate a file descriptor */ errr fd_chop(int fd, huge n) { /* Verify the fd */ if (fd < 0) return (-1); #if defined(SUNOS) || defined(ULTRIX) || defined(NeXT) /* Truncate */ ftruncate(fd, n); #endif /* 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) != 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) != 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); /* Assume success XXX XXX XXX */ return (0); } #endif /* ACORN */ /* * 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 (isalpha(c)) return (A2I(tolower(c)) + 10); return (0); } /* * Hack -- convert a printable string into real ascii * * This function will not work on non-ascii systems. * * To be safe, "buf" should be at least as large as "str". */ 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++; /* Hack -- simple way to specify Escape */ if (*str == 'e') { *s++ = ESCAPE; } /* Hack -- simple way to specify "space" */ else if (*str == 's') { *s++ = ' '; } /* 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'; } /* Actual "backslash" */ else if (*str == '\\') { *s++ = '\\'; } /* Hack -- Actual "caret" */ else if (*str == '^') { *s++ = '^'; } /* Hack -- Hex-mode */ else if (*str == 'x') { *s = 16 * dehex(*++str); *s++ += dehex(*++str); } /* Oops */ else { *s = *str; } /* 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 * * This function will not work on non-ascii systems. * * To be safe, "buf" should be at least four times as large as "str". */ 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 */ errr macro_add(cptr pat, cptr act) { int n; /* Paranoia -- require data */ if (!pat || !act) return (-1); /* 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; /* Success */ return (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); } /* * Flush all pending input. * * Actually, remember the flush, using the "inkey_xtra" flag, and in the * next call to "inkey()", perform the actual flushing, for efficiency, * and correctness of the "inkey()" function. */ void flush(void) { /* Do it later */ inkey_xtra = TRUE; } /* * 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; /* * 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 && !inkey_xtra) { /* Get next character, and advance */ ch = *inkey_next++; /* Cancel the various "global parameters" */ inkey_base = inkey_xtra = inkey_flag = 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)(inkey_xtra)) != 0)) { /* Cancel the various "global parameters" */ inkey_base = inkey_xtra = inkey_flag = inkey_scan = FALSE; /* Accept result */ return (ch); } #endif /* ALLOW_BORG */ /* Hack -- handle delayed "flush()" */ if (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 (!inkey_scan && (!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 (!inkey_base && 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 (inkey_base) { int w = 0; /* Wait forever */ if (!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 */ Term_set_cursor(v); /* Cancel the various "global parameters" */ inkey_base = inkey_xtra = inkey_flag = inkey_scan = FALSE; /* Return the keypress */ return (ch); } /* * Flush the screen, make a noise */ void bell(cptr reason) { /* Mega-Hack -- Flush the output */ Term_fresh(); /* Hack -- memorize the reason if possible */ if (character_generated && reason) message_add(reason); /* Make a bell noise (if allowed) */ if (ring_bell) Term_xtra(TERM_XTRA_NOISE, 0); /* Flush the input (later!) */ flush(); } /* * Hack -- Make a (relevant?) sound */ void sound(int val) { /* No sound */ if (!use_sound) return; /* Make a sound (if allowed) */ Term_xtra(TERM_XTRA_SOUND, val); } /* * 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". */ /* * Add a new "quark" to the set of quarks. */ s16b quark_add(cptr str) { int i; /* Look for an existing quark */ for (i = 1; i < quark__num; i++) { /* Check for equality */ if (streq(quark__str[i], str)) return (i); } /* Hack -- Require room XXX XXX XXX */ if (quark__num == QUARK_MAX) return (0); /* New quark */ i = quark__num++; /* Add a new quark */ quark__str[i] = string_make(str); /* Return the index */ return (i); } /* * This function looks up a quark */ cptr quark_str(s16b i) { cptr q; /* Verify */ if ((i < 0) || (i >= quark__num)) i = 0; /* Access the quark */ q = quark__str[i]; /* Return the quark */ return (q); } /* * Initialize the "quark" package */ errr quark_init(void) { /* Quark variables */ C_MAKE(quark__str, QUARK_MAX, cptr); /* 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. */ /* * 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 (""); /* Acquire the "logical" index */ x = (message__next + MESSAGE_MAX - (age + 1)) % MESSAGE_MAX; /* Get the "offset" for the message */ o = message__ptr[x]; /* Access the message text */ s = &message__buf[o]; /* Return the message text */ return (s); } /* * 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) { int n, k, i, x, o; cptr s; cptr t; 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 */ k = message_num() / 4; /* Limit number of messages to check */ if (k > 32) k = 32; /* 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; /* Access the old string */ old = &message__buf[o]; /* Inline 'streq(str, old)' */ for (s = str, t = old; (*s == *t) && *s; ++s, ++t) /* loop */ ; /* Continue if not equal */ if (*s) 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]; /* 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); } /* * Initialize the "message" package */ errr message_init(void) { /* Message variables */ C_MAKE(message__ptr, MESSAGE_MAX, u16b); C_MAKE(message__buf, MESSAGE_BUF, char); /* Hack -- No messages yet */ message__tail = MESSAGE_BUF; /* Success */ return (0); } /* * XXX XXX XXX Important note about "colors" XXX XXX XXX * * The "TERM_*" color definitions list the "composition" of each * "Angband color" in terms of "quarters" of each of the three color * components (Red, Green, Blue), for example, TERM_UMBER is defined * as 2/4 Red, 1/4 Green, 0/4 Blue. * * The following info is from "Torbjorn Lindgren" (see "main-xaw.c"). * * These values are NOT gamma-corrected. On most machines (with the * Macintosh being an important exception), you must "gamma-correct" * the given values, that is, "correct for the intrinsic non-linearity * of the phosphor", by converting the given intensity levels based * on the "gamma" of the target screen, which is usually 1.7 (or 1.5). * * The actual formula for conversion is unknown to me at this time, * but you can use the table below for the most common gamma values. * * So, on most machines, simply convert the values based on the "gamma" * of the target screen, which is usually in the range 1.5 to 1.7, and * usually is closest to 1.7. The converted value for each of the five * different "quarter" values is given below: * * Given Gamma 1.0 Gamma 1.5 Gamma 1.7 Hex 1.7 * ----- ---- ---- ---- --- * 0/4 0.00 0.00 0.00 #00 * 1/4 0.25 0.27 0.28 #47 * 2/4 0.50 0.55 0.56 #8f * 3/4 0.75 0.82 0.84 #d7 * 4/4 1.00 1.00 1.00 #ff * * Note that some machines (i.e. most IBM machines) are limited to a * hard-coded set of colors, and so the information above is useless. * * Also, some machines are limited to a pre-determined set of colors, * for example, the IBM can only display 16 colors, and only 14 of * those colors resemble colors used by Angband, and then only when * you ignore the fact that "Slate" and "cyan" are not really matches, * so on the IBM, we use "orange" for both "Umber", and "Light Umber" * in addition to the obvious "Orange", since by combining all of the * "indeterminate" colors into a single color, the rest of the colors * are left with "meaningful" values. */ /* * Move the cursor */ void move_cursor(int row, int col) { Term_gotoxy(col, row); } /* * Hack -- flush */ static void msg_flush(int x) { byte a = TERM_L_BLUE; /* Pause for response */ Term_putstr(x, 0, -1, a, "-more-"); /* Get an acceptable keypress */ while (1) { int cmd = inkey(); if (quick_messages) break; if ((cmd == ESCAPE) || (cmd == ' ')) break; if ((cmd == '\n') || (cmd == '\r')) break; bell("Illegal response to a 'more' prompt!"); } /* Clear the line */ Term_erase(0, 0, 255); } /* * 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 "msg_print()" functions without * explicitly calling the special "msg_print(NULL)" 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 "msg_print(NULL)" will clear the top line even if no * messages are pending. */ void msg_print(cptr msg) { static int p = 0; int n; char *t; char buf[1024]; /* Hack -- Reset */ if (!msg_flag) p = 0; /* Message Length */ n = (msg ? strlen(msg) : 0); /* Hack -- flush when requested or needed */ if (p && (!msg || ((p + n) > 72))) { /* Flush */ msg_flush(p); /* Forget it */ msg_flag = FALSE; /* Reset */ p = 0; } /* No message */ if (!msg) return; /* Paranoia */ if (n > 1000) return; /* Memorize the message */ if (character_generated) message_add(msg); /* Copy it */ strcpy(buf, msg); /* Analyze the buffer */ t = buf; /* 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 */ Term_putstr(0, 0, split, TERM_WHITE, t); /* Flush it */ msg_flush(split + 1); /* Memorize the piece */ /* if (character_generated) message_add(t); */ /* 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 */ Term_putstr(p, 0, n, TERM_WHITE, t); /* Memorize the tail */ /* if (character_generated) message_add(t); */ /* Window stuff */ p_ptr->window |= (PW_MESSAGE); /* Remember the message */ msg_flag = TRUE; /* Remember the position */ p += n + 1; /* Optional refresh */ if (fresh_after) Term_fresh(); } /* * Display a formatted message, using "vstrnfmt()" and "msg_print()". */ void msg_format(cptr fmt, ...) { va_list vp; char buf[1024]; /* 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); /* Display */ msg_print(buf); } /* * Hack -- prevent "accidents" in "screen_save()" or "screen_load()" */ static int screen_depth = 0; /* * Save the screen, and increase the "icky" depth. * * This function must match exactly one call to "screen_load()". */ void screen_save(void) { /* Hack -- Flush messages */ msg_print(NULL); /* Save the screen (if legal) */ if (screen_depth++ == 0) Term_save(); /* Increase "icky" depth */ character_icky++; } /* * Load the screen, and decrease the "icky" depth. * * This function must match exactly one call to "screen_save()". */ void screen_load(void) { /* Hack -- Flush messages */ msg_print(NULL); /* Load the screen (if legal) */ if (--screen_depth == 0) Term_load(); /* Decrease "icky" depth */ character_icky--; } /* * Display a string on the screen using an attribute. * * At the given location, using the given attribute, if allowed, * add the given string. Do not clear the line. */ void c_put_str(byte attr, cptr str, int row, int col) { /* Position cursor, Dump the attr/text */ Term_putstr(col, row, -1, attr, str); } /* * As above, but in "white" */ void put_str(cptr str, int row, int col) { /* Spawn */ Term_putstr(col, row, -1, TERM_WHITE, str); } /* * Display a string on the screen using an attribute, and clear * to the end of the line. */ void c_prt(byte attr, cptr str, int row, int col) { /* Clear line, position cursor */ Term_erase(col, row, 255); /* Dump the attr/text */ Term_addstr(-1, attr, str); } /* * As above, but in "white" */ void prt(cptr str, int row, int col) { /* Spawn */ c_prt(TERM_WHITE, str, row, col); } /* * Print some (colored) text to the screen at the current cursor position, * automatically "wrapping" existing text (at spaces) when necessary to * avoid placing any text into the last column, and clearing every line * before placing any text in that line. Also, allow "newline" to force * a "wrap" to the next line. Advance the cursor as needed so sequential * calls to this function will work correctly. * * Once this function has been called, the cursor should not be moved * until all the related "c_roff()" calls to the window are complete. * * This function will correctly handle any width up to the maximum legal * value of 256, though it works best for a standard 80 character width. */ void c_roff(byte a, cptr str) { int x, y; int w, h; cptr s; /* Obtain the size */ (void)Term_get_size(&w, &h); /* Obtain the cursor */ (void)Term_locate(&x, &y); /* Process the string */ for (s = str; *s; s++) { char ch; /* Force wrap */ if (*s == '\n') { /* Wrap */ x = 0; y++; /* Clear line, move cursor */ Term_erase(x, y, 255); } /* Clean up the char */ ch = (isprint(*s) ? *s : ' '); /* Wrap words as needed */ if ((x >= w - 1) && (ch != ' ')) { int i, n = 0; byte av[256]; char cv[256]; /* Wrap word */ if (x < w) { /* Scan existing text */ for (i = w - 2; i >= 0; i--) { /* Grab existing attr/char */ Term_what(i, y, &av[i], &cv[i]); /* Break on space */ if (cv[i] == ' ') break; /* Track current word */ n = i; } } /* Special case */ if (n == 0) n = w; /* Clear line */ Term_erase(n, y, 255); /* Wrap */ x = 0; y++; /* Clear line, move cursor */ Term_erase(x, y, 255); /* Wrap the word (if any) */ for (i = n; i < w - 1; i++) { /* Dump */ Term_addch(av[i], cv[i]); /* Advance (no wrap) */ if (++x > w) x = w; } } /* Dump */ Term_addch(a, ch); /* Advance */ if (++x > w) x = w; } } /* * As above, but in "white" */ void roff(cptr str) { /* Spawn */ c_roff(TERM_WHITE, str); } /* * Clear part of the screen */ void clear_from(int row) { int y; /* Erase requested rows */ for (y = row; y < Term->hgt; y++) { /* Erase part of the screen */ Term_erase(0, y, 255); } } /* * Get some input at the cursor location. * Assume the buffer is initialized to a default string. * Note that this string is often "empty" (see below). * The default buffer is displayed in yellow until cleared. * Pressing RETURN right away accepts the default entry. * Normal chars clear the default and append the char. * Backspace clears the default or deletes the final char. * ESCAPE clears the buffer and the window and returns FALSE. * RETURN accepts the current buffer contents and returns TRUE. * The buffer must be large enough for 'len+1' characters. */ bool askfor_aux(char *buf, int len) { int y, x; int i = 0; int k = 0; bool done = FALSE; /* Locate the cursor */ Term_locate(&x, &y); /* Paranoia -- check len */ if (len < 1) len = 1; /* Paranoia -- check column */ if ((x < 0) || (x >= 80)) x = 0; /* Restrict the length */ if (x + len > 80) len = 80 - x; /* Paranoia -- Clip the default entry */ buf[len] = '\0'; /* Display the default answer */ Term_erase(x, y, len); Term_putstr(x, y, -1, TERM_YELLOW, buf); /* Process input */ while (!done) { /* Place cursor */ Term_gotoxy(x + k, y); /* Get a key */ i = inkey(); /* Analyze the key */ switch (i) { case ESCAPE: { k = 0; done = TRUE; break; } case '\n': case '\r': { k = strlen(buf); done = TRUE; break; } case 0x7F: case '\010': { if (k > 0) k--; break; } default: { if ((k < len) && (isprint(i))) { buf[k++] = i; } else { bell("Illegal edit key!"); } break; } } /* Terminate */ buf[k] = '\0'; /* Update the entry */ Term_erase(x, y, len); Term_putstr(x, y, -1, TERM_WHITE, buf); } /* Aborted */ if (i == ESCAPE) return (FALSE); /* Success */ return (TRUE); } /* * Get a string from the user * * The "prompt" should take the form "Prompt: " * * Note that the initial contents of the string is used as * the default response, so be sure to "clear" it if needed. * * We clear the input, and return FALSE, on "ESCAPE". */ bool get_string(cptr prompt, char *buf, int len) { bool res; /* Paranoia XXX XXX XXX */ msg_print(NULL); /* Display prompt */ prt(prompt, 0, 0); /* Ask the user for a string */ res = askfor_aux(buf, len); /* Clear prompt */ prt("", 0, 0); /* Result */ return (res); } /* * Request a "quantity" from the user * * Allow "p_ptr->command_arg" to specify a quantity */ s16b get_quantity(cptr prompt, int max) { int amt = 1; /* Use "command_arg" */ if (p_ptr->command_arg) { /* Extract a number */ amt = p_ptr->command_arg; /* Clear "command_arg" */ p_ptr->command_arg = 0; } #ifdef ALLOW_REPEAT /* Get the item index */ else if ((max != 1) && allow_quantity && repeat_pull(&amt)) { /* nothing */ } #endif /* ALLOW_REPEAT */ /* Prompt if needed */ else if ((max != 1) && allow_quantity) { char tmp[80]; char buf[80]; /* Build a prompt if needed */ if (!prompt) { /* Build a prompt */ sprintf(tmp, "Quantity (0-%d): ", max); /* Use that prompt */ prompt = tmp; } /* Build the default */ sprintf(buf, "%d", amt); /* Ask for a quantity */ if (!get_string(prompt, buf, 6)) return (0); /* Extract a number */ amt = atoi(buf); /* A letter means "all" */ if (isalpha(buf[0])) amt = max; } /* Enforce the maximum */ if (amt > max) amt = max; /* Enforce the minimum */ if (amt < 0) amt = 0; #ifdef ALLOW_REPEAT if (amt) repeat_push(amt); #endif /* ALLOW_REPEAT */ /* Return the result */ return (amt); } /* * Verify something with the user * * The "prompt" should take the form "Query? " * * Note that "[y/n]" is appended to the prompt. */ bool get_check(cptr prompt) { int i; char buf[80]; /* Paranoia XXX XXX XXX */ msg_print(NULL); /* Hack -- Build a "useful" prompt */ strnfmt(buf, 78, "%.70s[y/n] ", prompt); /* Prompt for it */ prt(buf, 0, 0); /* Get an acceptable answer */ while (TRUE) { i = inkey(); if (quick_messages) break; if (i == ESCAPE) break; if (strchr("YyNn", i)) break; bell("Illegal response to a 'yes/no' question!"); } /* Erase the prompt */ prt("", 0, 0); /* Normal negation */ if ((i != 'Y') && (i != 'y')) return (FALSE); /* Success */ return (TRUE); } /* * Prompts for a keypress * * The "prompt" should take the form "Command: " * * Returns TRUE unless the character is "Escape" */ bool get_com(cptr prompt, char *command) { /* Paranoia XXX XXX XXX */ msg_print(NULL); /* Display a prompt */ prt(prompt, 0, 0); /* Get a key */ *command = inkey(); /* Clear the prompt */ prt("", 0, 0); /* Handle "cancel" */ if (*command == ESCAPE) return (FALSE); /* Success */ return (TRUE); } /* * Pause for user response * * This function is stupid. XXX XXX XXX */ void pause_line(int row) { int i; prt("", row, 0); put_str("[Press any key to continue]", row, 23); i = inkey(); prt("", row, 0); } /* * 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->command_cmd, p_ptr->command_dir, p_ptr->command_rep, * p_ptr->command_arg. May modify p_ptr->command_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->command_new" may not work any more. XXX XXX XXX */ void request_command(bool 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->command_cmd = 0; /* No "argument" yet */ p_ptr->command_arg = 0; /* No "direction" yet */ p_ptr->command_dir = 0; /* Get command */ while (1) { /* Hack -- auto-commands */ if (p_ptr->command_new) { /* Flush messages */ msg_print(NULL); /* Use auto-command */ cmd = p_ptr->command_new; /* Forget it */ p_ptr->command_new = 0; } /* Get a keypress in "command" mode */ else { /* Hack -- no flush needed */ msg_flag = FALSE; /* Activate "command mode" */ inkey_flag = TRUE; /* Get a command */ cmd = inkey(); } /* Clear top line */ prt("", 0, 0); /* Command Count */ if (cmd == '0') { int old_arg = p_ptr->command_arg; /* Reset */ p_ptr->command_arg = 0; /* Begin the input */ prt("Count: ", 0, 0); /* 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->command_arg = p_ptr->command_arg / 10; /* Show current count */ prt(format("Count: %d", p_ptr->command_arg), 0, 0); } /* Actual numeric data */ else if (cmd >= '0' && cmd <= '9') { /* Stop count at 9999 */ if (p_ptr->command_arg >= 1000) { /* Warn */ bell("Invalid repeat count!"); /* Limit */ p_ptr->command_arg = 9999; } /* Increase count */ else { /* Incorporate that digit */ p_ptr->command_arg = p_ptr->command_arg * 10 + D2I(cmd); } /* Show current count */ prt(format("Count: %d", p_ptr->command_arg), 0, 0); } /* Exit on "unusable" input */ else { break; } } /* Hack -- Handle "zero" */ if (p_ptr->command_arg == 0) { /* Default to 99 */ p_ptr->command_arg = 99; /* Show current count */ prt(format("Count: %d", p_ptr->command_arg), 0, 0); } /* Hack -- Handle "old_arg" */ if (old_arg != 0) { /* Restore old_arg */ p_ptr->command_arg = old_arg; /* Show current count */ prt(format("Count: %d", p_ptr->command_arg), 0, 0); } /* 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->command_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) */ 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->command_cmd = cmd; /* Done */ break; } /* Hack -- Auto-repeat certain commands */ if (always_repeat && (p_ptr->command_arg <= 0)) { /* Hack -- auto repeat certain commands */ if (strchr("TBDoc+", p_ptr->command_cmd)) { /* Repeat 99 times */ p_ptr->command_arg = 99; } } /* Shopping */ if (shopping) { /* Hack -- Convert a few special keys */ switch (p_ptr->command_cmd) { /* Command "p" -> "purchase" (get) */ case 'p': p_ptr->command_cmd = 'g'; break; /* Command "m" -> "purchase" (get) */ case 'm': p_ptr->command_cmd = 'g'; break; /* Command "s" -> "sell" (drop) */ case 's': p_ptr->command_cmd = 'd'; break; } } /* Hack -- Scan equipment */ for (i = INVEN_WIELD; i < INVEN_TOTAL; i++) { cptr s; object_type *o_ptr = &inventory[i]; /* Skip non-objects */ if (!o_ptr->k_idx) continue; /* No inscription */ if (!o_ptr->note) continue; /* Obtain the inscription */ s = quark_str(o_ptr->note); /* Find a '^' */ s = strchr(s, '^'); /* Process preventions */ while (s) { /* Check the "restriction" character */ if ((s[1] == p_ptr->command_cmd) || (s[1] == '*')) { /* Hack -- Verify command */ if (!get_check("Are you sure? ")) { /* Hack -- Use space */ p_ptr->command_cmd = ' '; } } /* Find another '^' */ s = strchr(s + 1, '^'); } } /* Hack -- erase the message line. */ prt("", 0, 0); } /* * Generates damage for "2d6" style dice rolls */ uint damroll(uint num, uint sides) { int i, sum = 0; for (i = 0; i < num; i++) { sum += (rand_int(sides) + 1); } return (sum); } /* * Same as above, but always maximal */ uint maxroll(uint num, uint sides) { return (num * sides); } /* * 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); } #if 0 /* * Replace the first instance of "target" in "buf" with "insert" * If "insert" is NULL, just remove the first instance of "target" * In either case, return TRUE if "target" is found. * * XXX Could be made more efficient, especially in the * case where "insert" is smaller than "target". */ static bool insert_str(char *buf, cptr target, cptr insert) { int i, len; int b_len, t_len, i_len; /* Attempt to find the target (modify "buf") */ buf = strstr(buf, target); /* No target found */ if (!buf) return (FALSE); /* Be sure we have an insertion string */ if (!insert) insert = ""; /* Extract some lengths */ t_len = strlen(target); i_len = strlen(insert); b_len = strlen(buf); /* How much "movement" do we need? */ len = i_len - t_len; /* We need less space (for insert) */ if (len < 0) { for (i = t_len; i < b_len; ++i) buf[i+len] = buf[i]; } /* We need more space (for insert) */ else if (len > 0) { for (i = b_len-1; i >= t_len; --i) buf[i+len] = buf[i]; } /* If movement occured, we need a new terminator */ if (len) buf[b_len+len] = '\0'; /* Now copy the insertion string */ for (i = 0; i < i_len; ++i) buf[i] = insert[i]; /* Successful operation */ return (TRUE); } #endif #ifdef ALLOW_REPEAT #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; } 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->command_cmd == ESCAPE) return; if (p_ptr->command_cmd == ' ') return; if (p_ptr->command_cmd == '\r') return; if (p_ptr->command_cmd == '\n') return; /* Repeat Last Command */ if (p_ptr->command_cmd == 'n') { /* Reset */ repeat__idx = 0; /* Get the command */ if (repeat_pull(&what)) { /* Save the command */ p_ptr->command_cmd = what; } } /* Start saving new command */ else { /* Reset */ repeat__cnt = 0; repeat__idx = 0; what = p_ptr->command_cmd; /* Save this command */ repeat_push(what); } } #endif /* ALLOW_REPEAT */