/* * The implementation of the player-avatar monster. */ #include #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 "damage.h" #include "options.h" #include "display.h" #include "obj-map.h" #include "signals.h" #include "scrsyms.h" #include "montypes.h" #include "disputil.h" #include "objtypes.h" #include "obj-wand.h" #include "obj-ring.h" #include "obj-scroll.h" #include "obj-potion.h" #include "obj-armour.h" #include "obj-weapon.h" #include "stdio-util.h" #include "pickup-inv.h" #include "filldungeon.h" #include "mon-@-you.h" #define MSGHISTSIZE 256 #define PERCEPTION_RADIUS 6 #define MAP_PER_TICK 25 /* * There are three kinds of motion: not running (lowercase motion * commands), regular running (uppercase motion commands), and * run-until-near-something (control-modified motion commands). */ typedef enum { RUN_NONE = 1, RUN_SHIFT, RUN_CTL, } RUNSTATE; /* * Return values from map_vis_internal's second argument. */ typedef enum { MSR_ALREADY = 1, MSR_TOOKTIME, MSR_NOTIME, } MAPSETRV; typedef struct areadamage AREADAMAGE; 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 { /* * Player experience. */ int xp; /* * One of the developer-support escapes turns pline() messages off or * back on again. This is true if they're currently off. */ int plineoff; /* * pline()-generated messages may be doubled up - ie, more than one * message displayed on a single line - if they're short. pl_buf is * a buffer holding pending messages. pl_left is the space * remaining in pl_buf; pl_left0 is the total space available (it's * a variable to accommodate different numbers of columns). pl_bp * is a pointer to the current end of pl_buf, where another message * would go; that is, pl_bp = pl_buf + (pl_left0 - pl_left). */ char *pl_buf; int pl_left; int pl_left0; char *pl_bp; /* * Invulnerability can be turned on or off. (This is a developer * support feature and will be very limited or absent in * non-development versions of the game.) */ int invulnerable; /* * True if automatic map switching when changing levels is currently * turned on. */ int auto_map_switch; /* * running says which kind of running (see RUNSTATE, above) is * currently in effect. */ RUNSTATE running; /* * Running can be limited by entering a numeric prefix before the * running motion command. When this is done, runcount holds the * current remaining count. (When not, it holds -1.) */ int runcount; /* * Flag for implementing the m (move without pickup) command. */ int justmove; /* * When a numeric prefix is used, it is accumulated in prefix until * the affected command is entered. prefix_a and prefix_n are the * usual allocated and current lengths. prefix_clear is set when * any prefix in progress should be cleared. */ char *prefix; int prefix_a; int prefix_n; int prefix_clear; /* * When a numeric prefix is applied to a command other than running * motion, it operates as a repetition count. When this is done, * the current remaining count is kept in repcount and the command * being repeated is kept in repcmd, with repfn nil. */ int repcount; int repcmd; /* * Support for continuing-action commands, such as the potion of * clairvoyance. repfn is the implementation to be called next time * the player gets an action. In normal play, this is nil. */ int (*action)(void *); void *action_arg; unsigned int action_flags; /* * When running, run_d is the delta for each step and startat is the * location where the running started. */ XY run_d; LOC *startat; /* * last_interrupt is used (in conjunction with interrupted()) to * detect SIGINT and interrupt long-running actions in progress. */ int last_interrupt; /* * This controls certain debugging messages. Exactly what it * controls is not documented here; search for the variable to see * that. It changes too readily to keep it up to date here. */ int debugmsg; /* * lastlevel and lastloc are the level and location the player was * last on. These are used to notice player motion and trigger the * corresponding display actions. */ LEVEL *lastlevel; LOC *lastloc; /* * pline()d messages are kept in a history ring, so the player can * look back at historical messages (up to a limit, of course). * mh_text holds message text, with mh_head and mh_tail giving the * head and tail of the ring buffer. (The unit of the ring is the * message, not the character.) */ char *mh_text[MSGHISTSIZE]; int mh_head; int mh_tail; /* * Permanemt phasing as a debug helper. If this is nil, it's off; if * not, it's on. */ EFFECT *debug_phasing; /* * Things that should happen after the next screen refresh. */ POST_REFRESH *post_refresh; /* * Set to trigger a complete recomputation of visibility. */ int redo_see; /* * State for map_vis() and friends. */ void *mvstate; } ; /* * Private data block for implementing area-generated damage. This is * used for, eg, the Plane of Water. */ struct areadamage { int (*stop)(LOC *, void *); int (*damage)(void *); const char *startmsg; const char *ongoingmsg; const char *stopmsg; void *priv; 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); } ; /* * Data for MEF_PERCEPTION off-level flagging. */ static int offlev_perception; static unsigned char percept_offlev[(PERCEPTION_RADIUS*2)+1][(PERCEPTION_RADIUS*2)+1]; /* * Turn off pline() messages. Developer assist. */ static void off_pline(void) { int new; new = ! y.plineoff; y.plineoff = 0; pline("Messages %s",new?"off":"on"); y.plineoff = new; } /* * Generate a pline()-style message given buffer and length. The only * thing of note here is probably the way messages beginning with * "You" are special-cased to never be appended to another message. */ static void pline_string(const char *s, int l) { int o; if ((l < y.pl_left-10) && strncmp(s,"You",3)) { if (y.pl_bp != y.pl_buf) { *y.pl_bp++ = ' '; *y.pl_bp++ = ' '; y.pl_left -= 2; } bcopy(s,y.pl_bp,l); y.pl_left -= l; y.pl_bp += l; return; } showmsgbuf(1); o = 0; while (l-o > y.pl_left0-10) { bcopy(s+o,y.pl_buf,y.pl_left0-10); y.pl_bp = y.pl_buf + y.pl_left0-10; showmsgbuf(1); o += y.pl_left0 - 20; } bcopy(s+o,y.pl_buf,l-o); y.pl_bp = y.pl_buf + (l-o); y.pl_left = y.pl_left0 - (l-o); } /* * Generate a message to the player. This is how most messages to the * player get produced. */ void pline(const char *fmt, ...) { va_list ap; static char *msg = 0; int l; FILE *f; if (y.plineoff) return; free(msg); f = fopenmalloc(&msg,&l); va_start(ap,fmt); vfprintf(f,fmt,ap); va_end(ap); fclose(f); pline_string(msg,l); } /* * Write function for fopen_pline() streams. */ static int pline_w(void *ppv, const char *buf, int len) { PLINE_PRIV *pp; const void *nlv; int o; int n; pp = ppv; o = 0; while (o < len) { nlv = memchr(buf+o,'\n',len-o); if (! nlv) { if (pp->l+(len-o) > pp->a) { pp->a = pp->l + len - o; pp->b = realloc(pp->b,pp->a); } bcopy(buf+o,pp->b+pp->l,len-o); pp->l += len - o; break; } else { n = ((const char *)nlv) - (buf + o); if (pp->l) { if (pp->l+n > pp->a) { pp->a = pp->l + n; pp->b = realloc(pp->b,pp->a); } bcopy(buf+o,pp->b+pp->l,n); o += n + 1; n += pp->l; pline_string(pp->b,n); pp->l = 0; } else { pline_string(buf+o,n); o += n + 1; } } } return(len); } /* * Close function for fopen_pline() streams. */ static int pline_c(void *ppv) { PLINE_PRIV *pp; pp = ppv; if (pp->l) pline_w(ppv,"\n",1); free(pp->b); free(pp); return(0); } /* * Open a stream which turns each line of output into a pline()-style * message. */ FILE *fopen_pline(void) { PLINE_PRIV *pp; FILE *f; pp = malloc(sizeof(PLINE_PRIV)); if (! pp) return(0); f = funopen(pp,0,&pline_w,0,&pline_c); if (! f) { free(pp); return(0); } pp->b = 0; pp->a = 0; pp->l = 0; return(f); } /* * Save a line in message history. */ static void mh_save(const char *txt) { free(y.mh_text[y.mh_head]); y.mh_text[y.mh_head] = strdup(txt); y.mh_head = (y.mh_head + 1) % MSGHISTSIZE; if (y.mh_head == y.mh_tail) y.mh_tail = (y.mh_tail + 1) % MSGHISTSIZE; } /* * Show the buffer holding any pline()d messages. If more is true, * prompt the user and wait for an input character, as some * approximation to evidence that the user has read the message line * before erasing it to display another message. */ void showmsgbuf(int more) { JMP j; if (setjmp(j.b)) { pop_sigint_throw(); } else { if (y.pl_bp == y.pl_buf) { move(MYO,0); clrtoeol(); return; } *y.pl_bp = '\0'; mvaddstr(MYO,0,y.pl_buf); mh_save(y.pl_buf); clrtoeol(); if (more) { addstr(" --More--"); push_sigint_throw(&j); tget(); pop_sigint_throw(); } } y.pl_bp = y.pl_buf; y.pl_left = y.pl_left0; } /* * This is called when serious can't-happen tests trip, ones bad enough * that its not worth trying to continue. It reports the message, * displays it, waits for an input character, cleans up the display * and tty state, and then drops core (or at least tries to). */ void panic(const char *fmt, ...) { va_list ap; FILE *f; int wf(void *cookie __attribute__((__unused__)), const char *buf, int len) { 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); tget(); cleanupscreen(); debugsig(0); abort(); } /* * This is called when minor can't-happen tests trip. When one of * these trips, the inconsistency is one that can be ignored with at * least moderate safety. */ void impossible(void) { pline("Program in disorder, perhaps you'd better Quit"); } /* * Clear a flag bit on every cell in a level. This updates the * display, too, in case the flag bit is one affecting display. * * This is used, for example, to clear mark bits when flood-filling * areas of the level. */ static void clearflag(LEVEL *lv, int flg) { int x; int y; LOC *lc; for (x=0;xcells[x][0]; for (y=0;yflags & flg) { lc->flags &= ~flg; upddisp1(lc); } lc ++; } } } /* * Initialize the tracking systems. There are three tracks: dumb (just * the path the player took), smart (tries to cut off hairpin turns * and the like), and exit (used when monsters want to head towards * the dungeon exit). * * This just does a fairly straightforward flood-fill of the entire * dungeon for the exit track. Dumb and smart tracks are set as the * player moves around. * * XXX Should redo the exit track at least partially in response to * (possible) topology changes, such as wand of digging! */ void init_track(void) { int x; int y; int i; int j; LOC *lc; LOC *lc2; LEVEL *lv; TRAIL *pending; TRAIL *active; TRAIL *t; for (i=0;icells[x][0]; for (y=0;ytracks[j] = 0; lc->tracktime[j] = 0; } lc->exitdist = ~0U; lc ++; } } } pending = newtrail(); pending->link = 0; pending->loc = entrance; entrance->exitdist = 0; active = 0; while (1) { if (active == 0) { if (pending == 0) break; active = pending; pending = 0; continue; } t = active; active = t->link; lc = t->loc; freetrail(t); j = lc->exitdist + 1; for (i=0;i<8;i++) { lc2 = movecell(lc,delta[i][0],delta[i][1],0); if (! lc2) continue; switch (lc2->type) { default: break; case LOC_CAVE: case LOC_DOOR: case LOC_SDOOR: case LOC_GATE: if (lc2->exitdist > j) { lc2->exitdist = j; t = newtrail(); t->link = pending; pending = t; t->loc = lc2; } break; } } if ( ( ( (lc->type == LOC_GATE) && ((lc2=gate_other_end(lc,GH_REGULAR))) ) || ( (lc->type == LOC_CAVE) && ( (lc->cavetype == LOC_STAIRS_U) || (lc->cavetype == LOC_STAIRS_D) ) && ((lc2=lc->to)) ) ) && (lc2->exitdist > j) ) { lc2->exitdist = j; t = newtrail(); t->link = pending; pending = t; t->loc = lc2; } } } /* * Maintain the dumb track as the player moves. */ static void set_dumb_track(void) { LOC *lc; int i; LOC *lc2; lc = you->loc; for (i=0;i<8;i++) { lc2 = movecell(lc,delta[i][0],delta[i][1],0); if (! lc2) continue; if ( (lc2->type == LOC_GATE) || (lc2->type == LOC_DOOR) || (lc2->type == LOC_SDOOR) || (lc2->type == LOC_CAVE) ) { lc2->tracks[TRACK_DUMB] = lc; lc2->tracktime[TRACK_DUMB] = curtime; } } if ( ( (lc->type == LOC_GATE) && ((lc2=gate_other_end(lc,GH_REGULAR))) ) || ( (lc->type == LOC_CAVE) && ( (lc->cavetype == LOC_STAIRS_U) || (lc->cavetype == LOC_STAIRS_D) ) && ((lc2=lc->to)) ) ) { lc2->tracks[TRACK_DUMB] = lc; lc2->tracktime[TRACK_DUMB] = curtime; } } /* * Get a direction from the user. Any prompting or the like must have * already been displayed. If the user types something other than a * direction character, or if the direction would move off a * non-WRAPAROUND level, returns 0; otherwise, returns the moved-to * cell. (SIGINT produces a 0 return.) */ static LOC *getdir(LOC *from) { LEVEL *lv; int x; int y; JMP j; if (setjmp(j.b)) { pop_sigint_throw(); return(0); } lv = from->on; while (1) { push_sigint_throw(&j); x = tget(); pop_sigint_throw(); if (! dirkey(x,&x,&y,0,0,0)) return(0); x += from->x; y += from->y; if (lv->flags & LVF_WRAPAROUND) { if (x < 0) x += LEV_X; else if (x >= LEV_X) x -= LEV_X; if (y < 0) y += LEV_Y; else if (y >= LEV_Y) y -= LEV_Y; } else { if ((x < 0) || (y < 0) || (x >= LEV_X) || (y >= LEV_Y)) return(0); } return(&lv->cells[x][y]); } } /* * Update what the player can see. This is used after player motion, * hence the name. */ static void bemoved_see(LOC *lc) { if (lc->objs.inv && lc->objs.inv->link) { INVOBJ *o; INVOBJ **op; o = lc->objs.inv; lc->objs.inv = o->link; for (op=(&lc->objs.inv);*op;op=(&(*op)->link)) ; *op = o; o->link = 0; /* no upddisp needed: seecell below always calls upddisp */ } seecell(lc); } /* * Update the smart track for intra-level player motion. */ static void set_smart_track(LOC *lc) { int x; int y; x = you->loc->x - lc->x; y = you->loc->y - lc->y; if ((x == 0) && (y == 0)) return; if (lc->on->flags & LVF_WRAPAROUND) { if (x > LEV_X/2) x -= LEV_X; else if (x < -LEV_X/2) x += LEV_X; if (y > LEV_Y/2) y -= LEV_Y; else if (y < -LEV_Y/2) y += LEV_Y; } if (x < 0) x = -1; else if (x > 0) x = 1; if (y < 0) y = -1; else if (y > 0) y = 1; x += lc->x; y += lc->y; if (lc->on->flags & LVF_WRAPAROUND) { if (x < 0) x += LEV_X; else if (x >= LEV_X) x -= LEV_X; if (y < 0) y += LEV_Y; else if (y >= LEV_Y) y -= LEV_Y; } if ((x < 0) || (x >= LEV_X) || (y < 0) || (y >= LEV_Y)) { panic("out of range in set_smart_track"); } lc->tracks[TRACK_SMART] = &lc->on->cells[x][y]; lc->tracktime[TRACK_SMART] = curtime; } /* * Update the smart track for inter-level player motion. */ static void set_smart_level_track(void) { LOC *lc; LOC *lc2; lc = you->loc; if ( ( (lc->type == LOC_GATE) && ((lc2=gate_other_end(lc,GH_REGULAR))) ) || ( (lc->type == LOC_CAVE) && ( (lc->cavetype == LOC_STAIRS_U) || (lc->cavetype == LOC_STAIRS_D) ) && ((lc2=lc->to)) ) ) { lc2->tracks[TRACK_SMART] = lc; lc2->tracktime[TRACK_SMART] = curtime; } } static void add_post_refresh(void (*fn)(void *), void *arg) { POST_REFRESH *pr; pr = malloc(sizeof(POST_REFRESH)); pr->cb = fn; pr->cbarg = arg; pr->link = y.post_refresh; y.post_refresh = pr; } /* * Subsystem for mapping visible dungeon cells. * * map_vis_init allocates and sets up an MVSTATE, returning a pointer * to it, as void *. This must then be passed to the rest of the * functions. * * map_vis_reset resets the MVSTATE if the player has moved. * * map_vis_auto maps one remaining cell and returns nonzero, or, if * there are no remaining cells or if there is a monster next to the * player, does nothing and returns zero. * * map_vis_start starts an M command. * * map_vis_force causes the next map_vis_reset to act as though the * player has moved, even if it actually hasn't. * * map_vis_memorize maps everything remaining, if any, but does so by * setting LF_MEMORIZED on the LOCs instead of updating the map. */ typedef struct mvstate MVSTATE; typedef struct mvloc MVLOC; struct mvloc { MVLOC *link; int x; int y; int d2; } ; struct mvstate { int n; int x; int a; unsigned char (*v)[2]; LOC *l; } ; static MVLOC *freemvl = 0; static void mvlog_(int, const char *, ...) __attribute__((__format__(__printf__,2,3))); #define mvlog(fmt...) mvlog_(__LINE__,fmt) static void mvlog_(int line, const char *fmt, ...) { static FILE *f = 0; va_list ap; if (! f) f = fopen("mv.dbg","w"); fprintf(f,"%d: ",line); va_start(ap,fmt); vfprintf(f,fmt,ap); va_end(ap); fprintf(f,"\n"); fflush(f); } static MVLOC *newml(void) { MVLOC *m; if (freemvl) { m = freemvl; freemvl = m->link; } else { m = malloc(sizeof(MVLOC)); } return(m); } static void oldml(MVLOC *ml) { ml->link = freemvl; freemvl = ml; } static void *map_vis_init(void) { MVSTATE *s; mvlog("map_vis_init"); s = malloc(sizeof(MVSTATE)); s->n = 0; s->x = 0; s->a = 0; s->v = 0; s->l = 0; return(s); } static MVLOC *sort_mvloc_list(MVLOC *list) { MVLOC *a; MVLOC *b; MVLOC *t; MVLOC **lp; if (!list || !list->link) return(list); a = 0; b = 0; while (list) { t = list; list = t->link; t->link = a; a = b; b = t; } a = sort_mvloc_list(a); b = sort_mvloc_list(b); lp = &list; while (1) { if (a && (!b || ((a->d2 < b->d2) || ((a->d2 == b->d2) && onein(2))))) { t = a; a = a->link; } else if (b) { t = b; b = b->link; } else { *lp = 0; return(list); } *lp = t; lp = &t->link; } } static void map_vis_reset(void *sv) { int x; int y; MVSTATE *s; int n; LOC *l; MVLOC *list; MVLOC *ml; LOC *yl; s = sv; yl = you->loc; if (yl == s->l) return; s->l = yl; n = 0; list = 0; mvlog("map_vis_reset"); for (x=0;xloc->on->cells[x][0]; for (y=0;yflags & LF_VISIBLE) { ml = newml(); ml->x = x; ml->y = y; ml->d2 = ((x - yl->x) * (x - yl->x)) + ((y - yl->y) * (y - yl->y)); ml->link = list; list = ml; mvlog(" saved (%d,%d) %d",ml->x,ml->y,ml->d2); n ++; } l ++; } } if (n > s->a) { free(s->v); s->a = n; s->v = malloc(n*sizeof(*s->v)); } x = 0; list = sort_mvloc_list(list); mvlog("list:"); while (list) { ml = list; list = ml->link; if (x > n) abort(); mvlog(" (%d,%d) %d",ml->x,ml->y,ml->d2); s->v[x][0] = ml->x; s->v[x][1] = ml->y; x ++; oldml(ml); } if (x != n) abort(); s->n = n; s->x = 0; } static int map_vis_internal(MVSTATE *s, MAPSETRV (*set)(LOC *)) { LOC *l; int x; int y; while (1) { if (s->x >= s->n) return(0); x = s->v[s->x][0]; y = s->v[s->x][1]; s->x ++; l = &you->loc->on->cells[x][y]; if (l->flags & LF_MEMORIZED) { mvlog("internal: (%d,%d): memorized",x,y); } else if (! (l->flags & LF_VISIBLE)) { mvlog("internal: (%d,%d): not visible",x,y); } else { switch ((*set)(l)) { case MSR_ALREADY: mvlog("internal: (%d,%d): already done",x,y); break; case MSR_TOOKTIME: mvlog("internal: (%d,%d): updated",x,y); return(1); break; case MSR_NOTIME: mvlog("internal: (%d,%d): updated",x,y); break; default: abort(); break; } } } } static MAPSETRV map_vis_setmap(LOC *l) { return(map_setat(map,l->x+mapox,l->y+mapoy,loc_char(l,LC_ORDINARY),l)?MSR_TOOKTIME:MSR_ALREADY); } static MAPSETRV map_vis_setmem(LOC *l) { if (l->flags & LF_MEMORIZED) return(MSR_ALREADY); l->flags |= LF_MEMORIZED; return(MSR_NOTIME); } static void map_vis_memorize(void *sv) { while (map_vis_internal(sv,&map_vis_setmem)) ; } static int map_vis_auto(void *sv) { if (! map) return(0); if (monst_next_to(you->loc)) return(0); return(map_vis_internal(sv,&map_vis_setmap)); } static int map_vis_more(void *sv) { MVSTATE *s; s = sv; if (!map || (s->n < 1) || monst_next_to(you->loc) || !map_vis_internal(s,&map_vis_setmap)) { set_continuing(0,0,0); return(0); } set_continuing(&map_vis_more,sv,0); you->speed /= MAP_PER_TICK; return(1); } static int map_vis_start(void *sv) { MVSTATE *s; s = sv; if (s->n < 1) return(0); set_continuing(&map_vis_more,sv,0); return(1); } static void map_vis_force(void *sv) { if (sv) ((MVSTATE *)sv)->l = 0; } /* * The bemoved method for the player avatar. */ static void you_bemoved(MONST *m) { LOC *lc; LEVEL *lv; int switched; lc = m->loc; lv = lc->on; if (lv != y.lastlevel) { clear(); cleardisp(); switched = 0; if (map && y.auto_map_switch) { if ((y.lastloc->type == LOC_CAVE) && (y.lastloc->to == lc)) { switch (y.lastloc->cavetype) { case LOC_STAIRS_U: switched = map_auto_switch_up(lv->longname); break; case LOC_STAIRS_D: switched = map_auto_switch_dn(lv->longname); break; default: break; } } else { LOC *lc2; lc2 = y.lastloc->tracks[TRACK_SMART]; if (lc2 && (lc2->type == LOC_GATE) && (gate_other_end(lc2,GH_PLAYER)->on == lv)) { switched = map_auto_switch_gate(lc2,lv->longname); } } } if (! switched) map = 0; if (y.lastlevel) clearflag(y.lastlevel,LF_VISIBLE); if (lv->levelno < 0) { dispox = 0; dispoy = 0; } else if (y.lastlevel) { dispox += y.lastlevel->off.x - lv->off.x; dispoy += y.lastlevel->off.y - lv->off.y; } drawmapped(lv); y.lastlevel = lv; y.redo_see = 1; } if (y.redo_see || (lc != y.lastloc)) { y.redo_see = 0; map_vis_force(y.mvstate); set_dumb_track(); clearflag(lv,LF_VISIBLE); seecell_loc = lc; see(lc,lv->visibility,LF_VISIBLE,&bemoved_see,0); if (you->effflags & MEF_PERCEPTION) { see(lc,PERCEPTION_RADIUS,LF_VISIBLE,&bemoved_see,1); set_showdisp_offlevel(offlev_perception,(PERCEPTION_RADIUS*2)+1,(PERCEPTION_RADIUS*2)+1,&percept_offlev[0][0],lc->x-PERCEPTION_RADIUS,lc->y-PERCEPTION_RADIUS); } else { set_showdisp_offlevel(offlev_perception,0,0,0,lc->x,lc->y); } see(lc,7,0,&set_smart_track,0); set_smart_level_track(); if (lc->objs.inv && (lc != y.lastloc)) { 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. */ int search(LOC *l, unsigned int flags) { int i; LOC *a; int found; found = 0; for (i=0;i<8;i++) { a = movecell(l,delta[i][0],delta[i][1],0); if (a && (a->type == LOC_SDOOR) && ((flags & SEARCH_PERFECT) || 1/*rnd(something)*/)) { finddoor(a); found = 1; } } return(found); } /* * Get a line of input text. prompt is the prompt, of course. rbuf * and rblen describe the buffer for input text. ilen is amount of * input text already present. suffix is anything which should be * displayed after the input text until the user types a character. * xtc is a string whose characters should be treated as equivalent to * RETURN for purposes of this read. * * Returns the character the user typed which terminated the operation, * or ESC if SIGINT terminated it. rbuf will always have a * terminating NUL present after the input string. */ static int get_iline(const char *prompt, char *rbuf, int rblen, int ilen, const char *suffix, const char *xtc) { int rlen; int plen; int xoff; int c; int suflen; int cursx; JMP j; void fullredraw(void) { move(LINES-1,0); if (xoff > 0) addch('<'); else addch(' '); if (xoff < plen) { addstr(prompt+xoff); 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); push_sigint_throw(&j); c = tget(); pop_sigint_throw(); if ((c == '\177') || (c == '\b')) { if (rlen > 0) { rlen --; if (cursx) { move(LINES-1,cursx-1); addch(' '); } } } else if (c == '\33') { rlen = 0; break; } else if (c == '\f') { fullredraw(); } else if ((c == '\r') || (c == '\n') || index(xtc,c)) { break; } else if ((c >= ' ') && (c <= '~')) { if (rlen < rblen-1) { addch(c); rbuf[rlen++] = c; } else { beep(); } } if (suflen) { suffix = ""; suflen = 0; fullredraw(); } } rbuf[rlen] = '\0'; move(LINES-1,0); clrtoeol(); return(c); } /* * Get a number from the user. If the user just enters *, starval is * the value to return. */ static int get_n(const char *prompt, int starval) { int plen; int n; int n2; char nbuf[64]; int nblen; int c; JMP j; void fullredraw(void) { move(LINES-1,0); 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); push_sigint_throw(&j); c = tget(); pop_sigint_throw(); if ((c == '\177') || (c == '\b')) { if (nblen > 0) { nblen --; move(LINES-1,plen+nblen); addch(' '); n /= 10; } } else if (c == '\33') { n = -1; break; } else if (c == '\f') { fullredraw(); } else if ((c == '\r') || (c == '\n')) { if (nblen > 0) break; beep(); } else if ((c >= '0') && (c <= '9')) { if ((nblen == 0) || (n == 0)) { nbuf[0] = c; nblen = 1; n = c - '0'; move(LINES-1,plen); addch(c); } else { n2 = (n * 10) + (c - '0'); if (n2/10 != n) { beep(); } else { addch(c); nbuf[nblen++] = c; n = n2; } } } else if ((c == '*') && (nblen == 0)) { n = starval; break; } else { beep(); } } move(LINES-1,0); clrtoeol(); return(n); } /* * Compare two INVOBJs. This is used to keep the player's inventory * sorted. */ static int you_inv_cmp(INVOBJ *io1, INVOBJ *io2) { OBJTYPE *ot1; OBJTYPE *ot2; char *ix1; char *ix2; const char order[] = "$~\")[=/?!%"; ot1 = &objtypes[io1->v[0]->type]; ot2 = &objtypes[io2->v[0]->type]; if (ot1->sym != ot2->sym) { ix1 = index(&order[0],ot1->sym); ix2 = index(&order[0],ot2->sym); if (ix1 && ix2) return(ix1-ix2); if (! ix1) pline("unknown symbol %c in you_inv_cmp",ot1->sym); if (! ix2) pline("unknown symbol %c in you_inv_cmp",ot2->sym); impossible(); } if (ot1->class != ot2->class) return(ot1->class-ot2->class); switch (ot1->class) { /*case OC_ARMOUR: if (armour_worn(io1) && !armour_worn(io2)) return(-1); if (armour_worn(io2) && !armour_worn(io1)) return(1); break; case OC_WEAPON: if (weapon_wielded(io1) && !weapon_wielded(io2)) return(-1); if (weapon_wielded(io2) && !weapon_wielded(io1)) return(1); break;*/ case OC_MAP: if (io1->v[0] == map) return(-1); if (io2->v[0] == map) return(1); if (map_blank(io1->v[0])) { if (! map_blank(io2->v[0])) return(1); } else { if (map_blank(io2->v[0])) return(-1); } break; case OC_SCROLL: case OC_POTION: case OC_WAND: case OC_RING: if ((ot1->flags & ~ot2->flags) & OTF_KNOWN) return(-1); if ((ot2->flags & ~ot1->flags) & OTF_KNOWN) return(1); if (ot1->called && !ot2->called) return(-1); if (ot2->called && !ot1->called) return(1); break; } return(io1->xwi-io2->xwi); } /* * Sort the player's inventory */ static void sort_you_inv(void) { you->invent.inv = sort_inv(you->invent.inv,&you_inv_cmp); } /* * Drop something. If mult is true, drop (potentially) more than one * thing. */ static int drop(int mult) { INVOBJ *io; int n; int rv; if (! you->invent.inv) { pline("You're not carrying anything!"); return(0); } sort_you_inv(); rv = 0; do { showmsgbuf(1); io = pick_inventory(&you->invent,"Drop what?",&invtest_all,0,"drop"); if (io == 0) break; if (io->dispn > 1) { n = get_n("Drop how many? ",io->dispn); if (n < 1) { pline("Aborted."); continue; } if (n < io->dispn) io = inventory_split_n(io,n,-1); } stop_using(you,io); inv_move_1(&you->invent,io,&you->loc->objs); pline("Dropped."); rv = 1; } while (mult); return(rv); } /* * Inventory filter function selecting things which can be read. */ static int invtest_readable(INVOBJ *io) { return(objtypes[io->v[0]->type].class == OC_SCROLL); } /* * Read command. */ static int do_read(void) { INVOBJ *io; INVOBJ *n; if (! test_inventory(&you->invent,&invtest_readable)) { pline("You don't have anything to read!"); return(0); } sort_you_inv(); io = pick_inventory(&you->invent,"Read what?",&invtest_readable,0,"read"); if (io == 0) return(0); if (io->dispn > 1) { n = inventory_split_n(io,1,find_xwi(&you->invent)); if (n) io = n; } read_it(io); return(1); } /* * Inventory filter function selecting things which can be wielded * (weapons not currently wielded). */ static int invtest_wieldable(INVOBJ *io) { return( (objtypes[io->v[0]->type].class == OC_WEAPON) && !weapon_being_wielded(io->v[0]) ); } /* * Wield-a-weapon command. */ static int do_wield(void) { INVOBJ *io; INVOBJ *n; int nothing; int cb(char ks) { if (ks != '-') return(0); nothing = 1; return(1); } sort_you_inv(); if (!you->weapon && !test_inventory(&you->invent,&invtest_wieldable)) { pline("You have nothing to wield!"); return(0); } io = pick_inventory(&you->invent,"Wield what (- for nothing)?",&invtest_wieldable,&cb,"wield"); if (io == 0) { if (nothing) { weapon_wield(you,0); return(1); } return(0); } if (io->dispn > 1) { n = inventory_split_n(io,1,find_xwi(&you->invent)); if (n) io = n; } weapon_wield(you,io->v[0]); pline_invobj(io); return(1); } /* * Inventory filter function selecting things which can be put on * (rings not being worn). */ static int invtest_puttable(INVOBJ *io) { return( (objtypes[io->v[0]->type].class == OC_RING) && !ring_being_worn(io->v[0]) ); } /* * Put on a ring command. */ #if MAXRINGS != 2 #error "do_put_on() assumes MAXRINGS is 2" #endif static int do_put_on(void) { INVOBJ *io; if (! test_inventory(&you->invent,&invtest_puttable)) { pline("You don't have anything%s you can wear!",you->rings[0]?" more":""); return(0); } if (you->rings[MAXRINGS-1]) { pline("You're already wearing as many rings as you can!"); return(0); } sort_you_inv(); io = pick_inventory(&you->invent,"Put on what?",&invtest_puttable,0,"put on"); if (io == 0) return(0); if (io->dispn > 1) panic("plural ring"); ring_put(you,io->v[0]); pline_invobj(io); return(1); } /* * Inventory filter function selecting things which can be removed * (rings currently being worn). */ static int invtest_removable(INVOBJ *io) { return( (objtypes[io->v[0]->type].class == OC_RING) && ring_being_worn(io->v[0]) ); } /* * Remove a ring command. */ #if MAXRINGS != 2 #error "do_remove() assumes MAXRINGS is 2" #endif static int do_remove(void) { INVOBJ *io; if (! you->rings[0]) { pline("You're not wearing any rings!"); return(0); } sort_you_inv(); io = pick_inventory(&you->invent,"Remove what?",&invtest_removable,0,"remove"); if (io == 0) return(0); if (io->dispn > 1) panic("plural ring"); ring_remove(you,io->v[0]); return(1); } /* * Save-game command. */ static void do_save(void) { int c; char sfb[2048]; c = get_iline("Save to: ",&sfb[0],sizeof(sfb),0,"",""); if (c == '\33') { pline("Aborted."); return; } savegame_save(&sfb[0]); } /* * Inventory filter function selecting things which can be worn (armour * not being worn). */ static int invtest_wearable(INVOBJ *io) { return( (objtypes[io->v[0]->type].class == OC_ARMOUR) && !armour_being_worn(io->v[0]) ); } /* * Wear armour command. */ static int do_wear(void) { INVOBJ *io; if (! test_inventory(&you->invent,&invtest_wearable)) { pline("You don't have anything to wear!"); return(0); } if (you->armour) { pline("You're already wearing armour!"); return(0); } sort_you_inv(); io = pick_inventory(&you->invent,"Wear what?",&invtest_wearable,0,"wear"); if (io == 0) return(0); if (io->dispn > 1) panic("plural armour"); armour_wear(you,io->v[0]); pline_invobj(io); return(1); } /* * Take off armour command. */ static int do_take_off(void) { INVOBJ *ao; int scan(INVOBJ *io) { return(io->v[0]==you->armour); } if (! you->armour) { pline("You aren't wearing any armour!"); return(0); } ao = inv_scan(&you->invent,&scan); if (! ao) panic("can't find armour in inventory"); armour_take_off(you,you->armour); pline_invobj(ao); return(1); } /* * Inventory filter function selecting things which can be quaffed. */ static int invtest_quaffable(INVOBJ *io) { return(objtypes[io->v[0]->type].class == OC_POTION); } /* * Quaff command. */ static int do_quaff(void) { INVOBJ *io; INVOBJ *n; if (! test_inventory(&you->invent,&invtest_quaffable)) { pline("You're not carrying anything you can quaff!"); return(0); } sort_you_inv(); io = pick_inventory(&you->invent,"Quaff what?",&invtest_quaffable,0,"quaff"); if (io == 0) return(0); if (io->dispn > 1) { n = inventory_split_n(io,1,find_xwi(&you->invent)); if (n) io = n; } quaff_it(io); return(1); } /* * Inventory filter function selecting things which can be zapped. */ static int invtest_zappable(INVOBJ *io) { return(objtypes[io->v[0]->type].class == OC_WAND); } /* * Zap command. */ static int zap(void) { INVOBJ *io; INVOBJ *n; if (! test_inventory(&you->invent,&invtest_zappable)) { pline("You're not carrying anything you can zap!"); return(0); } sort_you_inv(); io = pick_inventory(&you->invent,"Zap what?",&invtest_zappable,0,"zap"); if (io == 0) return(0); if (io->dispn > 1) { n = inventory_split_n(io,1,find_xwi(&you->invent)); if (n) io = n; } wand_zap(io); return(1); } /* * Pick something up. */ static int pickup(void) { INVOBJ *io; int n; int rv; rv = 0; while (you->loc->objs.inv) { showmsgbuf(1); io = pick_inventory(&you->loc->objs,"Pick up what?",&invtest_all,0,"pick up"); if (io == 0) break; if (io->dispn > 1) { n = get_n("Pick up how many? ",io->dispn); if (n < 1) continue; if (n < io->dispn) io = inventory_split_n(io,n,-1); } io = inv_move_1(&you->loc->objs,io,&you->invent); pline_invobj(io); rv = 1; } return(rv); } /* * Toggle (in)vulnerability. */ static void vulnerability_toggle(void) { y.invulnerable = ! y.invulnerable; pline("Invulnerability turned %s",y.invulnerable?"on":"off"); } /* * Prompt for, accept, and execute an extened command. */ static int x_cmd(const char *prompt, const XCMD *cmdtab) { char cmdbuf[32]; int i; int l; int p; int c; JMP j; l = 0; if (setjmp(j.b)) { pop_sigint_throw(); c = '\33'; } else { c = get_iline(prompt,&cmdbuf[0],sizeof(cmdbuf),l,""," ?"); } while (1) { l = strlen(&cmdbuf[0]); if (c == '?') { int x; int cl; void morebreak(const char *s) { addstr(s); push_sigint_throw(&j); tget(); pop_sigint_throw(); move(LINES-2,0); clrtoeol(); } move(LINES-2,0); clrtoeol(); x = 0; for (i=0;cmdtab[i].cmd;i++) { cl = strlen(cmdtab[i].cmd); if ((cl < l) || strncmp(cmdtab[i].cmd,&cmdbuf[0],l)) continue; if (x && (x+1+cl >= COLS-10)) { morebreak(" --More--"); x = 0; } if (x) { addch(' '); x ++; } addstr(cmdtab[i].cmd); x += cl; } morebreak(" --End--"); c = get_iline(prompt,&cmdbuf[0],sizeof(cmdbuf),l,""," ?"); continue; } move(LINES-1,0); clrtoeol(); if (! cmdbuf[0]) return(0); p = -1; for (i=0;cmdtab[i].cmd;i++) { if (!strcmp(&cmdbuf[0],cmdtab[i].cmd)) return((*cmdtab[i].fn)()); if (!strncmp(&cmdbuf[0],cmdtab[i].cmd,l)) { if (p == -1) p = i; else p = -2; } } if (p >= 0) return((*cmdtab[p].fn)()); c = get_iline(prompt,&cmdbuf[0],sizeof(cmdbuf),l,(p==-1)?" [no match]":" [ambiguous]"," ?"); } } /* * Inventory filter function selecting maps. */ static int invtest_map(INVOBJ *io) { return(io->v[0]->type == OBJ_MAP); } /* * #map use command. */ static int xcmd_map_use(void) { INVOBJ *io; OBJ *m; int ox; int oy; OBJ *msave; int rv; if (! test_inventory(&you->invent,&invtest_map)) { pline("You don't have any maps to use!"); return(0); } sort_you_inv(); io = pick_inventory(&you->invent,"Use which map?",&invtest_map,0,"use"); if (io == 0) return(0); m = io->v[0]; if (map_blank(m)) { if (io->dispn > 1) io = inventory_split_n(io,1,find_xwi(&you->invent)); map = io->v[0]; mapox = 0; mapoy = 0; pline("This map is blank."); return(1); } msave = map; map = 0; cleardisp(); drawmapped(you->loc->on); map = msave; if (place_map(m,&ox,&oy,"Position the map...")) { map = m; mapox = ox; mapoy = oy; pline("Done."); rv = 1; } else { pline("Aborted."); rv = 0; } cleardisp(); drawmapped(you->loc->on); return(rv); } /* * #map view command. */ static int xcmd_map_view(void) { INVOBJ *io; OBJ *m; if (! test_inventory(&you->invent,&invtest_map)) { pline("You don't have any maps to view!"); return(0); } sort_you_inv(); io = pick_inventory(&you->invent,"View which map?",&invtest_map,0,"view"); if (io == 0) return(0); m = io->v[0]; if (map_blank(m)) { pline("This map is blank."); return(0); } view_map(m); return(1); } /* * #map auto on command. */ static int xcmd_map_auto_on(void) { automap = 1; return(1); } /* * #map auto off command. */ static int xcmd_map_auto_off(void) { automap = 0; return(1); } /* * #map auto command. */ static int xcmd_map_auto(void) { static const XCMD cmds[] = { { "on", &xcmd_map_auto_on }, { "off", &xcmd_map_auto_off }, { 0 } }; return(x_cmd("#map auto ",&cmds[0])); } /* * #map command. */ static int xcmd_map(void) { static const XCMD cmds[] = { { "use", &xcmd_map_use }, { "view", &xcmd_map_view }, { "auto", &xcmd_map_auto }, { 0 } }; return(x_cmd("#map ",&cmds[0])); } /* * #inv pack command. */ static int xcmd_inv_pack(void) { INVOBJ *io; int n; sort_you_inv(); n = 1; for (io=you->invent.inv;io;io=io->link) if (io->xwi) io->xwi = n++; pline("Done."); return(1); } /* * #inv command. */ static int xcmd_inv(void) { static const XCMD cmds[] = { { "pack", &xcmd_inv_pack }, { 0 } }; return(x_cmd("#inv ",&cmds[0])); } /* * #drop command. */ static int xcmd_drop(void) { return(drop(1)); } /* * #seed command. */ static int xcmd_seed(void) { pline("Seed: %llu",get_dice_seed()); return(0); } /* * #stat command. This is debugging assist. */ static int xcmd_stat(void) { void list_bonuses(const char *tag, BONUSROLL *list) { FILE *f; if (! list) { pline("no %s bonuses",tag); return; } f = fopen_pline(); fprintf(f,"%s bonuses:",tag); for (;list;list=list->flink) fprintf(f," %s",list->roll); fclose(f); } list_bonuses("defense",you->bonus_def); list_bonuses("to-hit",you->bonus_hit); list_bonuses("damage",you->bonus_dmg); return(0); } /* * #quit command. */ static int xcmd_quit(void) { if (confirm("Quit? Really?")) { endwin(); exit(0); } return(0); } /* * # command. */ static int extended_command(void) { static const XCMD cmds[] = { { "map", &xcmd_map }, { "drop", &xcmd_drop }, { "seed", &xcmd_seed }, { "stat", &xcmd_stat }, { "quit", &xcmd_quit }, { "inv", &xcmd_inv }, { 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 area_damage(long int liarg __attribute__((__unused__)), void *adv) { AREADAMAGE *ad; int damage; DAMAGE d; ad = adv; if ((*ad->stop)(you->loc,ad->priv)) { free(ad); return; } damage = (*ad->damage)(ad->priv); ad->fuseid = addfuse_rel(&area_damage,0,ad,TIMESCALE); if (ad->damaging) { if (damage == 0) { pline("%s",ad->stopmsg); ad->damaging = 0; } else { pline("%s",ad->ongoingmsg); } } else { if (damage) { pline("%s",ad->startmsg); ad->damaging = 1; } } if (damage) { d = damage_simple(damage,DK_ORDINARY,0); (*you->ops->takedamage)(you,d); damage_free(d); } } static int plane_damage_stop(LOC *l, void *pv) { if (l->on->levelno == *(int *)pv) return(0); free(pv); return(1); } static int burning_damage(void *pv) { (void)pv; if ( (you->baseflags & MBF_CROWN) || (you->effflags & (MEF_PLANE_F|MEF_RESIST_FIRE)) ) return(0); return(rnd(1000)+1); } static int drowning_damage(void *pv) { int d; (void)pv; if ( (you->baseflags & MBF_CROWN) || (you->effflags & (MEF_PLANE_W|MEF_BREATHE_WATER)) ) return(0); d = (you->maxhp / 20) + rnd(1000) - rnd(1000); if (d < 1) d = 1; return(d); } static void start_plane_damage(void) { AREADAMAGE *ad; ad = malloc(sizeof(AREADAMAGE)); ad->stop = &plane_damage_stop; switch (you->loc->on->levelno) { case L_FIRE: ad->damage = &burning_damage; ad->startmsg = "You feel burning hot!"; ad->ongoingmsg = "You are burning!"; ad->stopmsg = "You no longer feel so burning hot!"; break; case L_WATER: ad->damage = &drowning_damage; ad->startmsg = "You start to drown!"; ad->ongoingmsg = "You are drowning!"; ad->stopmsg = "You can now breathe comfortably!"; break; } ad->priv = malloc(sizeof(int)); *(int *)ad->priv = you->loc->on->levelno; ad->damaging = 0; area_damage(0,ad); } /* * Move the player by a delta. Also handles walking into gates. * * Returns true if the move succeeded, false if not. */ static int move_you(XY d) { LOC *lc; LOC *lc2; lc = you->loc; lc2 = move_by(lc,d); if (! lc2) // can happen with phasing { y.running = RUN_NONE; return(0); } if ( (abs(lc->x-lc2->x) > 1) || (abs(lc->y-lc2->y) > 1) ) { y.running = RUN_NONE; } if ( (lc2->on->levelno == L_AIR) && !(you->baseflags & MBF_CROWN) && !(you->effflags & MEF_PLANE_A) && (lc2->type != LOC_GATE) ) { pline("You can't seem to figure out how to move!"); return(0); } if (lc2->type == LOC_GATE) { y.running = RUN_NONE; lc->tracks[TRACK_DUMB] = lc2; lc->tracktime[TRACK_DUMB] = curtime; lc2->tracks[TRACK_DUMB] = 0; lc2->tracktime[TRACK_DUMB] = curtime; lc->tracks[TRACK_SMART] = lc2; lc->tracktime[TRACK_SMART] = curtime; lc2->tracks[TRACK_SMART] = 0; lc2->tracktime[TRACK_SMART] = curtime; gatemon(you,gate_other_end(lc2,GH_PLAYER)); switch (you->loc->on->levelno) { case L_EARTH: if ( (you->baseflags & MBF_CROWN) || (you->effflags & MEF_PLANE_E) ) { pline("You feel oddly comfortable here."); } else { pline("You feel very confined here."); } break; case L_AIR: if ( (you->baseflags & MBF_CROWN) || (you->effflags & MEF_PLANE_A) ) { pline("You feel oddly comfortable here."); } else { pline("You are floating in midair and can't see how to move."); } break; case L_FIRE: if ( (you->baseflags & MBF_CROWN) || (you->effflags & MEF_PLANE_F) ) { pline("You feel oddly comfortable here."); } start_plane_damage(); break; case L_WATER: if ( (you->baseflags & MBF_CROWN) || (you->effflags & MEF_PLANE_W) ) { 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->effflags & MEF_PHASING) || ( (lc2->type == LOC_DOOR) && (lc2->walldoor & LOC_DOOR_OPEN) ) || ( (lc2->on->levelno == L_EARTH) && ( (you->baseflags & MBF_CROWN) || (you->effflags & MEF_PLANE_E) ) ) ) { if (lc2->monst) { y.running = RUN_NONE; mhitm(you,lc2->monst); } else { movemon(you,lc2); if ( lc2->objs.inv || !( (lc2->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); tget(); if (y.mh_head == y.mh_tail) return; o = 1; while (1) { x = (y.mh_head + MSGHISTSIZE - o) % MSGHISTSIZE; mvprintw(MYO,0,"(%d) %s",o,y.mh_text[x]); clrtoeol(); push_sigint_throw(&j); c = tget(); pop_sigint_throw(); switch (c) { case '\020': if (x == y.mh_tail) { beep(); } else { o ++; } break; case '\016': if (o < 2) { beep(); } else { o --; } break; default: y.repcmd = c; y.repcount = 1; /* fall through */ case '\33': case '\r': case '\n': case '\7': move(MYO,0); clrtoeol(); return; break; } } } /* * The debug phasing effect. */ static void apply_debug_phase(MONST *m, void *arg __attribute__((__unused__))) { if (m != you) panic("debug phasing on mon-you"); m->effflags |= MEF_PHASING; } static void death_debug_phase(MONST *m __attribute__((__unused__)), void *arg __attribute__((__unused__))) { } static EFFECTOPS effops_debug_phase = EFFECTOPS_INIT(debug_phase); /* * Toggle debug phasing. */ static void debug_phasing_toggle(void) { if (y.debug_phasing) { effect_remove(you,y.debug_phasing); pline("Debug phasing off."); y.debug_phasing = 0; } else { y.debug_phasing = effect_add(you,0,&effops_debug_phase); pline("Debug phasing on."); } } /* * Tell the player what's known about identifiable items (eg, rings). * * This code knows that all the types it cares about - potions, * scrolls, rings, and wands - use the OTF_KNOWN flag to indicate the * flavour of identifiedness it cares about. */ #define NCLASSES 4 static void knowledge_show(void) { int namelens[OBJ__N]; int count[NCLASSES]; int nlines[NCLASSES]; int cwidths[NCLASSES][OBJ__N]; int types[NCLASSES][OBJ__N]; static const int classes[] = { OC_POTION, OC_SCROLL, OC_RING, OC_WAND }; static const char * const names[] = { "potions", "scrolls", "rings", "wands" }; static const char * const Names[] = { "Potions", "Scrolls", "Rings", "Wands" }; static int state; int x; int i; int j; int k; int col; int totw; int w; int maxw; DLLS line(FILE *f) { int tx; int ntx; const char *name; int c; switch (state) { case 0: // about to generate "%s you know:" line; x is class index if (count[x] == 0) { fprintf(f,"You know no %s",names[x]); trace_text("You know no %s\n",names[x]); state = 1; x --; return(DL_L_MORE); } fprintf(f,"%s you know:",Names[x]); trace_text("%s you know\n",Names[x]); state = 2; i = 0; return(DL_L_MORE); break; case 1: // about to generate blank line; x is next class index if (x < 0) return(DL_L_DONE); state = 0; trace_text("blank line, x=%d (%s)\n",x,names[x]); return(DL_L_MORE); break; case 2: // generating list of types; x is class index, i is line # trace_text("line, x=%d i=%d nlines[x]=%d count[x]=%d\n",x,i,nlines[x],count[x]); fprintf(f," "); for (tx=count[x]-1-i,c=0;tx>=0;tx=ntx,c++) { ntx = tx - nlines[x]; name = objtypes[types[x][tx]].name; trace_text("loop, tx=%d c=%d ntx=%d types[x][tx]=%d cwidths[x][c]=%d name=%s\n",tx,c,ntx,types[x][tx],cwidths[x][c],name); if (ntx >= 0) { fprintf(f,"%-*s",cwidths[x][c]+2,name); } else { fprintf(f,"%s",name); } } trace_text("loop exit, i=%d\n",i); i ++; if (i >= nlines[x]) { state = 1; x --; trace_text("now in state 1, x=%d\n",x); } return(DL_L_MORE); break; } panic("impossible state %d when showing knowledge",state); } trace_text("knowledge_show: ==== new call ====\n"); if ((sizeof(classes)/sizeof(classes[0])) != NCLASSES) panic("NCLASSES wrong"); for (i=NCLASSES-1;i>=0;i--) count[i] = 0; for (i=OBJ__N-1;i>=0;i--) namelens[i] = objtypes[i].name ? strlen(objtypes[i].name) : 0; for (x=OBJ__N-1;x>=0;x--) { for (i=NCLASSES-1;i>=0;i--) { if (objtypes[x].class == classes[i]) { if (objtypes[x].flags & OTF_KNOWN) { types[i][count[i]++] = x; break; } } } } for (x=NCLASSES-1;x>=0;x--) { trace_text("class %d (%s): count %d\n",x,names[x],count[x]); if (count[x] < 1) continue; j = 1; while (j < count[x]) { trace_text("loop, trying j=%d\n",j); totw = -2; maxw = 0; k = 0; col = 0; for (i=count[x]-1;;i--) { trace_text(" loop, i=%d, k=%d\n",i,k); if ((k >= j) || (i < 0)) { trace_text(" setting cwidths[%d][%d]=%d\n",x,col,maxw); cwidths[x][col++] = maxw; totw += 2 + maxw; trace_text(" totw=%d\n",totw); maxw = 0; k = 0; } if (i < 0) break; w = namelens[types[x][i]]; if (w > maxw) maxw = w; trace_text(" w=%d maxw=%d (name=%s)\n",w,maxw,objtypes[types[x][i]].name); k ++; } trace_text(" loop done, totw %d\n",totw); if (totw <= COLS-6) break; j ++; } trace_text("loop done, j %d\n",j); nlines[x] = j; } x = NCLASSES - 1; state = 0; display_list(&line,"",&dl_justmore); } /* * Pick a location, eg for controlled teleport. Display is driven off * maps. Returns the picked location, or nil if the operation was * canceled. */ LOC *pick_location(const char *prompt) { int ox; int oy; int x; int y; int dx; int dy; int px; int py; int fulldisp; int f; OBJ *m; OBJ *m2; LEVEL *lv; LOC *rv; JMP j; int c; INVOBJ *mio; unsigned char fa[LEV_X][LEV_Y]; char ca[LEV_X][LEV_Y]; rv = 0; do <"done"> { if (setjmp(j.b)) { pop_sigint_throw(); break <"done">; } fulldisp = 1; lv = you->loc->on; m = map; px = you->loc->x; py = you->loc->y; while (1) { while <"reading"> (1) { if (! m) { mio = pick_inventory(&you->invent,"What map?",&invtest_nbmap,0,"look at"); if (! mio) { if (confirm("Abort?")) break <"done">; continue; } m = mio->v[0]; } if (fulldisp) { fulldisp = 0; clear(); ox = (map_maxx(m) + map_minx(m) - LEV_X) / 2; oy = (map_maxy(m) + map_miny(m) - LEV_Y) / 2; } if (map_label(m)) mvprintw(0,MXO,"%s",map_label(m)); mvprintw(LINES-2,0,"%s",prompt); mvprintw(LINES-1,0,"(Move, or ESC to abort)"); for (x=LEV_X-1;x>=0;x--) for (y=LEV_Y-1;y>=0;y--) { fa[x][y] = 0; ca[x][y] = SYM_OOS; } for (x=LEV_X-1;x>=0;x--) for (y=LEV_Y-1;y>=0;y--) ca[x][y] = map_charat(m,x+ox,y+oy); showdisp2(&fa[0],&ca[0],0,0,0); move(py-oy,px-ox); push_sigint_throw(&j); c = tget(); pop_sigint_throw(); if (dirkey(c,&dx,&dy,0,&f,DK_NOCTL)) { if (f & DK_CAPS) { dx *= 8; dy *= 4; } px += dx; py += dy; ox += dx; oy += dy; continue; } switch (c) { case '\7': case '\33': break <"reading">; case '\r': case '\n': rv = map_locat(m,px,py); break <"done">; case '<': m2 = map_auto_link_up(m,&ox,&oy); if (0) { case '>': m2 = map_auto_link_dn(m,&ox,&oy); } if (m2) { m = m2; fulldisp = 1; } break; } beep(); } if (confirm("Abort?")) break <"done">; } } while (0); clear(); return(rv); } /* * Stop water area damage. */ static int water_area_stop(LOC *l, void *pv) { (void)pv; if (l->flags & LF_WATER) return(0); return(1); } /* * Start water area damage. */ static void start_water_damage(void) { AREADAMAGE *ad; ad = malloc(sizeof(AREADAMAGE)); ad->stop = &water_area_stop; ad->damage = &drowning_damage; ad->startmsg = "You start to drown!"; ad->ongoingmsg = "You are drowning!"; ad->stopmsg = "You can now breathe comfortably!"; ad->priv = 0; ad->damaging = 0; area_damage(0,ad); } static void map_sanity_check(void) { INVOBJ *io; int n; int i; for (io=you->invent.inv;io;io=io->link) { n = 0; for (i=io->n-1;i>=0;i--) n += io->v[i]->number; if (n != io->dispn) panic("inventory inconsistency"); } } /* * The tick function for the player-avatar monster. Loop, interacting * with the player, until the player does something that consumes * time, upon which we return, to be called again when the avatar's * speed expires. */ static void you_tick(MONST *m) { XY d; int ch; int f; LEVEL *lv; LOC *lc; int i; JMP j; POST_REFRESH *pr; m->speed = (m->effflags & MEF_SPEED) ? m->type->info->basespeed / 2 : m->type->info->basespeed; time_healing(m); while (setjmp(j.b)) pop_sigint_throw(); y.justmove = 0; while (1) { map_sanity_check(); i = interrupted(); if (i != y.last_interrupt) { y.last_interrupt = i; y.running = RUN_NONE; y.repcount = 0; y.prefix_clear = 1; clear(); continue; } lc = m->loc; lv = lc->on; if (!y.action || !(y.action_flags & CONT_NODISP)) { if (y.redo_see || (y.lastloc != lc)) { you_bemoved(you); } map_vis_reset(y.mvstate); if (you->effflags & MEF_MEMORY) { map_vis_memorize(y.mvstate); } else if (automap) { if (map_vis_auto(y.mvstate)) { m->speed /= MAP_PER_TICK; return; } } showmsgbuf(0); showdisp(); mvprintw(0,MXO,"%s",lc->on->longname); clrtoeol(); mvprintw(1,MXO,"HP:%d/%d",m->hp,m->maxhp); clrtoeol(); mvprintw(2,MXO,"Exp: %d",y.xp); clrtoeol(); mvprintw(3,MXO,"%lld.%08llx",(long long int)(curtime>>TIMESHIFT),(unsigned long long int)(curtime&(TIMESCALE-1))); clrtoeol(); mvprintw(4,MXO,"%d %d",m->magiconly,m->timeonly); clrtoeol(); if (y.prefix_clear) { y.prefix_n = 0; y.prefix_clear = 0; move(LINES-1,0); clrtoeol(); } if (y.prefix_n > 0) { move(LINES-1,0); addnstr(y.prefix,y.prefix_n); } move(lc->y+dispoy,lc->x+dispox); refresh(); if (y.post_refresh) { while ((pr = y.post_refresh)) { y.post_refresh = pr->link; (*pr->cb)(pr->cbarg); free(pr); } continue; } } if (y.running != RUN_NONE) { if (run_stop()) { y.running = RUN_NONE; continue; } if (! move_you(y.run_d)) { y.running = RUN_NONE; continue; } if (y.runcount > 0) y.runcount --; if (! y.runcount) y.running = RUN_NONE; return; } if (y.action) { if ((*y.action)(y.action_arg)) return; continue; } if (y.repcount > 0) { ch = y.repcmd; y.repcount --; } else { push_sigint_throw(&j); ch = tget(); pop_sigint_throw(); } switch (ch) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': prefix_add(ch); continue; break; case '\7': y.prefix_n = 0; continue; break; case '\b': case '\177': if (y.prefix_n > 0) { y.prefix_n --; continue; } break; } y.prefix_clear = 1; if (dirkey(ch,0,0,&d,&f,0)) { if (f & DK_CAPS) { y.running = RUN_SHIFT; y.runcount = (y.prefix_n > 0) ? prefix_num() : -1; y.startat = you->loc; y.run_d = d; } else if (f & DK_CTL) { y.running = RUN_CTL; y.runcount = (y.prefix_n > 0) ? prefix_num() : -1; y.startat = you->loc; y.run_d = d; } else if (y.prefix_n > 0) { y.repcount = prefix_num(); y.repcmd = ch; } else { if (move_you(d)) return; } continue; } else { if (y.prefix_n > 0) { y.repcount = prefix_num(); y.repcmd = ch; continue; } switch (ch) { case '.': return; break; case 's': if (search(you->loc,0)) resee(); 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 { if ((lc->to->flags & LF_WATER) && !(lc->flags & LF_WATER)) { if (! confirm("The stairs lead into water. Take them anyway?")) continue; movemon(m,lc->to); start_water_damage(); } else { movemon(m,lc->to); } return; } break; case '=': ring_show(you); break; case '[': armour_show(you); break; case '\\': knowledge_show(); break; case 'D': { LOC *lc2; lc2 = getdir(lc); if (lc2 && (lc2->type == LOC_DOOR)) { if (lc2->walldoor & LOC_DOOR_OPEN) { closedoor(lc2); } else { opendoor(lc2); } return; } } break; case 'I': do_type_inv(); break; case 'M': if (! map) { pline("No map in use!"); } else { if (map_vis_start(y.mvstate)) { m->speed = 1; return; } } break; case 'O': options(); break; case 'P': if (do_put_on()) return; break; case 'Q': if (confirm("Quit? Really?")) { cleanupscreen(); exit(0); } break; case 'R': if (do_remove()) return; break; case 'S': do_save(); break; case 'T': if (do_take_off()) return; break; case 'W': if (do_wear()) return; break; case 'd': if (drop(0)) return; break; case 'i': sort_you_inv(); show_inventory(&you->invent,"",&invtest_all,"You're empty-handed!"); break; case 'm': y.justmove = ! y.justmove; break; case 'p': if (lc->objs.inv && pickup()) return; break; case 'q': if (do_quaff()) return; break; case 'r': if (do_read()) return; break; case 'w': if (do_wield()) return; break; case 'z': if (zap()) return; break; default: continue; break; case '~': push_sigint_throw(&j); ch = tget(); pop_sigint_throw(); switch (ch) { case '?': pline("ACDEHIMOPRSTV"); break; case 'A': aggravate(); break; case 'C': addfuse_rel(&makemon_fuse,1,0,0); pline("Created monster"); break; case 'D': y.debugmsg = ! y.debugmsg; pline("Debugging %s.",y.debugmsg?"on":"off"); break; case 'E': for (i=0;iinvent); o = obj_make(OBJ_SCROLL_OF_IDENTIFY); add_obj_to_inv(o,&you->invent); } } break; case 'H': debug_phasing_toggle(); break; case 'I': do { INVOBJ *io; i = 0; for (io=you->invent.inv;io;io=io->link) { if (invtest_unidentified(io)) { identify_it(io,0); i = 1; break; } } } while (i); break; case 'O': off_pline(); break; case 'P': probe(); break; case 'R': pline("rings[0] = %p, rings[1] = %p",(void *)you->rings[0],(void *)you->rings[1]); break; case 'S': structure(); break; case 'T': you->baseflags ^= MBF_TELEPATHIC; pline("Telepathy %s",(you->baseflags&MBF_TELEPATHIC)?"on":"off"); break; case 'V': vulnerability_toggle(); break; case 'X': { OBJ *o; o = obj_make(OBJ_SCROLL_OF_TELEPORTATION); add_obj_to_inv(o,&you->invent); o = obj_make(OBJ_RING_OF_TELEPORT_CONTROL); add_obj_to_inv(o,&you->invent); } break; } continue; break; } } } } /* * Convert a secret door to a (closed) normal door. */ void finddoor(LOC *lc) { if (lc->type != LOC_SDOOR) panic("finddoor on non-sdoor"); lc->type = LOC_DOOR; switch (lc->walldoor) { case LOC_HWALL: lc->walldoor = LOC_CVDOOR; break; case LOC_VWALL: lc->walldoor = LOC_CHDOOR; break; default: pline("bad walldoor %d in finddoor?",lc->walldoor); impossible(); lc->type = LOC_CAVE; lc->cavetype = LOC_JUSTCAVE; break; } upddisp(lc); } /* * Make sure lc (which the caller must have checked is a door) is open. */ void opendoor(LOC *lc) { lc->walldoor &= ~LOC_DOOR_CLOSED; lc->walldoor |= LOC_DOOR_OPEN; if (lc->flags & LF_VISIBLE) resee(); upddisp(lc); } /* * Make sure lc (which the caller must have checked is a door) is * closed. */ void closedoor(LOC *lc) { lc->walldoor &= ~LOC_DOOR_OPEN; lc->walldoor |= LOC_DOOR_CLOSED; if (lc->flags & LF_VISIBLE) resee(); upddisp(lc); } /* * Set up a continuing action, or, if fn is nil, cancel any continuing * action. */ void set_continuing(int (*fn)(void *), void *arg, unsigned int flags) { y.action = fn; y.action_arg = arg; y.action_flags = flags; } /* * Give the player experience. */ void you_gain_exp(int xp) { y.xp += xp; you->maxhp = 12000 + (150 * log(y.xp)); } /* * Redo visibility entirely. This is used when something has changed * which could drastically change what cells are visible (such as * opening or closing a door). */ void resee(void) { y.redo_see = 1; } /* * The player avatar's name method. */ static const char *you_name(MONST *m __attribute__((__unused__))) { return("you"); } /* * The player avatar's Name method. */ static const char *you_Name(MONST *m __attribute__((__unused__))) { return("You"); } /* * The player avatar's destroy method. This should never be called; a * monster should never be destroyed without killing it first, and we * exit in you_kill(). */ static void you_destroy(MONST *m __attribute__((__unused__))) { panic("destroying you?"); } /* * 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; y.mvstate = map_vis_init(); } /* * The player avatar's base attack. */ static int you_attack(MONST *m __attribute__((__unused__))) { you_attacked ++; return(rnd(100+log(y.xp))); } /* * The player avatar's base defense. * * This is where debug invulnerability is implemented. */ static int you_defend(MONST *m __attribute__((__unused__))) { you_were_attacked ++; if (y.invulnerable) return(1000000000); return(rnd(100+log(y.xp))); } /* * The player avatar's base damage. */ static DAMAGE you_givedamage(MONST *m __attribute__((__unused__)), int dd __attribute__((__unused__))) { return(damage_simple(roll("d1000")+rnd(log(y.xp)+1),DK_ORDINARY,0)); } /* * The player avatar's kill method. */ static void you_kill(MONST *m __attribute__((__unused__))) { pline("You die"); showmsgbuf(1); die(0); } /* * The player avatar's ops vector. */ static MONOPS ops = { &you_tick, &you_bemoved, &you_name, &you_Name, &you_attack, &you_defend, &you_givedamage, &std_takedamage, &you_kill, &you_destroy }; /* * Create a new player-avatar monster. Of course, this should never be * called but the once, during startup. */ static MONST *new_you(MONST *m, LEVEL *lv __attribute__((__unused__))) { int i; int j; m->symbol = '@'; m->hp = 12000; m->maxhp = 12000; m->ops = &ops; m->priv = &y; y.xp = 1; y.plineoff = 0; y.pl_buf = malloc(COLS); y.pl_left0 = COLS - 2; y.pl_left = COLS - 2; y.pl_bp = y.pl_buf; y.invulnerable = 0; y.auto_map_switch = 1; y.running = RUN_NONE; y.runcount = -1; y.justmove = 0; y.prefix = 0; y.prefix_a = 0; y.prefix_n = 0; y.prefix_clear = 0; y.repcount = 0; y.last_interrupt = 0; y.debugmsg = 0; y.lastlevel = 0; y.lastloc = 0; y.mh_head = 0; y.mh_tail = 0; y.debug_phasing = 0; y.post_refresh = 0; y.redo_see = 0; for (i=MSGHISTSIZE-1;i>=0;i--) y.mh_text[i] = 0; for (i=PERCEPTION_RADIUS;i>=-PERCEPTION_RADIUS;i--) { for (j=PERCEPTION_RADIUS;j>=-PERCEPTION_RADIUS;j--) { percept_offlev[i+PERCEPTION_RADIUS][j+PERCEPTION_RADIUS] = ( ((i*i)+(j*j)) <= (PERCEPTION_RADIUS*PERCEPTION_RADIUS) ); } } offlev_perception = alloc_showdisp_offlevel(); return(m); } /* * Player-avatar monster ctl method. Nothing here. */ static void monctl_you(MONST *m __attribute__((__unused__)), int op, ...) { va_list ap; va_start(ap,op); switch (op) { } va_end(ap); } /* * The player-avatar MTINFO. */ #define basespeed_you TIMESCALE #define baseheal_you 12 #define exp_you 0 MTINFO mtinfo_you = MTINFO_INIT(you);