/* * 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 "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 "mon-@-you.h" #define MSGHISTSIZE 32 typedef struct planedamage PLANEDAMAGE; /* * Private data block for implementing plane-generated damage. */ struct planedamage { int level; int damaging; int fuseid; } ; /* * 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). */ static char *pl_buf; static int pl_left; static int pl_left0; static char *pl_bp; /* * One of the developer-support escapes turns pline() messages off or * back on again. This is true if they're currently off. */ static int plineoff = 0; /* * Automatic mapping can be turned on or off. This is true if it's * currently on. */ static int auto_mapping = 1; /* * 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.) */ static int invulnerable = 0; /* * One planned feature is mapping to memory rather than a map. This is * true if that's turned on. (At this writing it doesn't work.) */ static int memory_mapping = 0; /* * True if automatic map switching when changing levels is currently * turned on. */ static int auto_map_switch = 1; /* * 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). * running says which kind is currently in effect. */ typedef enum { RUN_NONE = 1, RUN_SHIFT, RUN_CTL, } RUNSTATE; static RUNSTATE running = RUN_NONE; /* * 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.) */ static int runcount = -1; /* * 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. */ static char *prefix = 0; static int prefix_a = 0; static int prefix_n = 0; static int prefix_clear = 0; /* * 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. */ static int repcount = 0; static int repcmd; /* * When running, run_d is the delta for each step and startat is the * location where the running started. */ static XY run_d; static LOC *startat; /* * last_interrupt is used (in conjunction with interrupted()) to detect * SIGINT and interrupt long-running actions in progress. */ static int last_interrupt = 0; /* * 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. */ static int debugmsg = 0; /* * 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. */ static LEVEL *lastlevel = 0; static LOC *lastloc = 0; /* * 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_initted * is used to initialize this system; mh_text holds message text, and * mh_head and mh_tail describe the head and tail of the ring buffer. */ static int mh_initted = 0; static char *mh_text[MSGHISTSIZE]; static int mh_head; static int mh_tail; /* * Permanemt phasing as a debug helper. If this is nil, it's off; if * not, it's on. */ static EFFECT *debug_phasing; /* * 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); } ; /* * Called once during startup to initialize this module. (This * arguably should be done during creation of the avatar monster.) */ void inityou(void) { pl_buf = malloc(COLS); pl_left0 = COLS - 2; pl_left = pl_left0; pl_bp = pl_buf; } /* * Start or extend mapping-by-memory. This is called by potions of * memory. */ void extend_memory_mapping(int n) { memory_mapping += n; } /* * Turn off pline() messages. Developer assist. */ static void off_pline(void) { int new; new = ! plineoff; plineoff = 0; pline("Messages %s",new?"off":"on"); plineoff = new; } /* * Generate a message to the player. This is used for most messages to * the player. The only thing of note here is probably the way * messages beginning with "You" are special-cased to never be * appended to another message. */ void pline(const char *fmt, ...) { va_list ap; int l; int o; static char *msg = 0; FILE *f; if (plineoff) return; free(msg); f = fopenmalloc(&msg); va_start(ap,fmt); vfprintf(f,fmt,ap); va_end(ap); fclose(f); l = strlen(msg); if ((l < pl_left-10) && strncmp(msg,"You",3)) { if (pl_bp != pl_buf) { *pl_bp++ = ' '; *pl_bp++ = ' '; pl_left -= 2; } bcopy(msg,pl_bp,l); pl_left -= l; pl_bp += l; return; } showmsgbuf(1); o = 0; while (l-o > pl_left0-10) { bcopy(msg+o,pl_buf,pl_left0-10); pl_bp = pl_buf + pl_left0-10; showmsgbuf(1); o += pl_left0 - 20; } bcopy(msg+o,pl_buf,l-o); pl_bp = pl_buf + (l-o); pl_left = pl_left0 - (l-o); } /* * Initialize message history state. mh_initted is used to make it * harmless to call this multiple times; it's called at the beginning * of message-history-using routines. */ static void mh_init(void) { int i; if (mh_initted) return; mh_initted = 1; mh_head = 0; mh_tail = 0; for (i=MSGHISTSIZE-1;i>=0;i--) mh_text[i] = 0; } /* * Save a line in message history. */ static void mh_save(const char *txt) { mh_init(); free(mh_text[mh_head]); mh_text[mh_head] = strdup(txt); mh_head = (mh_head + 1) % MSGHISTSIZE; if (mh_head == mh_tail) mh_tail = (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 (pl_bp == pl_buf) { move(MYO,0); clrtoeol(); return; } *pl_bp = '\0'; mvaddstr(MYO,0,pl_buf); mh_save(pl_buf); clrtoeol(); if (more) { addstr(" --More--"); refresh(); push_sigint_throw(&j); getch(); pop_sigint_throw(); } } pl_bp = pl_buf; pl_left = 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; if ( (lc2->type == LOC_GATE) || (lc2->type == LOC_DOOR) || (lc2->type == LOC_SDOOR) || (lc2->type == LOC_CAVE) ) { if (lc2->exitdist > j) { lc2->exitdist = j; t = newtrail(); t->link = pending; pending = t; t->loc = lc2; } } } if ( ( (lc->type == LOC_GATE) || ( (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) || ( (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) || ( (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 by memory. Not, obviously, implemented yet. */ static void map_vis_mem(void) { } /* * 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 ++; } } } /* * 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 != lastlevel) { clear(); cleardisp(); switched = 0; if (map && auto_map_switch) { if ((lastloc->type == LOC_CAVE) && (lastloc->to == lc)) { switch (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 = 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 (lastlevel) clearflag(lastlevel,LF_VISIBLE); if (lv->levelno < 0) { dispox = 0; dispoy = 0; } else if (lastlevel) { dispox += lastlevel->off.x - lv->off.x; dispoy += lastlevel->off.y - lv->off.y; } drawmapped(lv); lastlevel = lv; lastloc = 0; } if (lc != lastloc) { set_dumb_track(); clearflag(lv,LF_VISIBLE); seecell_loc = lc; see(lc,lv->visibility,LF_VISIBLE,&bemoved_see); if (auto_mapping && map) map_vis(); if (memory_mapping > 0) { map_vis_mem(); memory_mapping --; } see(lc,7,0,&set_smart_track); set_smart_level_track(); lastloc = lc; if (lc->objs.inv) pline("There's something here you could pick up."); } } /* * 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. */ 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); } 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); } /* * 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; } zap_it(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) { auto_mapping = ! auto_mapping; pline("Automatic mapping turned %s",auto_mapping?"on":"off"); } /* * Toggle (in)vulnerability. */ static void vulnerability_toggle(void) { invulnerable = ! invulnerable; pline("Invulnerability turned %s",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)) { 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)); } /* * # command. */ static int extended_command(void) { static const XCMD cmds[] = { { "map", xcmd_map }, { "drop", xcmd_drop }, { 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 { running = RUN_NONE; return(0); } if ( (abs(lc->x-lc2->x) > 1) || (abs(lc->y-lc2->y) > 1) ) { 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) { 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) { running = RUN_NONE; mhitm(you,lc2->monst); } else { movemon(you,lc2); if ( lc2->objs.inv || !( (lc2->type == LOC_CAVE) && (lc2->cavetype == LOC_JUSTCAVE) ) ) running = RUN_NONE; } } else { 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 && invobj_unidentified(io)) || (wantknown && !invobj_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); } } /* * Add a character to the prefix/prefix_a/prefix_n string. */ static void prefix_add(int ch) { if (prefix_n >= prefix_a) prefix = realloc(prefix,prefix_a=(prefix_n+8)); prefix[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(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 (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,run_d); l = move_by(you->loc,turn_l(run_d)); r = move_by(you->loc,turn_r(run_d)); l45 = move_by(you->loc,turn_l45(run_d)); r45 = move_by(you->loc,turn_r45(run_d)); if (! f) return(1); if (foo(f)) { if (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 (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 (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 (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 (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(startat,run_d); if (bar(f,of)) return(1); ol45 = move_by(startat,turn_l45(run_d)); if (bar(l45,ol45)) return(1); or45 = move_by(startat,turn_r45(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; } mh_init(); mvprintw(MYO,0,"%d %d",mh_head,mh_tail); getch(); if (mh_head == mh_tail) return; o = 1; while (1) { x = (mh_head + MSGHISTSIZE - o) % MSGHISTSIZE; mvprintw(MYO,0,"(%d) %s",o,mh_text[x]); clrtoeol(); refresh(); push_sigint_throw(&j); c = getch(); pop_sigint_throw(); switch (c) { case '\020': if (x == mh_tail) { beep(); } else { o ++; } break; case '\016': if (o < 2) { beep(); } else { o --; } break; default: repcmd = c; 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 (debug_phasing) { effect_remove(you,debug_phasing); pline("Debug phasing off."); } else { 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; gainhp(m,m->heal); if (setjmp(j.b)) { pop_sigint_throw(); } while (1) { i = interrupted(); if (i != last_interrupt) { last_interrupt = i; running = RUN_NONE; repcount = 0; prefix_clear = 1; clear(); continue; } lc = m->loc; lv = lc->on; if (lastloc == 0) 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(); mvprintw(3,MXO,"disp %d %d",dispox,dispoy); clrtoeol(); mvprintw(4,MXO,"at %d %d",lc->x,lc->y); clrtoeol(); mvprintw(5,MXO,"on %s",lc->on->shortname); clrtoeol(); if (prefix_clear) { prefix_n = 0; prefix_clear = 0; move(LINES-1,0); clrtoeol(); } if (prefix_n > 0) { move(LINES-1,0); addnstr(prefix,prefix_n); } move(lc->y+dispoy,lc->x+dispox); refresh(); if (running != RUN_NONE) { if (run_stop()) { running = RUN_NONE; continue; } if (! move_you(run_d)) { running = RUN_NONE; continue; } if (runcount > 0) runcount --; if (! runcount) running = RUN_NONE; return; } if (repcount > 0) { ch = repcmd; 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': prefix_n = 0; continue; break; case '\b': case '\177': if (prefix_n > 0) { prefix_n --; continue; } break; } prefix_clear = 1; if (dirkey(ch,0,0,&d,&f,0)) { if (f & DK_CAPS) { running = RUN_SHIFT; runcount = (prefix_n > 0) ? prefix_num() : -1; startat = you->loc; run_d = d; } else if (f & DK_CTL) { running = RUN_CTL; runcount = (prefix_n > 0) ? prefix_num() : -1; startat = you->loc; run_d = d; } else if (prefix_n > 0) { repcount = prefix_num(); repcmd = ch; } else { if (move_you(d)) return; } continue; } else { if (prefix_n > 0) { repcount = prefix_num(); repcmd = ch; continue; } switch (ch) { case '.': return; break; case 's': search(); return; break; } repcount = 0; switch (ch) { case '\020': mh_show_hist(); 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 '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': /* XXX ask for confirmation */ endwin(); exit(0); break; case 'R': if (do_remove()) return; 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); 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': debugmsg = ! debugmsg; pline("Debugging %s.",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,&invobj_unidentified)) { INVOBJ *io; for (io=you->invent.inv;io;io=io->link) { if (invobj_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; } 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) { lastloc = 0; } /* * 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; map = obj_make(OBJ_MAP); mapox = 0; mapoy = 0; map_setlabel(map,entrance->on->longname); add_obj_to_inv(map,&you->invent); lastlevel = entrance->on; lastloc = 0; 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]); } /* * The player avatar's attack rolls. XXX should handle weapons. */ static int you_attack(MONST *m __attribute__((__unused__))) { return(roll("d100")+weapon_hit_bonus(you->weapon)); } /* * The player avatar's defense rolls. * * This is where debug invulnerability is implemented. */ static int you_defend(MONST *m __attribute__((__unused__))) { if (invulnerable) return(1000000000); return(rnd(100)+armour_def_bonus(you->armour)); } /* * The player avatar's damage rolls. XXX should handle weapons. */ static int you_givedamage(MONST *m __attribute__((__unused__)), int dd __attribute__((__unused__))) { return(you->weapon?weapon_dmg_bonus(you->weapon):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__))) { m->symbol = '@'; m->hp = 12000; m->maxhp = 12000; m->ops = &ops; 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); } /* * Spawning chances for player-avatar monsters. These never spawn the * way most monsters do; the only one that ever should exist is the * one created in setup_you(), above. */ static PROBINIT probinit_you[] = { PROBALL(0), PROBEND() }; /* * The player-avatar MTINFO. */ #define basespeed_you TIMESCALE #define baseheal_you 12 MTINFO mtinfo_you = MTINFO_INIT(you);