/* * The implementation of the player-avatar monster. */ #include #include #include #include #include #include "see.h" #include "obj.h" #include "mon.h" #include "vars.h" #include "util.h" #include "save.h" #include "dice.h" #include "time.h" #include "main.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 "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 "pickup-inv.h" #include "filldungeon.h" #include "mon-@-you.h" #define MSGHISTSIZE 256 /* * 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; typedef struct planedamage PLANEDAMAGE; typedef struct you YOU; typedef struct post_refresh POST_REFRESH; typedef struct pline_priv PLINE_PRIV; 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 { /* * 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; /* * Automatic mapping can be turned on or off. This is true if it's * currently on. */ int auto_mapping; /* * 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; /* * 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; } ; /* * Private data block for implementing plane-generated damage. */ struct planedamage { int level; int damaging; int fuseid; } ; /* * Player-avatar state (see above). */ static YOU y; /* * 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); } ; /* * 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--"); refresh(); push_sigint_throw(&j); getch(); 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) { addbytes(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); refresh(); getch(); 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. */ 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) { refresh(); push_sigint_throw(&j); x = getch(); 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; } } /* * Record what's visible on the current map. It is the caller's * responsibility to ensure there _is_ a current map. */ static void map_vis(void) { int x; int y; LOC *l; char c; if (! map) panic("invalid map_vis"); for (x=0;xloc->on->cells[x][0]; for (y=0;yflags & LF_VISIBLE) { c = loc_char(l,LC_ORDINARY); if (c != SYM_OOS) map_setcharat(map,x+mapox,y+mapoy,c); } l ++; } } } 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; } /* * 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; set_dumb_track(); clearflag(lv,LF_VISIBLE); seecell_loc = lc; see(lc,lv->visibility,LF_VISIBLE,&bemoved_see); if (y.auto_mapping && map) map_vis(); // map by memory here? see(lc,7,0,&set_smart_track); set_smart_level_track(); if (lc->objs.inv && (lc != y.lastloc)) { y.running = RUN_NONE; 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); if (! pickup_inv_report_one(pi)) add_post_refresh(&pickup_inv_report_list,pi); } } y.lastloc = lc; } } /* * Search. Currently, (a) searching always finds anything that's there * to find and (b) doesn't have anything to find but secret doors. */ static void search(void) { int i; LOC *lc; int found; found = 0; for (i=0;i<8;i++) { lc = movecell(you->loc,delta[i][0],delta[i][1],0); if (lc && (lc->type == LOC_SDOOR)) { finddoor(lc); found = 1; } } if (found) resee(); } /* * 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); addbytes(rbuf,rlen); addbytes(suffix,suflen); } else if (xoff < plen+rlen) { addbytes(rbuf+(xoff-plen),rlen-(xoff-plen)); addbytes(suffix,suflen); } else { addbytes(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); refresh(); push_sigint_throw(&j); c = getch(); 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); addbytes(prompt,plen); addbytes(&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); refresh(); push_sigint_throw(&j); c = getch(); 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); 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); 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); } io = pick_inventory(&you->invent,"Wield what (- for nothing)? ",&invtest_wieldable,&cb); 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); 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); 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); 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); 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); 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); 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 automatic mapping. */ static void auto_map_toggle(void) { y.auto_mapping = ! y.auto_mapping; pline("Automatic mapping turned %s",y.auto_mapping?"on":"off"); } /* * 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); getch(); 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); } /* * #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); 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); 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 command. */ static int xcmd_map(void) { static const XCMD cmds[] = { { "use", xcmd_map_use }, { "view", xcmd_map_view }, /*{ "name", xcmd_map_name },*/ { 0 } }; return(x_cmd("#map ",&cmds[0])); } /* * #drop command. */ static int xcmd_drop(void) { return(drop(1)); } 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); } /* * # command. */ static int extended_command(void) { static const XCMD cmds[] = { { "map", xcmd_map }, { "drop", xcmd_drop }, { "stat", xcmd_stat }, { "quit", xcmd_quit }, { 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 void plane_damage(long int liarg __attribute__((__unused__)), void *pdv) { PLANEDAMAGE *pd; int ok; const char *start; const char *ongoing; const char *stop; int damage; pd = pdv; if (you->loc->on->levelno != pd->level) { free(pd); return; } switch (pd->level) { case L_FIRE: ok = !! (you->flags & (MF_PLANE_F|MF_CROWN)); start = "You feel burning hot!"; ongoing = "You are burning!"; stop = "You no longer feel so burning hot!"; damage = rnd(1000) + 1; break; case L_WATER: ok = !! (you->flags & (MF_PLANE_W|MF_CROWN)); start = "You start to drown!"; ongoing = "You are drowning!"; stop = "You can now breathe comfortably!"; damage = (you->maxhp / 20) + rnd(1000) - rnd(1000); if (damage < 0) damage = 1; break; } pd->fuseid = addfuse_rel(&plane_damage,0,pd,TIMESCALE); if (pd->damaging) { if (ok) { pline("%s",stop); pd->damaging = 0; } else { pline("%s",ongoing); } } else { if (! ok) { pline("%s",start); pd->damaging = 1; } } if (! ok) (*you->ops->takedamage)(you,damage); } static void start_plane_damage(void) { PLANEDAMAGE *pd; pd = malloc(sizeof(PLANEDAMAGE)); pd->level = you->loc->on->levelno; pd->damaging = 0; plane_damage(0,pd); } /* * 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->flags & (MF_PLANE_A|MF_CROWN)) && (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->flags & (MF_PLANE_E|MF_CROWN)) { pline("You feel oddly comfortable here."); } else { pline("You feel very confined here."); } break; case L_AIR: if (you->flags & (MF_PLANE_A|MF_CROWN)) { 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->flags & (MF_PLANE_F|MF_CROWN)) { pline("You feel oddly comfortable here."); } start_plane_damage(); break; case L_WATER: if (you->flags & (MF_PLANE_W|MF_CROWN)) { pline("You feel oddly comfortable here."); } start_plane_damage(); 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->flags & MF_PHASING) || ( (lc2->type == LOC_DOOR) && (lc2->walldoor & LOC_DOOR_OPEN) ) || ( (lc2->on->levelno == L_EARTH) && (you->flags & (MF_PLANE_E|MF_CROWN)) ) ) { if (lc2->monst) { y.running = RUN_NONE; mhitm(you,lc2->monst); } else { movemon(you,lc2); if ( lc2->objs.inv || !( (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'; sprintf(&prompt[0],"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); } 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); getch(); 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(); refresh(); push_sigint_throw(&j); c = getch(); 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->flags |= MF_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."); } else { y.debug_phasing = effect_add(you,0,&effops_debug_phase); pline("Debug phasing on."); } } /* * 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; gainhp(m,m->heal); if (setjmp(j.b)) { pop_sigint_throw(); } y.justmove = 0; while (1) { 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.redo_see || (y.lastloc != lc)) you_bemoved(you); showmsgbuf(0); showdisp(); mvprintw(0,MXO,"%s",lc->on->longname); clrtoeol(); mvprintw(1,MXO,"HP:%d/%d",m->hp,m->maxhp); 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); 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.repcount > 0) { ch = y.repcmd; y.repcount --; } else { push_sigint_throw(&j); ch = getch(); 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; } switch (ch) { case '.': return; break; case 's': search(); return; break; } y.repcount = 0; switch (ch) { case '\020': mh_show_hist(); 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) { die(0); /* XXX leaving dungeon */ } else if (seestairs()) { continue; } else if (lc->to->monst) { mhitm(m,lc->to->monst); return; } else { movemon(m,lc->to); return; } break; case '=': ring_show(you); break; case '[': armour_show(you); 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) map_vis(); else pline("No map in use!"); 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 = getch(); 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 'H': debug_phasing_toggle(); break; case 'I': while (inv_scan(&you->invent,&invtest_unidentified)) { INVOBJ *io; for (io=you->invent.inv;io;io=io->link) { if (invtest_unidentified(io)) { identify_it(io,0); break; } } } break; case 'M': auto_map_toggle(); 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->flags ^= MF_TELEPATHIC; pline("Telepathy %s",(you->flags&MF_TELEPATHIC)?"on":"off"); break; case 'V': vulnerability_toggle(); break; case 'X': { OBJ *o; o = obj_make(OBJ_WAND_OF_DIGGING); 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); } /* * 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?"); } /* * 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; 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; } /* * The player avatar's base attack. */ static int you_attack(MONST *m __attribute__((__unused__))) { you_attacked ++; return(rnd(100)); } /* * 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)); } /* * The player avatar's base damage. */ static int you_givedamage(MONST *m __attribute__((__unused__)), int dd __attribute__((__unused__))) { return(roll("d1000")); } /* * 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; m->symbol = '@'; m->hp = 12000; m->maxhp = 12000; m->ops = &ops; m->priv = &y; 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.auto_mapping = 1; 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; 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 MTINFO mtinfo_you = MTINFO_INIT(you);