/* * The implementation of the player-avatar monster. */ #include #include #include #include #include #include #include #include "see.h" #include "obj.h" #include "mon.h" #include "vars.h" #include "help.h" #include "util.h" #include "save.h" #include "dice.h" #include "less.h" #include "time.h" #include "trap.h" #include "main.h" #include "param.h" #include "pline.h" #include "trail.h" #include "gates.h" #include "fight.h" #include "fuses.h" #include "debug.h" #include "screen.h" #include "effect.h" #include "damage.h" #include "options.h" #include "display.h" #include "obj-map.h" #include "signals.h" #include "scrsyms.h" #include "montypes.h" #include "disputil.h" #include "objtypes.h" #include "obj-wand.h" #include "obj-ring.h" #include "obj-scroll.h" #include "obj-potion.h" #include "obj-armour.h" #include "obj-weapon.h" #include "stdio-util.h" #include "filldungeon.h" #include "mon-@-you.h" #define MSGHISTSIZE 256 #define PERCEPTION_RADIUS 6 #define MAP_PER_TICK 25 /* * There are three kinds of motion: not running (lowercase motion * commands), regular running (uppercase motion commands), and * run-until-near-something (control-modified motion commands). */ typedef enum { RUN_NONE = 1, RUN_SHIFT, RUN_CTL, } RUNSTATE; /* * Return values from map_vis_internal's second argument. */ typedef enum { MSR_ALREADY = 1, MSR_TOOKTIME, MSR_NOTIME, } MAPSETRV; typedef struct you YOU; typedef struct post_refresh POST_REFRESH; typedef struct pline_priv PLINE_PRIV; typedef struct areadamage AREADAMAGE; struct areadamage { int (*test)(AREADAMAGE *, MONST *); DAMAGE (*damage)(AREADAMAGE *, MONST *); const char *startmsg; const char *ongoingmsg; const char *stopmsg; int going_on; TIME lasttime; } ; struct pline_priv { char *b; int a; int l; } ; struct post_refresh { POST_REFRESH *link; void (*cb)(void *); void *cbarg; } ; /* * Private data for the player avatar monster. We have a struct for * this, rather than just making them file statics, for cleanness and * against the possibility of someday going multiplayer (to be sure, * there's lots of other stuff we'd have to fix in that case). */ struct you { /* * Player experience. */ int xp; /* * One of the developer-support escapes turns pline() messages off or * back on again. This is true if they're currently off. */ int plineoff; /* * pline()-generated messages may be doubled up - ie, more than one * message displayed on a single line - if they're short. pl_buf is * a buffer holding pending messages. pl_left is the space * remaining in pl_buf; pl_left0 is the total space available (it's * a variable to accommodate different numbers of columns). pl_bp * is a pointer to the current end of pl_buf, where another message * would go; that is, pl_bp = pl_buf + (pl_left0 - pl_left). */ char *pl_buf; int pl_left; int pl_left0; char *pl_bp; /* * Invulnerability can be turned on or off. (This is a developer * support feature and will be very limited or absent in * non-development versions of the game.) */ int invulnerable; /* * True if automatic map switching when changing levels is currently * turned on. */ int auto_map_switch; /* * running says which kind of running (see RUNSTATE, above) is * currently in effect. */ RUNSTATE running; /* * Running can be limited by entering a numeric prefix before the * running motion command. When this is done, runcount holds the * current remaining count. (When not, it holds -1.) */ int runcount; /* * Flag for implementing the m (move without pickup) command. */ int justmove; /* * When a numeric prefix is used, it is accumulated in prefix until * the affected command is entered. prefix_a and prefix_n are the * usual allocated and current lengths. prefix_clear is set when * any prefix in progress should be cleared. */ char *prefix; int prefix_a; int prefix_n; int prefix_clear; /* * When a numeric prefix is applied to a command other than running * motion, it operates as a repetition count. When this is done, * the current remaining count is kept in repcount and the command * being repeated is kept in repcmd. */ int repcount; int repcmd; /* * Support for continuing-action commands, such as the potion of * clairvoyance. action is the implementation to be called next * time the player gets an action; action_arg is its argument. In * normal play, action is nil and action_arg is indeterminate and * meaningless. If action is non-nil, action_flags holds the flags * argument which was passed to set_continuing (see mon-@-you.h's * comment for set_continuing). */ int (*action)(void *); void *action_arg; unsigned int action_flags; /* * When running, run_d is the delta for each step and startat is the * location where the running started. */ XY run_d; LOC *startat; /* * last_interrupt is used (in conjunction with interrupted()) to * detect SIGINT and interrupt long-running actions in progress. */ int last_interrupt; /* * This controls certain debugging messages. Exactly what it * controls is not documented here; search for the variable to see * that. It changes too readily to keep it up to date here. */ int debugmsg; /* * lastlevel and lastloc are the level and location the player was * last on. These are used to notice player motion and trigger the * corresponding display actions. */ LEVEL *lastlevel; LOC *lastloc; /* * pline()d messages are kept in a history ring, so the player can * look back at historical messages (up to a limit, of course). * mh_text holds message text, with mh_head and mh_tail giving the * head and tail of the ring buffer. (The unit of the ring is the * message, not the character.) */ char *mh_text[MSGHISTSIZE]; int mh_head; int mh_tail; /* * Permanemt phasing as a debug helper. If this is nil, it's off; if * not, it's on. */ EFFECT *debug_phasing; /* * Things that should happen after the next screen refresh. */ POST_REFRESH *post_refresh; /* * Set to trigger a complete recomputation of visibility. */ int redo_see; /* * State for map_vis() and friends. */ void *mvstate; } ; /* * Player-avatar state (see above). */ static YOU y; /* * Pickup inventory code. * * The reason this code exists is that we want a cross between two * different inventories. For example, if we're carrying 3 potions of * healing and we pick up one more, we want the display to mention * just one potion of healing, but with the inventory letter for the * resulting "4 potions of healing" inventory item. * * We assume, here, that INVOBJs being picked up correspond one-to-one * to INVOBJs in the resulting inventory. Since inventory collapsing * works the same way for all inventories, this should be true. */ typedef struct piline PILINE; typedef struct pickup_inv PICKUP_INV; struct piline { PILINE *link; char *text; } ; struct pickup_inv { PILINE *list; PILINE **listt; char *srctext; } ; /* * An XCMD is an `extended command', a multi-character command entered * after #. These are collected into arrays which are used in a tree * structure. cmd is of course the command name; fn is its * implementation. */ typedef struct xcmd XCMD; struct xcmd { const char *cmd; int (*fn)(void); } ; /* * Data for MEF_PERCEPTION off-level flagging. */ static int offlev_perception; static unsigned char percept_offlev[(PERCEPTION_RADIUS*2)+1][(PERCEPTION_RADIUS*2)+1]; /* * Turn off pline() messages. Developer assist. */ static void off_pline(void) { int new; new = ! y.plineoff; y.plineoff = 0; pline("Messages %s",new?"off":"on"); y.plineoff = new; } /* * Generate a pline()-style message given buffer and length. The only * thing of note here is probably the way messages beginning with * "You" are special-cased to never be appended to another message. */ static void pline_string(const char *s, int l) { int o; if ((l < y.pl_left-10) && strncmp(s,"You",3)) { if (y.pl_bp != y.pl_buf) { *y.pl_bp++ = ' '; *y.pl_bp++ = ' '; y.pl_left -= 2; } bcopy(s,y.pl_bp,l); y.pl_left -= l; y.pl_bp += l; return; } showmsgbuf(1); o = 0; while (l-o > y.pl_left0-10) { bcopy(s+o,y.pl_buf,y.pl_left0-10); y.pl_bp = y.pl_buf + y.pl_left0-10; showmsgbuf(1); o += y.pl_left0 - 20; } bcopy(s+o,y.pl_buf,l-o); y.pl_bp = y.pl_buf + (l-o); y.pl_left = y.pl_left0 - (l-o); } /* * Generate a message to the player. This is how most messages to the * player get produced. */ void pline(const char *fmt, ...) { va_list ap; static char *msg = 0; int l; FILE *f; if (y.plineoff) return; free(msg); f = fopenmalloc(&msg,&l); va_start(ap,fmt); vfprintf(f,fmt,ap); va_end(ap); fclose(f); pline_string(msg,l); } /* * Write function for fopen_pline() streams. */ static int pline_w(void *ppv, const char *buf, int len) { PLINE_PRIV *pp; const void *nlv; int o; int n; pp = ppv; o = 0; while (o < len) { nlv = memchr(buf+o,'\n',len-o); if (! nlv) { if (pp->l+(len-o) > pp->a) { pp->a = pp->l + len - o; pp->b = realloc(pp->b,pp->a); } bcopy(buf+o,pp->b+pp->l,len-o); pp->l += len - o; break; } else { n = ((const char *)nlv) - (buf + o); if (pp->l) { if (pp->l+n > pp->a) { pp->a = pp->l + n; pp->b = realloc(pp->b,pp->a); } bcopy(buf+o,pp->b+pp->l,n); o += n + 1; n += pp->l; pline_string(pp->b,n); pp->l = 0; } else { pline_string(buf+o,n); o += n + 1; } } } return(len); } /* * Close function for fopen_pline() streams. */ static int pline_c(void *ppv) { PLINE_PRIV *pp; pp = ppv; if (pp->l) pline_w(ppv,"\n",1); free(pp->b); free(pp); return(0); } /* * Open a stream which turns each line of output into a pline()-style * message. */ FILE *fopen_pline(void) { PLINE_PRIV *pp; FILE *f; pp = malloc(sizeof(PLINE_PRIV)); if (! pp) return(0); f = funopen(pp,0,&pline_w,0,&pline_c); if (! f) { free(pp); return(0); } pp->b = 0; pp->a = 0; pp->l = 0; return(f); } /* * Save a line in message history. */ static void mh_save(const char *txt) { free(y.mh_text[y.mh_head]); y.mh_text[y.mh_head] = strdup(txt); y.mh_head = (y.mh_head + 1) % MSGHISTSIZE; if (y.mh_head == y.mh_tail) y.mh_tail = (y.mh_tail + 1) % MSGHISTSIZE; } /* * Show the buffer holding any pline()d messages. If more is true, * prompt the user and wait for an input character, as some * approximation to evidence that the user has read the message line * before erasing it to display another message. */ void showmsgbuf(int more) { JMP j; if (setjmp(j.b)) { pop_sigint_throw(); } else { if (y.pl_bp == y.pl_buf) { move(MYO,0); clrtoeol(); return; } *y.pl_bp = '\0'; mvaddstr(MYO,0,y.pl_buf); mh_save(y.pl_buf); clrtoeol(); if (more) { addstr(" --More--"); push_sigint_throw(&j); tget(); pop_sigint_throw(); } } y.pl_bp = y.pl_buf; y.pl_left = y.pl_left0; } /* * This is called when serious can't-happen tests trip, ones bad enough * that its not worth trying to continue. It reports the message, * displays it, waits for an input character, cleans up the display * and tty state, and then drops core (or at least tries to). */ void panic(const char *fmt, ...) { va_list ap; FILE *f; int wf(void *cookie __attribute__((__unused__)), const char *buf, int len) { addnstr(buf,len); return(len); } block_int(); showmsgbuf(1); mvaddstr(LINES-2,0,"PANIC: "); /* grrr, no vprintw */ f = funopen(0,0,wf,0,0); va_start(ap,fmt); vfprintf(f,fmt,ap); va_end(ap); fclose(f); clrtobot(); move(LINES-1,COLS-2); tget(); cleanupscreen(); debugsig(0); abort(); } /* * This is called when minor can't-happen tests trip. When one of * these trips, the inconsistency is one that can be ignored with at * least moderate safety. */ void impossible(void) { pline("Program in disorder, perhaps you'd better Quit"); } /* * Clear a flag bit on every cell in a level. This updates the * display, too, in case the flag bit is one affecting display. * * This is used, for example, to clear mark bits when flood-filling * areas of the level. */ static void clearflag(LEVEL *lv, int flg) { int x; int y; LOC *lc; for (x=0;xcells[x][0]; for (y=0;yflags & flg) { lc->flags &= ~flg; upddisp1(lc); } lc ++; } } } /* * Initialize the tracking systems. There are three tracks: dumb (just * the path the player took), smart (tries to cut off hairpin turns * and the like), and exit (used when monsters want to head towards * the dungeon exit). * * This just does a fairly straightforward flood-fill of the entire * dungeon for the exit track. Dumb and smart tracks are set as the * player moves around. * * XXX Should redo the exit track at least partially in response to * (possible) topology changes, such as wand of digging! */ void init_track(void) { int x; int y; int i; int j; LOC *lc; LOC *lc2; LEVEL *lv; TRAIL *pending; TRAIL *active; TRAIL *t; for (i=0;icells[x][0]; for (y=0;ytracks[j] = 0; lc->tracktime[j] = 0; } lc->exitdist = ~0U; lc ++; } } } pending = newtrail(); pending->link = 0; pending->loc = entrance; entrance->exitdist = 0; active = 0; while (1) { if (active == 0) { if (pending == 0) break; active = pending; pending = 0; continue; } t = active; active = t->link; lc = t->loc; freetrail(t); j = lc->exitdist + 1; for (i=0;i<8;i++) { lc2 = movecell(lc,delta[i][0],delta[i][1],0); if (! lc2) continue; switch (lc2->type) { default: break; case LOC_CAVE: case LOC_DOOR: case LOC_SDOOR: case LOC_GATE: if (lc2->exitdist > j) { lc2->exitdist = j; t = newtrail(); t->link = pending; pending = t; t->loc = lc2; } break; } } if ( ( ( (lc->type == LOC_GATE) && ((lc2=gate_other_end(lc,GH_REGULAR))) ) || ( (lc->type == LOC_CAVE) && ( (lc->cavetype == LOC_STAIRS_U) || (lc->cavetype == LOC_STAIRS_D) ) && ((lc2=lc->to)) ) ) && (lc2->exitdist > j) ) { lc2->exitdist = j; t = newtrail(); t->link = pending; pending = t; t->loc = lc2; } } } /* * Maintain the dumb track as the player moves. */ static void set_dumb_track(void) { LOC *lc; int i; LOC *lc2; lc = you->loc; for (i=0;i<8;i++) { lc2 = movecell(lc,delta[i][0],delta[i][1],0); if (! lc2) continue; if ( (lc2->type == LOC_GATE) || (lc2->type == LOC_DOOR) || (lc2->type == LOC_SDOOR) || (lc2->type == LOC_CAVE) ) { lc2->tracks[TRACK_DUMB] = lc; lc2->tracktime[TRACK_DUMB] = curtime; } } if ( ( (lc->type == LOC_GATE) && ((lc2=gate_other_end(lc,GH_REGULAR))) ) || ( (lc->type == LOC_CAVE) && ( (lc->cavetype == LOC_STAIRS_U) || (lc->cavetype == LOC_STAIRS_D) ) && ((lc2=lc->to)) ) ) { lc2->tracks[TRACK_DUMB] = lc; lc2->tracktime[TRACK_DUMB] = curtime; } } /* * Get a direction from the user. Any prompting or the like must have * already been displayed. If the user types something other than a * direction character, or if the direction would move off a * non-WRAPAROUND level, returns 0; otherwise, returns the moved-to * cell. (SIGINT produces a 0 return.) */ static LOC *getdir(LOC *from) { LEVEL *lv; int x; int y; JMP j; if (setjmp(j.b)) { pop_sigint_throw(); return(0); } lv = from->on; while (1) { push_sigint_throw(&j); x = tget(); pop_sigint_throw(); if (! dirkey(x,&x,&y,0,0,0)) return(0); x += from->x; y += from->y; if (lv->flags & LVF_WRAPAROUND) { if (x < 0) x += LEV_X; else if (x >= LEV_X) x -= LEV_X; if (y < 0) y += LEV_Y; else if (y >= LEV_Y) y -= LEV_Y; } else { if ((x < 0) || (y < 0) || (x >= LEV_X) || (y >= LEV_Y)) return(0); } return(&lv->cells[x][y]); } } /* * Update what the player can see. This is used after player motion, * hence the name. */ static void bemoved_see(LOC *lc) { if (lc->objs.inv && lc->objs.inv->link) { INVOBJ *o; INVOBJ **op; o = lc->objs.inv; lc->objs.inv = o->link; for (op=(&lc->objs.inv);*op;op=(&(*op)->link)) ; *op = o; o->link = 0; /* no upddisp needed: seecell below always calls upddisp */ } seecell(lc); } /* * Update the smart track for intra-level player motion. */ static void set_smart_track(LOC *lc) { int x; int y; x = you->loc->x - lc->x; y = you->loc->y - lc->y; if ((x == 0) && (y == 0)) return; if (lc->on->flags & LVF_WRAPAROUND) { if (x > LEV_X/2) x -= LEV_X; else if (x < -LEV_X/2) x += LEV_X; if (y > LEV_Y/2) y -= LEV_Y; else if (y < -LEV_Y/2) y += LEV_Y; } if (x < 0) x = -1; else if (x > 0) x = 1; if (y < 0) y = -1; else if (y > 0) y = 1; x += lc->x; y += lc->y; if (lc->on->flags & LVF_WRAPAROUND) { if (x < 0) x += LEV_X; else if (x >= LEV_X) x -= LEV_X; if (y < 0) y += LEV_Y; else if (y >= LEV_Y) y -= LEV_Y; } if ((x < 0) || (x >= LEV_X) || (y < 0) || (y >= LEV_Y)) { panic("out of range in set_smart_track"); } lc->tracks[TRACK_SMART] = &lc->on->cells[x][y]; lc->tracktime[TRACK_SMART] = curtime; } /* * Update the smart track for inter-level player motion. */ static void set_smart_level_track(void) { LOC *lc; LOC *lc2; lc = you->loc; if ( ( (lc->type == LOC_GATE) && ((lc2=gate_other_end(lc,GH_REGULAR))) ) || ( (lc->type == LOC_CAVE) && ( (lc->cavetype == LOC_STAIRS_U) || (lc->cavetype == LOC_STAIRS_D) ) && ((lc2=lc->to)) ) ) { lc2->tracks[TRACK_SMART] = lc; lc2->tracktime[TRACK_SMART] = curtime; } } static void add_post_refresh(void (*fn)(void *), void *arg) { POST_REFRESH *pr; pr = malloc(sizeof(POST_REFRESH)); pr->cb = fn; pr->cbarg = arg; pr->link = y.post_refresh; y.post_refresh = pr; } /* * Subsystem for mapping visible dungeon cells. * * map_vis_init allocates and sets up an MVSTATE, returning a pointer * to it, as void *. This must then be passed to the rest of the * functions. * * map_vis_reset resets the MVSTATE if the player has moved. * * map_vis_auto maps one remaining cell and returns nonzero, or, if * there are no remaining cells or if there is a monster next to the * player, does nothing and returns zero. If the player's hit points * have decreased since the last call (or init/reset, as appropriate), * also does nothing and returns zero. * * map_vis_start starts an M command. * * map_vis_force causes the next map_vis_reset to act as though the * player has moved, even if it actually hasn't. * * map_vis_memorize maps everything remaining, if any, but does so by * setting LF_MEMORIZED on the LOCs instead of updating the map. */ typedef struct mvstate MVSTATE; typedef struct mvloc MVLOC; struct mvloc { MVLOC *link; int x; int y; int d2; } ; struct mvstate { int n; int x; int a; unsigned char (*v)[2]; LOC *l; int lasthp; int done; } ; static MVLOC *freemvl = 0; static void mvlog_(int, const char *, ...) __attribute__((__format__(__printf__,2,3))); #define mvlog(fmt...) mvlog_(__LINE__,fmt) #include static void mvlog_(int line, const char *fmt, ...) { static int fd = -1; static FILE *f; va_list ap; switch (fd) { case -1: fd = open("mv.dbg",O_WRONLY|O_APPEND|O_TRUNC,0); if (fd < 0) { fd = -2; case -2: return; } f = fdopen(fd,"w"); break; } fprintf(f,"%d: ",line); va_start(ap,fmt); vfprintf(f,fmt,ap); va_end(ap); fprintf(f,"\n"); fflush(f); } static MVLOC *newml(void) { MVLOC *m; if (freemvl) { m = freemvl; freemvl = m->link; } else { m = malloc(sizeof(MVLOC)); } return(m); } static void oldml(MVLOC *ml) { ml->link = freemvl; freemvl = ml; } static void *map_vis_init(void) { MVSTATE *s; mvlog("map_vis_init"); s = malloc(sizeof(MVSTATE)); s->n = 0; s->x = 0; s->a = 0; s->v = 0; s->l = 0; s->lasthp = 0; return(s); } static MVLOC *sort_mvloc_list(MVLOC *list) { MVLOC *a; MVLOC *b; MVLOC *t; MVLOC **lp; if (!list || !list->link) return(list); a = 0; b = 0; while (list) { t = list; list = t->link; t->link = a; a = b; b = t; } a = sort_mvloc_list(a); b = sort_mvloc_list(b); lp = &list; while (1) { if (a && (!b || ((a->d2 < b->d2) || ((a->d2 == b->d2) && onein(2))))) { t = a; a = a->link; } else if (b) { t = b; b = b->link; } else { *lp = 0; return(list); } *lp = t; lp = &t->link; } } static void map_vis_reset(void *sv) { int x; int y; MVSTATE *s; int n; LOC *l; MVLOC *list; MVLOC *ml; LOC *yl; s = sv; yl = you->loc; if (yl == s->l) return; s->lasthp = you->hp; s->l = yl; n = 0; list = 0; mvlog("map_vis_reset"); for (x=0;xloc->on->cells[x][0]; for (y=0;yflags & LF_VISIBLE) { ml = newml(); ml->x = x; ml->y = y; ml->d2 = ((x - yl->x) * (x - yl->x)) + ((y - yl->y) * (y - yl->y)); ml->link = list; list = ml; mvlog(" saved (%d,%d) %d",ml->x,ml->y,ml->d2); n ++; } l ++; } } if (n > s->a) { free(s->v); s->a = n; s->v = malloc(n*sizeof(*s->v)); } x = 0; list = sort_mvloc_list(list); mvlog("list:"); while (list) { ml = list; list = ml->link; if (x > n) abort(); mvlog(" (%d,%d) %d",ml->x,ml->y,ml->d2); s->v[x][0] = ml->x; s->v[x][1] = ml->y; x ++; oldml(ml); } if (x != n) abort(); s->n = n; s->x = 0; s->done = 0; } static int map_vis_internal(MVSTATE *s, MAPSETRV (*set)(LOC *)) { LOC *l; int x; int y; while (1) { if (s->x >= s->n) return(0); x = s->v[s->x][0]; y = s->v[s->x][1]; s->x ++; l = &you->loc->on->cells[x][y]; if (l->flags & LF_MEMORIZED) { mvlog("internal: (%d,%d): memorized",x,y); } else if (! (l->flags & LF_VISIBLE)) { mvlog("internal: (%d,%d): not visible",x,y); } else { switch ((*set)(l)) { case MSR_ALREADY: mvlog("internal: (%d,%d): already done",x,y); break; case MSR_TOOKTIME: mvlog("internal: (%d,%d): updated",x,y); return(1); break; case MSR_NOTIME: mvlog("internal: (%d,%d): updated",x,y); break; default: abort(); break; } } } } static MAPSETRV map_vis_setmap(LOC *l) { char c; c = loc_char(l,LC_ORDINARY); if ((c == SYM_ROCK) && (l != you->loc)) return(MSR_NOTIME); return(map_setat(map,l->x+mapox,l->y+mapoy,c,l)?MSR_TOOKTIME:MSR_ALREADY); } static MAPSETRV map_vis_setmem(LOC *l) { if (l->flags & LF_MEMORIZED) return(MSR_ALREADY); l->flags |= LF_MEMORIZED; return(MSR_NOTIME); } static void map_vis_memorize(void *sv) { while (map_vis_internal(sv,&map_vis_setmem)) ; } static int map_vis_auto(void *sv) { MVSTATE *s; if (! map) return(0); if (monst_next_to(you->loc)) return(0); s = sv; if (s->done && (you->hp < s->lasthp)) return(0); if (! map_vis_internal(s,&map_vis_setmap)) return(0); s->done ++; if (you->hp > s->lasthp) s->lasthp = you->hp; return(1); } static int map_vis_more(void *sv) { MVSTATE *s; s = sv; if ( !map || (s->n < 1) || monst_next_to(you->loc) || (s->done && (you->hp < s->lasthp)) || !map_vis_internal(s,&map_vis_setmap) ) { set_continuing(0,0,0); return(0); } s->done ++; if (you->hp > s->lasthp) s->lasthp = you->hp; set_continuing(&map_vis_more,sv,0); you->speed /= MAP_PER_TICK; return(1); } static int map_vis_start(void *sv) { MVSTATE *s; s = sv; if (s->n < 1) return(0); set_continuing(&map_vis_more,sv,0); return(1); } static void map_vis_force(void *sv) { if (sv) ((MVSTATE *)sv)->l = 0; } static PICKUP_INV *pickup_inv_init(void) { PICKUP_INV *pi; pi = malloc(sizeof(PICKUP_INV)); pi->list = 0; pi->listt = 0; pi->srctext = 0; return(pi); } static void pickup_inv_cb(MWC_OP op, INVOBJ *io, void *piv) { PICKUP_INV *pi; PILINE *l; FILE *f; pi = piv; switch (op) { default: panic("bad op %d to pickup_inv_cb",(int)op); break; case MWC_BEGIN: pi->listt = &pi->list; pi->srctext = 0; break; case MWC_PRE: if (pi->srctext) panic("pickup_inv PRE with text"); f = fopenmalloc(&pi->srctext,0); (*objtypes[io->v[0]->type].ops->format)(f,io); fclose(f); break; case MWC_POST: if (! pi->srctext) break; l = malloc(sizeof(PILINE)); f = fopenmalloc(&l->text,0); format_inv_letter(io->xwi,f); fprintf(f,"%s",pi->srctext); fclose(f); free(pi->srctext); pi->srctext = 0; *pi->listt = l; pi->listt = &l->link; break; case MWC_DONE: if (pi->srctext) panic("pickup_inv DONE with text"); break; case MWC_END: if (pi->srctext) panic("pickup_inv END with text"); *pi->listt = 0; break; } } static void pickup_inv_report_list(void *piv) { PICKUP_INV *pi; PILINE *l; DLLS line(FILE *f) { if (! l) return(DL_L_DONE); fprintf(f,"%s",l->text); l = l->link; return(DL_L_MORE); } DLKS key(int ch) { switch (ch) { case ' ': case '\r': case '\n': return(DL_K_CONTINUE); break; case '\e': return(DL_K_ABORT); break; } return(DL_K_IGNORE); } pi = piv; l = pi->list; display_list(&line,"Picked up",&key); while ((l = pi->list)) { pi->list = l->link; free(l->text); free(l); } free(pi); } static void pickup_inv_report(PICKUP_INV *pi) { PILINE *l; if (pi->list && !pi->list->link) { pline("%s",pi->list->text); free(pi->list->text); free(pi->list); free(pi); return; } for (l=pi->list;l;l=l->link) mh_save(l->text); add_post_refresh(&pickup_inv_report_list,pi); } /* * The bemoved method for the player avatar. */ static void you_bemoved(MONST *m) { LOC *lc; LEVEL *lv; int switched; lc = m->loc; lv = lc->on; if (lv != y.lastlevel) { clear(); cleardisp(); switched = 0; if (map && y.auto_map_switch) { if ((y.lastloc->type == LOC_CAVE) && (y.lastloc->to == lc)) { switch (y.lastloc->cavetype) { case LOC_STAIRS_U: switched = map_auto_switch_up(lv->longname); break; case LOC_STAIRS_D: switched = map_auto_switch_dn(lv->longname); break; default: break; } } else { LOC *lc2; lc2 = y.lastloc->tracks[TRACK_SMART]; if (lc2 && (lc2->type == LOC_GATE) && (gate_other_end(lc2,GH_PLAYER)->on == lv)) { switched = map_auto_switch_gate(lc2,lv->longname); } } } if (! switched) map = 0; if (y.lastlevel) clearflag(y.lastlevel,LF_VISIBLE); if (lv->levelno < 0) { dispox = 0; dispoy = 0; } else if (y.lastlevel) { dispox += y.lastlevel->off.x - lv->off.x; dispoy += y.lastlevel->off.y - lv->off.y; } drawmapped(lv); y.lastlevel = lv; y.redo_see = 1; } if (y.redo_see || (lc != y.lastloc)) { y.redo_see = 0; map_vis_force(y.mvstate); set_dumb_track(); clearflag(lv,LF_VISIBLE); seecell_loc = lc; see(lc,lv->visibility,LF_VISIBLE,&bemoved_see,0); if (you->effflags & MEF_PERCEPTION) { see(lc,PERCEPTION_RADIUS,LF_VISIBLE,&bemoved_see,1); set_showdisp_offlevel(offlev_perception,(PERCEPTION_RADIUS*2)+1,(PERCEPTION_RADIUS*2)+1,&percept_offlev[0][0],lc->x-PERCEPTION_RADIUS,lc->y-PERCEPTION_RADIUS); } else { set_showdisp_offlevel(offlev_perception,0,0,0,lc->x,lc->y); } see(lc,7,0,&set_smart_track,0); set_smart_level_track(); if (lc->objs.inv && (lc != y.lastloc)) { int see_obj_type(INVOBJ *io) { see_type(io->v[0]->type); return(0); } y.running = RUN_NONE; inv_scan(&lc->objs,&see_obj_type); if (y.justmove) { pline("There's something here you could pick up."); } else { PICKUP_INV *pi; pi = pickup_inv_init(); merge_with_cb(&lc->objs,&you->invent,&pickup_inv_cb,pi); pickup_inv_report(pi); } } y.lastloc = lc; } } /* * Helper for search(), below. Searches just one cell. */ static int searchcell(LOC *c, double chance) { int rv; rv = 0; if ((c->type == LOC_SDOOR) && (frnd() < chance)) { finddoor(c); rv = 1; } if (((c->trap & TRAP_KIND) != TRAP_KIND_NONE) && !(c->trap & TRAP_KNOWN) && (frnd() < chance)) { findtrap(c); rv = 1; } return(rv); } /* * Search. chance is the chance of finding something that's there to * be found. * * Return is zero if nothing was found, nonzero if something was. */ int search(LOC *l, double chance) { int i; LOC *a; int found; found = 0; for (i=0;i<8;i++) { a = movecell(l,delta[i][0],delta[i][1],0); if (a && searchcell(a,chance)) found = 1; } if (searchcell(l,chance)) found = 1; return(found); } /* * Get a line of input text. prompt is the prompt, of course. rbuf * and rblen describe the buffer for input text. ilen is amount of * input text already present. suffix is anything which should be * displayed after the input text until the user types a character. * xtc is a string whose characters should be treated as equivalent to * RETURN for purposes of this read. * * Returns the character the user typed which terminated the operation, * or ESC if SIGINT terminated it. rbuf will always have a * terminating NUL present after the input string. */ static int get_iline(const char *prompt, char *rbuf, int rblen, int ilen, const char *suffix, const char *xtc) { int rlen; int plen; int xoff; int c; int suflen; int cursx; JMP j; void fullredraw(void) { move(LINES-1,0); if (xoff > 0) addch('<'); else addch(' '); if (xoff < plen) { addstr(prompt+xoff); addnstr(rbuf,rlen); addnstr(suffix,suflen); } else if (xoff < plen+rlen) { addnstr(rbuf+(xoff-plen),rlen-(xoff-plen)); addnstr(suffix,suflen); } else { addnstr(suffix+(xoff-plen-rlen),suflen-(xoff-plen-rlen)); } clrtoeol(); } int shiftxoff(void) { if ((xoff > 0) && (plen+rlen+suflen-xoff < (COLS/4))) { xoff = plen + rlen + suflen - ((2*COLS)/3); if (xoff < 0) xoff = 0; return(1); } if (plen+rlen+suflen-xoff >= COLS-4) { xoff = plen + rlen + suflen - ((2*COLS)/3); return(1); } return(0); } if (setjmp(j.b)) { pop_sigint_throw(); rbuf[0] = '\0'; move(LINES-1,0); clrtoeol(); return('\33'); } suflen = strlen(suffix); plen = strlen(prompt); rlen = ilen; xoff = 0; shiftxoff(); fullredraw(); while (1) { if (shiftxoff()) fullredraw(); cursx = (xoff <= plen+rlen) ? 1+plen+rlen-xoff : 0; move(LINES-1,cursx); push_sigint_throw(&j); c = tget(); pop_sigint_throw(); if ((c == '\177') || (c == '\b')) { if (rlen > 0) { rlen --; if (cursx) { move(LINES-1,cursx-1); addch(' '); } } } else if (c == '\33') { rlen = 0; break; } else if (c == '\f') { fullredraw(); } else if ((c == '\r') || (c == '\n') || index(xtc,c)) { break; } else if ((c >= ' ') && (c <= '~')) { if (rlen < rblen-1) { addch(c); rbuf[rlen++] = c; } else { beep(); } } if (suflen) { suffix = ""; suflen = 0; fullredraw(); } } rbuf[rlen] = '\0'; move(LINES-1,0); clrtoeol(); return(c); } /* * Get a number from the user. If the user just enters *, starval is * the value to return. */ static int get_n(const char *prompt, int starval) { int plen; int n; int n2; char nbuf[64]; int nblen; int c; JMP j; void fullredraw(void) { move(LINES-1,0); addnstr(prompt,plen); addnstr(&nbuf[0],nblen); clrtoeol(); } if (setjmp(j.b)) { pop_sigint_throw(); move(LINES-1,0); clrtoeol(); return(-1); } plen = strlen(prompt); n = 0; nblen = 0; fullredraw(); while (1) { move(LINES-1,plen+nblen); push_sigint_throw(&j); c = tget(); pop_sigint_throw(); if ((c == '\177') || (c == '\b')) { if (nblen > 0) { nblen --; move(LINES-1,plen+nblen); addch(' '); n /= 10; } } else if (c == '\33') { n = -1; break; } else if (c == '\f') { fullredraw(); } else if ((c == '\r') || (c == '\n')) { if (nblen > 0) break; beep(); } else if ((c >= '0') && (c <= '9')) { if ((nblen == 0) || (n == 0)) { nbuf[0] = c; nblen = 1; n = c - '0'; move(LINES-1,plen); addch(c); } else { n2 = (n * 10) + (c - '0'); if (n2/10 != n) { beep(); } else { addch(c); nbuf[nblen++] = c; n = n2; } } } else if ((c == '*') && (nblen == 0)) { n = starval; break; } else { beep(); } } move(LINES-1,0); clrtoeol(); return(n); } /* * Compare two INVOBJs. This is used to keep the player's inventory * sorted. */ static int you_inv_cmp(INVOBJ *io1, INVOBJ *io2) { OBJTYPE *ot1; OBJTYPE *ot2; char *ix1; char *ix2; const char order[] = "$~\")[=/?!%"; ot1 = &objtypes[io1->v[0]->type]; ot2 = &objtypes[io2->v[0]->type]; if (ot1->sym != ot2->sym) { ix1 = index(&order[0],ot1->sym); ix2 = index(&order[0],ot2->sym); if (ix1 && ix2) return(ix1-ix2); if (! ix1) pline("unknown symbol %c in you_inv_cmp",ot1->sym); if (! ix2) pline("unknown symbol %c in you_inv_cmp",ot2->sym); impossible(); } if (ot1->class != ot2->class) return(ot1->class-ot2->class); switch (ot1->class) { /*case OC_ARMOUR: if (armour_worn(io1) && !armour_worn(io2)) return(-1); if (armour_worn(io2) && !armour_worn(io1)) return(1); break; case OC_WEAPON: if (weapon_wielded(io1) && !weapon_wielded(io2)) return(-1); if (weapon_wielded(io2) && !weapon_wielded(io1)) return(1); break;*/ case OC_MAP: if (io1->v[0] == map) return(-1); if (io2->v[0] == map) return(1); if (map_blank(io1->v[0])) { if (! map_blank(io2->v[0])) return(1); } else { if (map_blank(io2->v[0])) return(-1); } break; case OC_SCROLL: case OC_POTION: case OC_WAND: case OC_RING: if ((ot1->flags & ~ot2->flags) & OTF_KNOWN) return(-1); if ((ot2->flags & ~ot1->flags) & OTF_KNOWN) return(1); if (ot1->called && !ot2->called) return(-1); if (ot2->called && !ot1->called) return(1); break; } return(io1->xwi-io2->xwi); } /* * Sort the player's inventory */ static void sort_you_inv(void) { you->invent.inv = sort_inv(you->invent.inv,&you_inv_cmp); } /* * Drop something. If mult is true, drop (potentially) more than one * thing. */ static int drop(int mult) { INVOBJ *io; int n; int rv; if (! you->invent.inv) { pline("You're not carrying anything!"); return(0); } sort_you_inv(); rv = 0; do { showmsgbuf(1); io = pick_inventory(&you->invent,"Drop what?",&invtest_all,0,"drop"); if (io == 0) break; if (io->dispn > 1) { n = get_n("Drop how many? ",io->dispn); if (n < 1) { pline("Aborted."); continue; } if (n < io->dispn) io = inventory_split_n(io,n,-1); } stop_using(you,io); inv_move_1(&you->invent,io,&you->loc->objs); pline("Dropped."); rv = 1; } while (mult); return(rv); } /* * Inventory filter function selecting things which can be read. */ static int invtest_readable(INVOBJ *io) { return(objtypes[io->v[0]->type].class == OC_SCROLL); } /* * Read command. */ static int do_read(void) { INVOBJ *io; INVOBJ *n; if (! test_inventory(&you->invent,&invtest_readable)) { pline("You don't have anything to read!"); return(0); } sort_you_inv(); io = pick_inventory(&you->invent,"Read what?",&invtest_readable,0,"read"); if (io == 0) return(0); if (io->dispn > 1) { n = inventory_split_n(io,1,find_xwi(&you->invent)); if (n) io = n; } read_it(io); return(1); } /* * Inventory filter function selecting things which can be wielded * (weapons not currently wielded). */ static int invtest_wieldable(INVOBJ *io) { return( (objtypes[io->v[0]->type].class == OC_WEAPON) && !weapon_being_wielded(io->v[0]) ); } /* * Wield-a-weapon command. */ static int do_wield(void) { INVOBJ *io; INVOBJ *n; int nothing; int cb(char ks) { if (ks != '-') return(0); nothing = 1; return(1); } sort_you_inv(); if (!you->weapon && !test_inventory(&you->invent,&invtest_wieldable)) { pline("You have nothing to wield!"); return(0); } nothing = 0; io = pick_inventory(&you->invent,"Wield what (- for nothing)?",&invtest_wieldable,&cb,"wield"); if (io == 0) { if (nothing) { weapon_wield(you,0); return(1); } return(0); } if (io->dispn > 1) { n = inventory_split_n(io,1,find_xwi(&you->invent)); if (n) io = n; } weapon_wield(you,io->v[0]); pline_invobj(io); return(1); } /* * Inventory filter function selecting things which can be put on * (rings not being worn). */ static int invtest_puttable(INVOBJ *io) { return( (objtypes[io->v[0]->type].class == OC_RING) && !ring_being_worn(io->v[0]) ); } /* * Put on a ring command. */ #if MAXRINGS != 2 #error "do_put_on() assumes MAXRINGS is 2" #endif static int do_put_on(void) { INVOBJ *io; if (! test_inventory(&you->invent,&invtest_puttable)) { pline("You don't have anything%s you can wear!",you->rings[0]?" more":""); return(0); } if (you->rings[MAXRINGS-1]) { pline("You're already wearing as many rings as you can!"); return(0); } sort_you_inv(); io = pick_inventory(&you->invent,"Put on what?",&invtest_puttable,0,"put on"); if (io == 0) return(0); if (io->dispn > 1) panic("plural ring"); ring_put(you,io->v[0]); pline_invobj(io); return(1); } /* * Inventory filter function selecting things which can be removed * (rings currently being worn). */ static int invtest_removable(INVOBJ *io) { return( (objtypes[io->v[0]->type].class == OC_RING) && ring_being_worn(io->v[0]) ); } /* * Remove a ring command. */ #if MAXRINGS != 2 #error "do_remove() assumes MAXRINGS is 2" #endif static int do_remove(void) { INVOBJ *io; if (! you->rings[0]) { pline("You're not wearing any rings!"); return(0); } sort_you_inv(); io = pick_inventory(&you->invent,"Remove what?",&invtest_removable,0,"remove"); if (io == 0) return(0); if (io->dispn > 1) panic("plural ring"); ring_remove(you,io->v[0]); return(1); } /* * Save-game command. */ static void do_save(void) { int c; char sfb[2048]; c = get_iline("Save to: ",&sfb[0],sizeof(sfb),0,"",""); if (c == '\33') { pline("Aborted."); return; } savegame_save(&sfb[0]); } /* * Inventory filter function selecting things which can be worn (armour * not being worn). */ static int invtest_wearable(INVOBJ *io) { return( (objtypes[io->v[0]->type].class == OC_ARMOUR) && !armour_being_worn(io->v[0]) ); } /* * Wear armour command. */ static int do_wear(void) { INVOBJ *io; if (! test_inventory(&you->invent,&invtest_wearable)) { pline("You don't have anything to wear!"); return(0); } if (you->armour) { pline("You're already wearing armour!"); return(0); } sort_you_inv(); io = pick_inventory(&you->invent,"Wear what?",&invtest_wearable,0,"wear"); if (io == 0) return(0); if (io->dispn > 1) panic("plural armour"); armour_wear(you,io->v[0]); pline_invobj(io); return(1); } /* * Take off armour command. */ static int do_take_off(void) { INVOBJ *ao; int scan(INVOBJ *io) { return(io->v[0]==you->armour); } if (! you->armour) { pline("You aren't wearing any armour!"); return(0); } ao = inv_scan(&you->invent,&scan); if (! ao) panic("can't find armour in inventory"); armour_take_off(you,you->armour); pline_invobj(ao); return(1); } /* * Inventory filter function selecting things which can be quaffed. */ static int invtest_quaffable(INVOBJ *io) { return(objtypes[io->v[0]->type].class == OC_POTION); } /* * Quaff command. */ static int do_quaff(void) { INVOBJ *io; INVOBJ *n; if (! test_inventory(&you->invent,&invtest_quaffable)) { pline("You're not carrying anything you can quaff!"); return(0); } sort_you_inv(); io = pick_inventory(&you->invent,"Quaff what?",&invtest_quaffable,0,"quaff"); if (io == 0) return(0); if (io->dispn > 1) { n = inventory_split_n(io,1,find_xwi(&you->invent)); if (n) io = n; } quaff_it(io); return(1); } /* * Inventory filter function selecting things which can be zapped. */ static int invtest_zappable(INVOBJ *io) { return(objtypes[io->v[0]->type].class == OC_WAND); } /* * Zap command. */ static int zap(void) { INVOBJ *io; INVOBJ *n; if (! test_inventory(&you->invent,&invtest_zappable)) { pline("You're not carrying anything you can zap!"); return(0); } sort_you_inv(); io = pick_inventory(&you->invent,"Zap what?",&invtest_zappable,0,"zap"); if (io == 0) return(0); if (io->dispn > 1) { n = inventory_split_n(io,1,find_xwi(&you->invent)); if (n) io = n; } wand_zap(io); return(1); } /* * Pick something up. */ static int pickup(void) { INVOBJ *io; int n; int rv; rv = 0; while (you->loc->objs.inv) { showmsgbuf(1); io = pick_inventory(&you->loc->objs,"Pick up what?",&invtest_all,0,"pick up"); if (io == 0) break; if (io->dispn > 1) { n = get_n("Pick up how many? ",io->dispn); if (n < 1) continue; if (n < io->dispn) io = inventory_split_n(io,n,-1); } io = inv_move_1(&you->loc->objs,io,&you->invent); pline_invobj(io); rv = 1; } return(rv); } /* * Toggle (in)vulnerability. */ static void vulnerability_toggle(void) { y.invulnerable = ! y.invulnerable; pline("Invulnerability turned %s",y.invulnerable?"on":"off"); } /* * Prompt for, accept, and execute an extened command. */ static int x_cmd(const char *prompt, const XCMD *cmdtab) { char cmdbuf[32]; int i; int l; int p; int c; JMP j; l = 0; if (setjmp(j.b)) { pop_sigint_throw(); c = '\33'; } else { c = get_iline(prompt,&cmdbuf[0],sizeof(cmdbuf),l,""," ?"); } while (1) { l = strlen(&cmdbuf[0]); if (c == '?') { int x; int cl; void morebreak(const char *s) { addstr(s); push_sigint_throw(&j); tget(); pop_sigint_throw(); move(LINES-2,0); clrtoeol(); } move(LINES-2,0); clrtoeol(); x = 0; for (i=0;cmdtab[i].cmd;i++) { cl = strlen(cmdtab[i].cmd); if ((cl < l) || strncmp(cmdtab[i].cmd,&cmdbuf[0],l)) continue; if (x && (x+1+cl >= COLS-10)) { morebreak(" --More--"); x = 0; } if (x) { addch(' '); x ++; } addstr(cmdtab[i].cmd); x += cl; } morebreak(" --End--"); c = get_iline(prompt,&cmdbuf[0],sizeof(cmdbuf),l,""," ?"); continue; } move(LINES-1,0); clrtoeol(); if (! cmdbuf[0]) return(0); p = -1; for (i=0;cmdtab[i].cmd;i++) { if (!strcmp(&cmdbuf[0],cmdtab[i].cmd)) return((*cmdtab[i].fn)()); if (!strncmp(&cmdbuf[0],cmdtab[i].cmd,l)) { if (p == -1) p = i; else p = -2; } } if (p >= 0) return((*cmdtab[p].fn)()); c = get_iline(prompt,&cmdbuf[0],sizeof(cmdbuf),l,(p==-1)?" [no match]":" [ambiguous]"," ?"); } } /* * Inventory filter function selecting maps. */ static int invtest_map(INVOBJ *io) { return(io->v[0]->type == OBJ_MAP); } /* * Suspend the game. */ static void suspend_game(void) { cleanupscreen(); kill(0,SIGTSTP); initscreen(); } /* * #map use command. */ static int xcmd_map_use(void) { INVOBJ *io; OBJ *m; int ox; int oy; OBJ *msave; int rv; if (! test_inventory(&you->invent,&invtest_map)) { pline("You don't have any maps to use!"); return(0); } sort_you_inv(); io = pick_inventory(&you->invent,"Use which map?",&invtest_map,0,"use"); if (io == 0) return(0); m = io->v[0]; if (map_blank(m)) { if (io->dispn > 1) io = inventory_split_n(io,1,find_xwi(&you->invent)); map = io->v[0]; mapox = 0; mapoy = 0; pline("This map is blank."); return(1); } msave = map; map = 0; cleardisp(); drawmapped(you->loc->on); map = msave; if (place_map(m,&ox,&oy,"Position the map...")) { map = m; mapox = ox; mapoy = oy; pline("Done."); rv = 1; } else { pline("Aborted."); rv = 0; } cleardisp(); drawmapped(you->loc->on); return(rv); } /* * #map view command. */ static int xcmd_map_view(void) { INVOBJ *io; OBJ *m; if (! test_inventory(&you->invent,&invtest_map)) { pline("You don't have any maps to view!"); return(0); } sort_you_inv(); io = pick_inventory(&you->invent,"View which map?",&invtest_map,0,"view"); if (io == 0) return(0); m = io->v[0]; if (map_blank(m)) { pline("This map is blank."); return(0); } view_map(m); return(1); } /* * #map auto on command. */ static int xcmd_map_auto_on(void) { automap = 1; return(1); } /* * #map auto off command. */ static int xcmd_map_auto_off(void) { automap = 0; return(1); } /* * #map auto command. */ static int xcmd_map_auto(void) { static const XCMD cmds[] = { { "on", &xcmd_map_auto_on }, { "off", &xcmd_map_auto_off }, { 0 } }; return(x_cmd("#map auto ",&cmds[0])); } /* * #map command. */ static int xcmd_map(void) { static const XCMD cmds[] = { { "use", &xcmd_map_use }, { "view", &xcmd_map_view }, { "auto", &xcmd_map_auto }, { 0 } }; return(x_cmd("#map ",&cmds[0])); } /* * #inv pack command. */ static int xcmd_inv_pack(void) { INVOBJ *io; int n; sort_you_inv(); n = 1; for (io=you->invent.inv;io;io=io->link) if (io->xwi) io->xwi = n++; pline("Done."); return(1); } /* * #inv command. */ static int xcmd_inv(void) { static const XCMD cmds[] = { { "pack", &xcmd_inv_pack }, { 0 } }; return(x_cmd("#inv ",&cmds[0])); } /* * #drop command. */ static int xcmd_drop(void) { return(drop(1)); } /* * #call command. */ static int xcmd_call(void) { char tvec[8]; int ntvec; int ocvec[8]; char prompt[64]; char choice[2]; PRSTAT rs; int ocn; const OBJCLASS *oc; int j; OBJTYPE *ot; int n; char as[50]; char **strings; int *otmap; char *p; int k; void scan_types(int oc) { const OBJCLASS *c; OBJTYPE *t; int i; c = &objclass[oc-OC__BASE]; for (i=c->minn;i<=c->max;i++) { t = &objtypes[i]; if (t->flags & OTF_SEEN) { tvec[ntvec] = t->sym; ocvec[ntvec] = oc; ntvec ++; return; } } } ntvec = 0; scan_types(OC_POTION); scan_types(OC_SCROLL); scan_types(OC_WAND); scan_types(OC_RING); if (ntvec < 1) { pline("You haven't seen anything callable!"); return(0); } if (ntvec == 1) { ocn = ocvec[0]; } else { char *tp; CHOK chr(char c) { if (memchr(&tvec[0],c,ntvec)) return(CHOK_TERM); return(CHOK_BAD); } snprintf(&prompt[0],sizeof(prompt),"What type [%.*s]? ",ntvec,&tvec[0]); rs = prompt_and_read(&prompt[0],&choice[0],sizeof(choice),0,&chr); if (! choice[0]) return(0); tp = memchr(&tvec[0],choice[0],ntvec); if (! tp) { pline("Can't find choice %c in %.*s",choice[0],ntvec,&tvec[0]); impossible(); return(0); } ocn = ocvec[tp-&tvec[0]]; } if ((ocn < OC__BASE) || (ocn > OC__MAX)) { pline("Chose impossible class %d",ocn); impossible(); return(0); } oc = &objclass[ocn-OC__BASE]; n = 0; for (j=oc->minn;j<=oc->max;j++) { ot = &objtypes[j]; if (ot->flags & OTF_SEEN) n ++; } strings = malloc(n*sizeof(char *)); otmap = malloc(n*sizeof(int)); k = 0; for (j=oc->minn;j<=oc->max;j++) { ot = &objtypes[j]; if (ot->flags & OTF_SEEN) { if (k >= n) panic("impossible string list overrun"); if (ot->called) { asprintf(&strings[k],"%s (called %s)",ot->name2,ot->called); } else { strings[k] = strdup(ot->name2); } otmap[k] = j; k ++; } } if (k != n) panic("impossible string list mismatch"); asprintf(&p,"Which %s? ",oc->name); j = choose_one_v(p,(const void *)&strings[0],n); free(p); for (k=n-1;k>=0;k--) free(strings[k]); free(strings); if (j < 0) return(0); if (j >= n) panic("impossible string choice"); ot = &objtypes[otmap[j]]; free(otmap); if (ot->called) { n = strlen(ot->called); if (n >= sizeof(as)) n = sizeof(as) - 1; bcopy(ot->called,&as[0],n); } else { n = 0; } j = get_iline("What do you want to call it? ",&as[0],sizeof(as),n,"",""); if (j == '\33') return(0); free(ot->called); if (as[0]) { ot->called = strdup(&as[0]); } else { ot->called = 0; } return(0); } /* * #name command. */ static int xcmd_name(void) { int i; char newname[64]; INVOBJ *io; OBJ *o; if (! you->invent.inv) { pline("You're not carrying anything!"); return(0); } sort_you_inv(); showmsgbuf(1); io = pick_inventory(&you->invent,"Name what?",&invtest_all,0,"name"); if (! io) return(0); if (io->v[0]->name) { for (i=1;in;i++) { if ( !io->v[i]->name || strcmp(io->v[0]->name,io->v[i]->name) ) { panic("INVOBJ's OBJs disagree on name"); } } i = strlen(io->v[0]->name); if (i > sizeof(newname)-1) i = sizeof(newname) - 1; bcopy(io->v[0]->name,&newname[0],i); newname[i] = '\0'; i = get_iline((io->dispn==1)?"Name it: ":"Name them:",&newname[0],sizeof(newname),i,"",""); } else { for (i=1;in;i++) { if (io->v[i]->name) panic("INVOBJ's OBJs disagree on name"); } i = get_iline((io->dispn==1)?"Name it: ":"Name them:",&newname[0],sizeof(newname),0,"",""); } if (i == '\33') return(0); if (! newname[0]) { for (i=0;in;i++) { o = io->v[i]; free(o->name); o->name = 0; } } else { for (i=0;in;i++) { o = io->v[i]; free(o->name); o->name = strdup(&newname[0]); } } return(0); } /* * #seed command. */ static int xcmd_seed(void) { pline("Seed: %llu",get_dice_seed()); return(0); } /* * #stat command. This is debugging assist. */ static int xcmd_stat(void) { void list_bonuses(const char *tag, BONUSROLL *list) { FILE *f; if (! list) { pline("no %s bonuses",tag); return; } f = fopen_pline(); fprintf(f,"%s bonuses:",tag); for (;list;list=list->flink) fprintf(f," %s",list->roll); fclose(f); } list_bonuses("defense",you->bonus_def); list_bonuses("to-hit",you->bonus_hit); list_bonuses("damage",you->bonus_dmg); return(0); } /* * #quit command. */ static int xcmd_quit(void) { if (confirm("Quit? Really?")) { endwin(); exit(0); } return(0); } /* * #z command. */ static int xcmd_z(void) { suspend_game(); return(0); } /* * #msgs command. */ static int xcmd_msgs(void) { LESS *l; int i; l = less_start(); do <"err"> { if (less_send(l,"[Newest]\n",-1,LESS_BLOCKING) < 0) break <"err">; i = y.mh_head; while (1) { if (i == y.mh_tail) break; i = (i ? i : MSGHISTSIZE) - 1; if ( (less_send(l,y.mh_text[i],-1,LESS_BLOCKING) < 0) || (less_send(l,"\n",1,LESS_BLOCKING) < 0) ) break <"err">; } if ((y.mh_head+1)%MSGHISTSIZE == y.mh_tail) { if (less_send(l,"...\n",-1,LESS_BLOCKING) < 0) break <"err">; } if (less_send(l,"[Oldest]\n",-1,LESS_BLOCKING) < 0) break <"err">; less_done(l); return(0); } while (0); less_abort(l); return(0); } /* * # command. */ static int extended_command(void) { static const XCMD cmds[] = { { "map", &xcmd_map }, { "drop", &xcmd_drop }, { "call", &xcmd_call }, { "name", &xcmd_name }, { "seed", &xcmd_seed }, { "stat", &xcmd_stat }, { "quit", &xcmd_quit }, { "inv", &xcmd_inv }, { "z", &xcmd_z }, { "msgs", &xcmd_msgs }, { 0 } }; return(x_cmd("#",&cmds[0])); } /* * Move from a location by a delta. Returns the moved-to LOC, or 0 if * the motion would move off the edge of a non_WRAPAROUND level. */ static LOC *move_by(LOC *l, XY d) { int x; int y; x = l->x + d.x; y = l->y + d.y; if ((x < 0) || (y < 0) || (x >= LEV_X) || (y >= LEV_Y)) { if (l->on->flags & LVF_WRAPAROUND) { x = (x + LEV_X) % LEV_X; y = (y + LEV_Y) % LEV_Y; } else { return(0); } } return(&l->on->cells[x][y]); } static int ad_test_fire(AREADAMAGE *ad, MONST *m) { (void)ad; return( (m->loc->on->levelno == L_FIRE) && !( (m->baseflags & MBF_CROWN) || (m->effflags & (MEF_PLANE_F|MEF_RESIST_FIRE)) ) ); } static DAMAGE ad_damage_fire(AREADAMAGE *ad, MONST *m) { (void)m; return(damage_simple(rnd(((curtime-ad->lasttime)*1000)/TIMESCALE)+1,DK_FIRE,0)); } static AREADAMAGE ad_fire = { &ad_test_fire, &ad_damage_fire, "You feel burning hot!", "You are burning!", "You no longer feel so burning hot!", 0 }; static int ad_test_water(AREADAMAGE *ad, MONST *m) { (void)ad; return( (m->loc->flags & LF_WATERFILL) && !( (m->effflags & (MEF_BREATHE_WATER|MEF_PHASING)) || ( (m->loc->on->levelno == L_WATER) && ( (m->baseflags & MBF_CROWN) || (m->effflags & MEF_PLANE_W) ) ) ) ); } static DAMAGE ad_damage_water(AREADAMAGE *ad, MONST *m) { int d; d = (m->maxhp / 20) + rnd(1000) - rnd(1000); if (d < 0) d = 0; return(damage_simple((((curtime-ad->lasttime)*d)/TIMESCALE)+1,DK_ORDINARY,DF_NOREDUCTION)); } static AREADAMAGE ad_water = { &ad_test_water, &ad_damage_water, "You start to drown!", "You are drowning!", "You can now breathe comfortably!", 0 }; static void do_area_damage(MONST *m, AREADAMAGE *ad) { if ((*ad->test)(ad,m)) { if (ad->going_on) { pline("%s",ad->ongoingmsg); (*m->ops->takedamage)(m,(*ad->damage)(ad,m)); ad->lasttime = curtime; } else { pline("%s",ad->startmsg); ad->going_on = 1; ad->lasttime = curtime; } } else { if (ad->going_on) { pline("%s",ad->stopmsg); ad->going_on = 0; } } } static void area_damage(MONST *m) { do_area_damage(m,&ad_water); do_area_damage(m,&ad_fire); } /* * Move the player by a delta. Also handles walking into gates. * * Returns true if the move succeeded, false if not. */ static int move_you(XY d) { LOC *lc; LOC *lc2; lc = you->loc; lc2 = move_by(lc,d); if (! lc2) // can happen with phasing { y.running = RUN_NONE; return(0); } if ( (abs(lc->x-lc2->x) > 1) || (abs(lc->y-lc2->y) > 1) ) { y.running = RUN_NONE; } if ( (lc2->on->levelno == L_AIR) && !(you->baseflags & MBF_CROWN) && !(you->effflags & MEF_PLANE_A) && (lc2->type != LOC_GATE) ) { pline("You can't seem to figure out how to move!"); return(0); } if (lc2->type == LOC_GATE) { y.running = RUN_NONE; lc->tracks[TRACK_DUMB] = lc2; lc->tracktime[TRACK_DUMB] = curtime; lc2->tracks[TRACK_DUMB] = 0; lc2->tracktime[TRACK_DUMB] = curtime; lc->tracks[TRACK_SMART] = lc2; lc->tracktime[TRACK_SMART] = curtime; lc2->tracks[TRACK_SMART] = 0; lc2->tracktime[TRACK_SMART] = curtime; gatemon(you,gate_other_end(lc2,GH_PLAYER)); switch (you->loc->on->levelno) { case L_EARTH: if ( (you->baseflags & MBF_CROWN) || (you->effflags & MEF_PLANE_E) ) { pline("You feel oddly comfortable here."); } else { pline("You feel very confined here."); } break; case L_AIR: if ( (you->baseflags & MBF_CROWN) || (you->effflags & MEF_PLANE_A) ) { pline("You feel oddly comfortable here."); } else { pline("You are floating in midair and can't see how to move."); } break; case L_FIRE: if ( (you->baseflags & MBF_CROWN) || (you->effflags & MEF_PLANE_F) ) { pline("You feel oddly comfortable here."); } break; case L_WATER: if ( (you->baseflags & MBF_CROWN) || (you->effflags & MEF_PLANE_W) ) { pline("You feel oddly comfortable here."); } break; case L_UNDERWORLD: pline("You feel creeped out by this place."); break; case L_HELL: pline("You feel the hair standing up on the back of your neck."); break; } } else if ( (lc2->type == LOC_CAVE) || (you->effflags & MEF_PHASING) || ( (lc2->type == LOC_DOOR) && (lc2->walldoor & LOC_DOOR_OPEN) ) || ( (lc2->on->levelno == L_EARTH) && ( (you->baseflags & MBF_CROWN) || (you->effflags & MEF_PLANE_E) ) ) ) { if (lc2->monst) { y.running = RUN_NONE; mhitm(you,lc2->monst); } else { movemon(you,lc2); if ( lc2->objs.inv || ( ((lc2->trap & TRAP_KIND) != TRAP_KIND_NONE) && (lc2->trap & TRAP_KNOWN) ) || !( (lc2->type == LOC_CAVE) && (lc2->cavetype == LOC_JUSTCAVE) ) ) y.running = RUN_NONE; } } else { y.running = RUN_NONE; return(0); } return(1); } /* * Inventory objects of only some types. This prompts for which * type(s) to list, then lists them. The pseudo-types u and k are * also accepted, meaning unknown and known. */ static void do_type_inv(void) { char prompt[64]; char resp[64]; char *cp; INVOBJ *io; char c; char lastc; int wantunknown; int wantknown; int invent_interest(INVOBJ *io) { return( (wantunknown && invtest_unidentified(io)) || (wantknown && !invtest_unidentified(io)) || !!index(&resp[0],objtypes[io->v[0]->type].sym) ); } CHOK pcharok(char typed) { return(index(&resp[0],typed)?CHOK_OK:CHOK_BAD); } cp = &resp[0]; lastc = '\0'; sort_you_inv(); for (io=you->invent.inv;io;io=io->link) { c = objtypes[io->v[0]->type].sym; if (c != lastc) { *cp++ = c; lastc = c; } } *cp++ = 'u'; *cp++ = 'k'; *cp = '\0'; snprintf(&prompt[0],sizeof(prompt),"What types [%s]? ",&resp[0]); if (prompt_and_read(&prompt[0],&resp[0],sizeof(resp),0,&pcharok) == PR_OK) { wantunknown = !!index(&resp[0],'u'); wantknown = !!index(&resp[0],'k'); show_inventory(&you->invent,"",&invent_interest,"Nothing matches"); } } /* * Add a character to the prefix/prefix_a/prefix_n string. */ static void prefix_add(int ch) { if (y.prefix_n >= y.prefix_a) y.prefix = realloc(y.prefix,y.prefix_a=(y.prefix_n+8)); y.prefix[y.prefix_n++] = ch; } /* * If we have a numeric prefix, return its value; if not, or if it's * garbage, return -1. */ static int prefix_num(void) { long int v; int iv; prefix_add('\0'); v = strtol(y.prefix,0,10); iv = v; if ((v < 0) || (v != iv)) return(-1); return(iv); } /* * Checks whether we should stop running. This is where control-letter * running's stop condition is tested. */ static int run_stop(void) { LOC *f; LOC *l; LOC *r; LOC *l45; LOC *r45; LOC *of; LOC *ol45; LOC *or45; switch (y.running) { default: abort(); break; case RUN_SHIFT: break; case RUN_CTL: { int foo(LOC *l) { return(l->monst || l->objs.inv || (((l->trap & TRAP_KIND) != TRAP_KIND_NONE) && (l->trap & TRAP_KNOWN))); } int bar(LOC *a, LOC *b) { LOCTYPE at; LOCTYPE bt; at = a->type; bt = b->type; if (at == LOC_SDOOR) at = LOC_WALL; if (bt == LOC_SDOOR) bt = LOC_WALL; if (at != bt) return(1); switch (at) { case LOC_WALL: case LOC_ROCK: return(0); break; case LOC_CAVE: return(a->cavetype!=b->cavetype); break; case LOC_GATE: return(a->gatetype!=b->gatetype); break; case LOC_DOOR: return(a->walldoor!=b->walldoor); break; default: abort(); break; } } f = move_by(you->loc,y.run_d); l = move_by(you->loc,turn_l(y.run_d)); r = move_by(you->loc,turn_r(y.run_d)); l45 = move_by(you->loc,turn_l45(y.run_d)); r45 = move_by(you->loc,turn_r45(y.run_d)); if (! f) return(1); if (foo(f)) { if (y.debugmsg) pline("f (%d,%d) monst %p inv %p",f->x,f->y,(void *)f->monst,(void *)f->objs.inv); return(1); } if (l && foo(l)) { if (y.debugmsg) pline("l (%d,%d) monst %p inv %p",l->x,l->y,(void *)l->monst,(void *)l->objs.inv); return(1); } if (r && foo(r)) { if (y.debugmsg) pline("r (%d,%d) monst %p inv %p",r->x,r->y,(void *)r->monst,(void *)r->objs.inv); return(1); } if (r45 && foo(r45)) { if (y.debugmsg) pline("r45 (%d,%d) monst %p inv %p",r45->x,r45->y,(void *)r45->monst,(void *)r45->objs.inv); return(1); } if (l45 && foo(l45)) { if (y.debugmsg) pline("l45 (%d,%d) monst %p inv %p",l45->x,l45->y,(void *)l45->monst,(void *)l45->objs.inv); return(1); } of = move_by(y.startat,y.run_d); if (bar(f,of)) return(1); ol45 = move_by(y.startat,turn_l45(y.run_d)); if (bar(l45,ol45)) return(1); or45 = move_by(y.startat,turn_r45(y.run_d)); if (bar(r45,or45)) return(1); } break; } return(0); } /* * Show message history. This lets the user ^P/^N back and forth * through message history. ESC, CR, LF, ^G, and SIGINT abort the * process. */ static void mh_show_hist(void) { int x; int o; JMP j; int c; if (setjmp(j.b)) { pop_sigint_throw(); return; } mvprintw(MYO,0,"%d %d",y.mh_head,y.mh_tail); tget(); if (y.mh_head == y.mh_tail) return; o = 1; while (1) { x = (y.mh_head + MSGHISTSIZE - o) % MSGHISTSIZE; mvprintw(MYO,0,"(%d) %s",o,y.mh_text[x]); clrtoeol(); push_sigint_throw(&j); c = tget(); pop_sigint_throw(); switch (c) { case '\020': if (x == y.mh_tail) { beep(); } else { o ++; } break; case '\016': if (o < 2) { beep(); } else { o --; } break; default: y.repcmd = c; y.repcount = 1; /* fall through */ case '\33': case '\r': case '\n': case '\7': move(MYO,0); clrtoeol(); return; break; } } } /* * The debug phasing effect. */ static void apply_debug_phase(MONST *m, void *arg __attribute__((__unused__))) { if (m != you) panic("debug phasing on mon-you"); m->effflags |= MEF_PHASING; } static void death_debug_phase(MONST *m __attribute__((__unused__)), void *arg __attribute__((__unused__))) { } static EFFECTOPS effops_debug_phase = EFFECTOPS_INIT(debug_phase); /* * Toggle debug phasing. */ static void debug_phasing_toggle(void) { if (y.debug_phasing) { effect_remove(you,y.debug_phasing); pline("Debug phasing off."); y.debug_phasing = 0; } else { y.debug_phasing = effect_add(you,0,&effops_debug_phase); pline("Debug phasing on."); } } /* * Tell the player what's known about identifiable items (eg, rings). * * This code knows that all the types it cares about - potions, * scrolls, rings, and wands - use the OTF_KNOWN flag to indicate the * flavour of identifiedness it cares about. */ #define NCLASSES 4 static void knowledge_show(int verbose) { char *text[OBJ__N]; int len[OBJ__N]; int count[NCLASSES]; int nlines[NCLASSES]; int cwidths[NCLASSES][OBJ__N]; int types[NCLASSES][OBJ__N]; static const int classes[] = { OC_POTION, OC_SCROLL, OC_RING, OC_WAND }; static const char * const names[] = { "potions", "scrolls", "rings", "wands" }; static const char * const Names[] = { "Potions", "Scrolls", "Rings", "Wands" }; static int state; int x; int i; int j; int k; int col; int totw; int w; int maxw; DLLS line(FILE *f) { int tx; int ntx; const char *s; int c; switch (state) { case 0: // about to generate "%s you know:" line; x is class index if (count[x] == 0) { fprintf(f,"You know no %s",names[x]); trace_text("You know no %s\n",names[x]); state = 1; x --; return(DL_L_MORE); } fprintf(f,"%s you know:",Names[x]); trace_text("%s you know\n",Names[x]); state = 2; i = 0; return(DL_L_MORE); break; case 1: // about to generate blank line; x is next class index if (x < 0) return(DL_L_DONE); state = 0; trace_text("blank line, x=%d (%s)\n",x,names[x]); return(DL_L_MORE); break; case 2: // generating list of types; x is class index, i is line # trace_text("line, x=%d i=%d nlines[x]=%d count[x]=%d\n",x,i,nlines[x],count[x]); fprintf(f," "); for (tx=count[x]-1-i,c=0;tx>=0;tx=ntx,c++) { ntx = tx - nlines[x]; s = text[types[x][tx]]; trace_text("loop, tx=%d c=%d ntx=%d types[x][tx]=%d cwidths[x][c]=%d text=%s\n",tx,c,ntx,types[x][tx],cwidths[x][c],s); if (ntx >= 0) { fprintf(f,"%-*s",cwidths[x][c]+2,s); } else { fprintf(f,"%s",s); } } trace_text("loop exit, i=%d\n",i); i ++; if (i >= nlines[x]) { state = 1; x --; trace_text("now in state 1, x=%d\n",x); } return(DL_L_MORE); break; } panic("impossible state %d when showing knowledge",state); } trace_text("knowledge_show: ==== new call ==== (verbose=%d)\n",verbose); if ((sizeof(classes)/sizeof(classes[0])) != NCLASSES) panic("NCLASSES wrong"); for (i=NCLASSES-1;i>=0;i--) count[i] = 0; for (i=OBJ__N-1;i>=0;i--) { text[i] = 0; len[i] = 0; } for (x=OBJ__N-1;x>=0;x--) { for (i=NCLASSES-1;i>=0;i--) { if (objtypes[x].class == classes[i]) { if (objtypes[x].flags & OTF_KNOWN) { types[i][count[i]++] = x; break; } } } } for (x=NCLASSES-1;x>=0;x--) { trace_text("class %d (%s): count %d\n",x,names[x],count[x]); if (count[x] < 1) continue; for (i=count[x]-1;i>=0;i--) { if (verbose) { len[types[x][i]] = asprintf(&text[types[x][i]],"%s (%s)",objtypes[types[x][i]].name,objtypes[types[x][i]].name2); } else { text[types[x][i]] = strdup(objtypes[types[x][i]].name); len[types[x][i]] = strlen(text[types[x][i]]); } trace_text("gen text for %d (%d): %s\n",types[x][i],len[types[x][i]],text[types[x][i]]); } j = 1; while (j < count[x]) { trace_text("loop, trying j=%d\n",j); totw = -2; maxw = 0; k = 0; col = 0; for (i=count[x]-1;;i--) { trace_text(" loop, i=%d, k=%d\n",i,k); if ((k >= j) || (i < 0)) { trace_text(" setting cwidths[%d][%d]=%d\n",x,col,maxw); cwidths[x][col++] = maxw; totw += 2 + maxw; trace_text(" totw=%d\n",totw); maxw = 0; k = 0; } if (i < 0) break; w = len[types[x][i]]; if (w > maxw) maxw = w; trace_text(" w=%d maxw=%d (text=%s)\n",w,maxw,text[types[x][i]]); k ++; } trace_text(" loop done, totw %d\n",totw); if (totw <= COLS-6) break; j ++; } trace_text("loop done, j %d\n",j); nlines[x] = j; } x = NCLASSES - 1; state = 0; display_list(&line,"",&dl_justmore); for (i=OBJ__N-1;i>=0;i--) free(text[i]); } /* * Pick a location, eg for controlled teleport. Display is driven off * maps and memorized locations. Returns the picked location, or nil * if the operation was canceled. */ LOC *pick_location(const char *prompt) { int ox; int oy; int x; int y; int dx; int dy; int px; int py; int fulldisp; int f; OBJ *m; OBJ *m2; LEVEL *lv; LOC *rv; LOC *l; JMP j; int c; INVOBJ *mio; unsigned char fa[LEV_X][LEV_Y]; char ca[LEV_X][LEV_Y]; rv = 0; do <"done"> { if (setjmp(j.b)) { pop_sigint_throw(); break <"done">; } fulldisp = 1; lv = you->loc->on; m = map; px = you->loc->x; py = you->loc->y; while (1) { while <"reading"> (1) { if (! m) { mio = pick_inventory(&you->invent,"What map?",&invtest_nbmap,0,"look at"); if (! mio) { if (confirm("Abort?")) break <"done">; continue; } m = mio->v[0]; } if (fulldisp) { fulldisp = 0; clear(); ox = (map_maxx(m) + map_minx(m) - LEV_X) / 2; oy = (map_maxy(m) + map_miny(m) - LEV_Y) / 2; } if (map_label(m)) mvprintw(0,MXO,"%s",map_label(m)); mvprintw(LINES-2,0,"%s",prompt); mvprintw(LINES-1,0,"(Move, or ESC to abort)"); for (x=LEV_X-1;x>=0;x--) for (y=LEV_Y-1;y>=0;y--) { fa[x][y] = 0; ca[x][y] = SYM_OOS; } for (x=LEV_X-1;x>=0;x--) for (y=LEV_Y-1;y>=0;y--) { ca[x][y] = map_charat(m,x+ox,y+oy); // XXX Figure out how to merge maps and memorized locations, // especially in view of possibly-wrong maps! l = map_locat(m,x+ox,y+oy); if (l && (l->on == you->loc->on) && (l->flags & LF_VISIBLE)) fa[x][y] = DF_LIT; } showdisp2(&fa[0],&ca[0],0,0,0); move(py-oy,px-ox); push_sigint_throw(&j); c = tget(); pop_sigint_throw(); if (dirkey(c,&dx,&dy,0,&f,DK_NOCTL)) { if (f & DK_CAPS) { dx *= 8; dy *= 4; } px += dx; py += dy; ox += dx; oy += dy; continue; } switch (c) { case '\7': case '\33': break <"reading">; case '\r': case '\n': rv = map_locat(m,px,py); break <"done">; case '<': m2 = map_auto_link_up(m,&ox,&oy); if (0) { case '>': m2 = map_auto_link_dn(m,&ox,&oy); } if (m2) { m = m2; fulldisp = 1; } break; } beep(); } if (confirm("Abort?")) break <"done">; } } while (0); clear(); return(rv); } /* * Sanity-check things that need it. */ static void sanity_check(void) { INVOBJ *io; int n; int i; for (io=you->invent.inv;io;io=io->link) { n = 0; for (i=io->n-1;i>=0;i--) n += io->v[i]->number; if (n != io->dispn) panic("inventory inconsistency"); } } /* * prompt_and_read approval function used by display_identify(). */ static CHOK dispchar(char ch) { (void)ch; return(CHOK_TERM); } /* * Show what a dungeon-display character represents. * * Letters are monsters. Unused screen symbols: * * &'(*,123456789;\]{} */ static void display_identify(void) { PRSTAT prs; char ch[2]; prs = prompt_and_read("What display character? ",&ch[0],2,PRF_NOSTD,&dispchar); switch (prs) { case PR_OK: break; case PR_ABORTED: return; break; default: pline("Bad return %d from prompt_and_read",(int)prs); impossible(); return; break; } switch (ch[0]) { case ' ': pline(" Solid rock, or unknown"); break; case '-': pline("- East-west wall"); break; case '|': pline("| North-south wall"); break; case '+': pline("+ Intersection of two walls"); break; case '<': pline("< A stairwell leading up"); break; case '>': pline("> A stairwell leading down"); break; case '.': pline(". Empty cave"); break; case '_': pline("_ Shallow water"); break; case ':': pline(": Water-filled cave"); break; case '0': pline("0 A magical portal"); break; case '#': pline("# A closed door"); break; case '`': pline("` An open door"); break; case '^': pline("^ A trap"); break; case '$': pline("$ Gold"); break; case '"': pline("\" A scarab"); break; case '~': pline("~ The Crown of Yendor"); break; case '%': pline("%% A map"); break; case '?': pline("? A scroll"); break; case '!': pline("! A potion"); break; case '/': pline("/ A wand"); break; case '=': pline("= A ring"); break; case ')': pline(") A weapon"); break; case '[': pline("[ Armour"); break; case 'l': pline("l A lizard"); break; case 'r': pline("r A rat"); break; case 'u': pline("u An urchin"); break; case 'C': pline("C A clay golem"); break; case '@': pline("@ You, or some other human"); break; default: pline("%c not known (you should never see it)",ch[0]); break; } } static void indicate_cursed_inv(void) { INVOBJ *io; int i; int nc; int nnc; OBJ *o; OBJTYPE *t; FILE *f; char *s; FILE *g; char *s2; int l2; int p2; f = fopenmalloc(&s,0); for (io=you->invent.inv;io;io=io->link) { nc = 0; nnc = 0; for (i=io->n-1;i>=0;i--) { o = io->v[i]; t = &objtypes[o->type]; if (t->ops->cursable && (*t->ops->iscursed)(o)) nc ++; else nnc ++; } if (nc < 1) continue; g = fopenmalloc(&s2,&l2); format_inv_letter(io->xwi,g); fclose(g); while ((l2 > 0) && ((s2[l2-1] == ' ') || (s2[l2-1] == ')'))) l2 --; p2 = 0; while ((p2 < l2) && (s2[p2] == ' ')) p2 ++; fprintf(f,"%.*s%s",l2-p2,s2+p2,(nnc<1)?"":"*"); free(s2); } fclose(f); pline("%s",s); } /* * The tick function for the player-avatar monster. Loop, interacting * with the player, until the player does something that consumes * time, upon which we return, to be called again when the avatar's * speed expires. */ static void you_tick(MONST *m) { XY d; int ch; int f; LEVEL *lv; LOC *lc; int i; JMP j; POST_REFRESH *pr; m->speed = (m->effflags & MEF_SPEED) ? m->type->info->basespeed / 2 : m->type->info->basespeed; if (m->effflags & MEF_SLEEP) { i = you->heal; you->heal = 50; time_healing(m); you->heal = i; area_damage(m); return; } time_healing(m); area_damage(m); while (setjmp(j.b)) pop_sigint_throw(); y.justmove = 0; while (1) { sanity_check(); i = interrupted(); if (i != y.last_interrupt) { y.last_interrupt = i; y.running = RUN_NONE; y.repcount = 0; y.prefix_clear = 1; clear(); continue; } lc = m->loc; lv = lc->on; if (!y.action || !(y.action_flags & CONT_NODISP)) { if (y.redo_see || (y.lastloc != lc)) { you_bemoved(you); } map_vis_reset(y.mvstate); if (you->effflags & MEF_MEMORY) { map_vis_memorize(y.mvstate); } else if (automap) { if (map_vis_auto(y.mvstate)) { m->speed /= MAP_PER_TICK; return; } } showmsgbuf(0); showdisp(); mvprintw(0,MXO,"%s",lc->on->longname); clrtoeol(); mvprintw(1,MXO,"HP:%d/%d",m->hp,m->maxhp); clrtoeol(); mvprintw(2,MXO,"Exp: %d",y.xp); clrtoeol(); mvprintw(3,MXO,"%lld.%08llx",(long long int)(curtime>>TIMESHIFT),(unsigned long long int)(curtime&(TIMESCALE-1))); clrtoeol(); mvprintw(4,MXO,"%d %d",m->magiconly,m->timeonly); clrtoeol(); if (y.prefix_clear) { y.prefix_n = 0; y.prefix_clear = 0; move(LINES-1,0); clrtoeol(); } if (y.prefix_n > 0) { move(LINES-1,0); addnstr(y.prefix,y.prefix_n); } move(lc->y+dispoy,lc->x+dispox); if (! debug_warp) refresh(); if (y.post_refresh) { while ((pr = y.post_refresh)) { y.post_refresh = pr->link; (*pr->cb)(pr->cbarg); free(pr); } continue; } } if (y.running != RUN_NONE) { if (run_stop()) { y.running = RUN_NONE; continue; } if (! move_you(y.run_d)) { y.running = RUN_NONE; continue; } if (y.runcount > 0) y.runcount --; if (! y.runcount) y.running = RUN_NONE; return; } if (y.action) { if ((*y.action)(y.action_arg)) return; continue; } if (y.repcount > 0) { ch = y.repcmd; y.repcount --; } else { push_sigint_throw(&j); ch = tget(); pop_sigint_throw(); } switch (ch) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': prefix_add(ch); continue; break; case '\7': y.prefix_n = 0; continue; break; case '\b': case '\177': if (y.prefix_n > 0) { y.prefix_n --; continue; } break; } y.prefix_clear = 1; if (dirkey(ch,0,0,&d,&f,0)) { if (f & DK_CAPS) { y.running = RUN_SHIFT; y.runcount = (y.prefix_n > 0) ? prefix_num() : -1; y.startat = you->loc; y.run_d = d; } else if (f & DK_CTL) { y.running = RUN_CTL; y.runcount = (y.prefix_n > 0) ? prefix_num() : -1; y.startat = you->loc; y.run_d = d; } else if (y.prefix_n > 0) { y.repcount = prefix_num(); y.repcmd = ch; } else { if (move_you(d)) return; } continue; } else { if (y.prefix_n > 0) { y.repcount = prefix_num(); y.repcmd = ch; continue; } if (search(you->loc,(you->effflags&MEF_SEARCHING)?SEARCH_CHANCE_RING:SEARCH_CHANCE_BASE)) { y.repcount = 0; resee(); } switch (ch) { case '.': return; break; case 's': if (search(you->loc,(you->effflags&MEF_SEARCHING)?SEARCH_CHANCE_ACTIVE_RING:SEARCH_CHANCE_ACTIVE)) { y.repcount = 0; resee(); } return; break; } y.repcount = 0; switch (ch) { case '\020': // ^P mh_show_hist(); break; case '\032': // ^Z suspend_game(); break; case ')': weapon_show(you); break; case '#': if (extended_command()) return; break; case '<': if (lc->cavetype != LOC_STAIRS_U) break; if (0) { case '>': if (lc->cavetype != LOC_STAIRS_D) break; } if (lc->to == 0) { if (! confirm("These are the stairs out of the dungeon. Take them anyway?")) continue; die(0); /* XXX leaving dungeon */ } else if (seestairs()) { continue; } else if (lc->to->monst) { mhitm(m,lc->to->monst); return; } else { if ( (lc->to->flags & LF_WATERFILL) && !(lc->flags & LF_WATERFILL) && !confirm("The stairs lead into water. Take them anyway?") ) continue; movemon(m,lc->to); return; } break; case '=': ring_show(you); break; case '[': armour_show(you); break; case '\\': knowledge_show(0); break; case '|': knowledge_show(1); break; case '?': help(); break; case '/': display_identify(); break; case 'D': { LOC *lc2; lc2 = getdir(lc); if (lc2 && (lc2->type == LOC_DOOR)) { if (lc2->walldoor & LOC_DOOR_OPEN) { closedoor(lc2); } else { opendoor(lc2); } return; } } break; case 'I': do_type_inv(); break; case 'M': if (! map) { pline("No map in use!"); } else { map_vis_force(y.mvstate); if (map_vis_start(y.mvstate)) { m->speed = 1; return; } } break; case 'O': options(); break; case 'P': if (do_put_on()) return; break; case 'Q': if (confirm("Quit? Really?")) { cleanupscreen(); exit(0); } break; case 'R': if (do_remove()) return; break; case 'S': do_save(); break; case 'T': if (do_take_off()) return; break; case 'W': if (do_wear()) return; break; case 'd': if (drop(0)) return; break; case 'i': sort_you_inv(); show_inventory(&you->invent,"",&invtest_all,"You're empty-handed!"); break; case 'm': y.justmove = ! y.justmove; break; case 'p': if (lc->objs.inv && pickup()) return; break; case 'q': if (do_quaff()) return; break; case 'r': if (do_read()) return; break; case 'w': if (do_wield()) return; break; case 'z': if (zap()) return; break; default: continue; break; case '~': push_sigint_throw(&j); ch = tget(); pop_sigint_throw(); switch (ch) { case '?': pline("ACDEHIMOPRSTV"); break; case 'A': aggravate(); break; case 'C': addfuse_rel(&makemon_fuse,1,0,0); pline("Created monster"); break; case 'D': y.debugmsg = ! y.debugmsg; pline("Debugging %s.",y.debugmsg?"on":"off"); break; case 'E': for (i=0;iinvent); o = obj_make(OBJ_SCROLL_OF_IDENTIFY); add_obj_to_inv(o,&you->invent); } } break; case 'F': indicate_cursed_inv(); break; case 'H': debug_phasing_toggle(); break; case 'I': do { INVOBJ *io; i = 0; for (io=you->invent.inv;io;io=io->link) { if (invtest_unidentified(io)) { identify_it(io,0); i = 1; break; } } } while (i); break; case 'O': off_pline(); break; case 'P': probe(); break; case 'R': pline("rings[0] = %p, rings[1] = %p",(void *)you->rings[0],(void *)you->rings[1]); break; case 'S': structure(); break; case 'T': you->baseflags ^= MBF_TELEPATHIC; pline("Telepathy %s",(you->baseflags&MBF_TELEPATHIC)?"on":"off"); break; case 'V': vulnerability_toggle(); break; case 'X': { OBJ *o; o = obj_make(OBJ_SCROLL_OF_TELEPORTATION); add_obj_to_inv(o,&you->invent); o = obj_make(OBJ_RING_OF_TELEPORT_CONTROL); add_obj_to_inv(o,&you->invent); } break; } continue; break; } } } } /* * Convert a secret door to a (closed) normal door. */ void finddoor(LOC *lc) { if (lc->type != LOC_SDOOR) panic("finddoor on non-sdoor"); lc->type = LOC_DOOR; switch (lc->walldoor) { case LOC_HWALL: lc->walldoor = LOC_CVDOOR; break; case LOC_VWALL: lc->walldoor = LOC_CHDOOR; break; default: pline("bad walldoor %d in finddoor?",lc->walldoor); impossible(); lc->type = LOC_CAVE; lc->cavetype = LOC_JUSTCAVE; break; } upddisp(lc); } /* * Make sure lc (which the caller must have checked is a door) is open. */ void opendoor(LOC *lc) { lc->walldoor &= ~LOC_DOOR_CLOSED; lc->walldoor |= LOC_DOOR_OPEN; if (lc->flags & LF_VISIBLE) resee(); upddisp(lc); } /* * Make sure lc (which the caller must have checked is a door) is * closed. */ void closedoor(LOC *lc) { lc->walldoor &= ~LOC_DOOR_OPEN; lc->walldoor |= LOC_DOOR_CLOSED; if (lc->flags & LF_VISIBLE) resee(); upddisp(lc); } /* * Set up a continuing action, or, if fn is nil, cancel any continuing * action. */ void set_continuing(int (*fn)(void *), void *arg, unsigned int flags) { y.action = fn; y.action_arg = arg; y.action_flags = flags; } /* * Give the player experience. */ void you_gain_exp(int xp) { y.xp += xp; you->maxhp = 12000 + (300 * log(y.xp)); } /* * Redo visibility entirely. This is used when something has changed * which could drastically change what cells are visible (such as * opening or closing a door). */ void resee(void) { y.redo_see = 1; } /* * The player avatar's name method. */ static const char *you_name(MONST *m __attribute__((__unused__))) { return("you"); } /* * The player avatar's Name method. */ static const char *you_Name(MONST *m __attribute__((__unused__))) { return("You"); } /* * The player avatar's destroy method. This should never be called; a * monster should never be destroyed without killing it first, and we * exit in you_kill(). */ static void you_destroy(MONST *m __attribute__((__unused__))) { panic("destroying you?"); } /* * inv_scan() test function to find the crown, if present. */ static int io_test_crown(INVOBJ *io) { return(io->v[0]->type==OBJ_CROWN); } /* * The player avatar has teleported (or been teleported). Both smart * and dumb tracks go cold here. */ void teleport_you(LOC *lc) { you->loc->tracks[TRACK_DUMB] = 0; you->loc->tracktime[TRACK_DUMB] = curtime; you->loc->tracks[TRACK_SMART] = 0; you->loc->tracktime[TRACK_SMART] = curtime; /* * The crown does not inhibit teleportation, but it is itself immune * to teleportation. Teleport while carrying it and the teleport * happens as normal, but the crown remains behind. */ if (you->baseflags & MBF_CROWN) { INVOBJ *io; io = inv_scan(&you->invent,&io_test_crown); if (! io) panic("MBF_CROWN but can't find crown"); stop_using(you,io); inv_move_1(&you->invent,io,&you->loc->objs); } movemon(you,lc); } /* * Called during startup. This sets up the player-avatar monster. */ void setup_you(void) { OBJ *o; int i; INVOBJ *io; if (entrance->monst) killmon(entrance->monst); you = newmon(MON_YOU,entrance->on); if (placemon(you,entrance)) panic("can't place you"); dispox = (LEV_X/2) - entrance->x; dispoy = (LEV_Y/2) - entrance->y; for (i=0;i<5;i++) { o = obj_make(OBJ_MAP); add_obj_to_inv(o,&you->invent); } io = add_obj_to_inv(obj_make(OBJ_DAGGER),&you->invent); identify_it(io,0); weapon_wield(you,io->v[0]); io = add_obj_to_inv(obj_make(OBJ_LEATHER_ARMOUR),&you->invent); identify_it(io,0); armour_wear(you,io->v[0]); map = obj_make(OBJ_MAP); mapox = 0; mapoy = 0; map_setlabel(map,entrance->on->longname); add_obj_to_inv(map,&you->invent); y.lastlevel = entrance->on; y.redo_see = 1; y.mvstate = map_vis_init(); } /* * The player avatar's base attack. */ static int you_attack(MONST *m __attribute__((__unused__))) { you_attacked ++; return(rnd(100+(10*log(y.xp)))); } /* * The player avatar's base defense. * * This is where debug invulnerability is implemented. */ static int you_defend(MONST *m __attribute__((__unused__))) { you_were_attacked ++; if (y.invulnerable) return(1000000000); return(rnd(100+(10*log(y.xp)))); } /* * The player avatar's base damage. */ static DAMAGE you_givedamage(MONST *m __attribute__((__unused__)), int dd __attribute__((__unused__))) { return(damage_simple(roll("d1000")+rnd((10*log(y.xp))+1),DK_ORDINARY,0)); } /* * The player avatar's kill method. */ static void you_kill(MONST *m __attribute__((__unused__))) { pline("You die"); showmsgbuf(1); die(0); } /* * The player avatar's ops vector. */ static MONOPS ops = { &you_tick, &you_bemoved, &you_name, &you_Name, &you_attack, &you_defend, &you_givedamage, &std_takedamage, &you_kill, &you_destroy }; /* * Create a new player-avatar monster. Of course, this should never be * called but the once, during startup. */ static MONST *new_you(MONST *m, LEVEL *lv __attribute__((__unused__))) { int i; int j; m->symbol = '@'; m->hp = 12000; m->maxhp = 12000; m->ops = &ops; m->priv = &y; y.xp = 1; y.plineoff = 0; y.pl_buf = malloc(COLS); y.pl_left0 = COLS - 2; y.pl_left = COLS - 2; y.pl_bp = y.pl_buf; y.invulnerable = 0; y.auto_map_switch = 1; y.running = RUN_NONE; y.runcount = -1; y.justmove = 0; y.prefix = 0; y.prefix_a = 0; y.prefix_n = 0; y.prefix_clear = 0; y.repcount = 0; y.last_interrupt = 0; y.debugmsg = 0; y.lastlevel = 0; y.lastloc = 0; y.mh_head = 0; y.mh_tail = 0; y.debug_phasing = 0; y.post_refresh = 0; y.redo_see = 0; for (i=MSGHISTSIZE-1;i>=0;i--) y.mh_text[i] = 0; for (i=PERCEPTION_RADIUS;i>=-PERCEPTION_RADIUS;i--) { for (j=PERCEPTION_RADIUS;j>=-PERCEPTION_RADIUS;j--) { percept_offlev[i+PERCEPTION_RADIUS][j+PERCEPTION_RADIUS] = ( ((i*i)+(j*j)) <= (PERCEPTION_RADIUS*PERCEPTION_RADIUS) ); } } offlev_perception = alloc_showdisp_offlevel(); return(m); } /* * Player-avatar monster ctl method. Nothing here. */ static void monctl_you(MONST *m __attribute__((__unused__)), int op, ...) { va_list ap; va_start(ap,op); switch (op) { } va_end(ap); } /* * The player-avatar MTINFO. */ #define basespeed_you TIMESCALE #define baseheal_you 12 #define exp_you 0 MTINFO mtinfo_you = MTINFO_INIT(you);