/* * Implementation of wands. */ #include #include #include #include "obj.h" #include "mon.h" #include "vars.h" #include "dice.h" #include "util.h" #include "pline.h" #include "debug.h" #include "format.h" #include "display.h" #include "signals.h" #include "structs.h" #include "obj-map.h" #include "scrsyms.h" #include "objtypes.h" #include "mon-@-you.h" #include "digdungeon.h" #include "obj-wand.h" typedef struct wand WAND; typedef struct wandtpriv WANDTPRIV; /* * Type-private data for wands. This contains the bits parsed out from * the type's name4 string. */ struct wandtpriv { char *init_charges; char *charging_charges; int max_safe; int expl_percent; char *expl_damage; } ; /* * Private data for wands. */ struct wand { WAND *flink; WAND *blink; unsigned int flags; #define WF_KNOWN 0x00000001 int charges; int type; } ; /* * List of all wands. */ static WAND *allwands = 0; /* * Symbol used by some wand ray functions. */ static char wand_ray_sym; /* * Ray function for wand of striking. */ static int striking_cell(LOC *lc, int n) { MONST *m; if (lc->flags & LF_VISIBLE) { mvaddch(lc->y+dispoy,lc->x+dispox,'*'); move(you->loc->y+dispoy,you->loc->x+dispox); refresh(); } m = lc->monst; if (m) { int dd; int vis; vis = monst_cansee_monst(you,m); if (roll("3d100") < (*m->ops->defend)(m)) { if (vis) pline("The wand %smisses %s!",(dd<-10)?"":"barely ",(*m->ops->name)(m)); } else { if (vis) pline("The wand hits %s!",(*m->ops->name)(m)); if ((*m->ops->takedamage)(m,roll("8d2000")).flags & TDRV_DIED) { if (vis) pline("%s dies!",(*m->ops->Name)(m)); } } return(1); } else if ((n > 15) && onein(5)) { return(1); } else { if ( (lc->type == LOC_DOOR) && ((lc->walldoor & (LOC_DOOR_CLOSED|LOC_DOOR_OPEN)) == LOC_DOOR_CLOSED) && !rnd(10) ) { if (lc->flags & LF_VISIBLE) pline("The door is blasted apart!"); lc->type = LOC_CAVE; lc->cavetype = LOC_JUSTCAVE; resee(); return(1); } return(!occupiable(lc)); } } /* * Ray function for wand of make invisible. */ static int make_invisible_cell(LOC *lc, int n) { MONST *m; m = lc->monst; if (m) { m->baseflags |= MBF_INVISIBLE; upddisp1(lc); return(1); } else if ((n > 15) && onein(5)) { return(1); } else { return(!occupiable(lc)); } } /* * Ray function for wand of magic missle. */ static int magic_missle_cell(LOC *lc, int n) { MONST *m; if (lc->flags & LF_VISIBLE) { mvaddch(lc->y+dispoy,lc->x+dispox,wand_ray_sym); move(you->loc->y+dispoy,you->loc->x+dispox); refresh(); } m = lc->monst; if (m) { int vis; vis = monst_cansee_monst(you,m); if (vis) pline("The missles hit %s!",(*m->ops->name)(m)); if ((*m->ops->takedamage)(m,roll("6d4000")).flags & TDRV_DIED) { if (vis) pline("%s dies!",(*m->ops->Name)(m)); } return(1); } else if ((n > 15) && onein(5)) { return(1); } else { return(!occupiable(lc)); } } /* * Ray function for wand of digging. */ static int digging_cell(LOC *lc, int n) { if (lc->flags & LF_VISIBLE) { mvaddch(lc->y+dispoy,lc->x+dispox,' '); move(you->loc->y+dispoy,you->loc->x+dispox); refresh(); } switch (lc->type) { default: panic("impossible cell type %d",(int)lc->type); break; case LOC_ROCK: case LOC_WALL: case LOC_SDOOR: digcell_(lc->on,lc->x,lc->y); if (map) { int dx; int dy; for (dx=-1;dx<=1;dx++) for (dy=-1;dy<=1;dy++) map_setcharat(map,lc->x+dx,lc->y+dy,SYM_OOS); } resee(); return(1); break; case LOC_CAVE: return(n>LEV_X); break; case LOC_GATE: return(1); break; case LOC_DOOR: if (lc->flags & LF_VISIBLE) pline("The door is blasted apart!"); lc->type = LOC_CAVE; lc->cavetype = LOC_JUSTCAVE; resee(); return(1); } } /* * Return the WAND * for an OBJ *. */ static WAND *wand_for_obj(OBJ *o) { if (objtypes[o->type].class != OC_WAND) panic("non-wand where wand needed"); return(o->private); } /* * Return a wand type's private struct, creating it if necessary. */ static WANDTPRIV *wand_type_priv(int type) { OBJTYPE *ot; WANDTPRIV *p; char *s; char *comma; ot = &objtypes[type]; if (ot->class != OC_WAND) panic("non-wand to wand_type_priv"); if (! ot->priv) { p = malloc(sizeof(WANDTPRIV)+strlen(ot->name4)+1); s = (void *)(p+1); strcpy(s,ot->name4); comma = index(s,','); if (! comma) panic("no comma 1 in wand name4"); *comma++ = '\0'; p->init_charges = s; s = comma; comma = index(s,','); if (! comma) panic("no comma 2 in wand name4"); *comma++ = '\0'; p->charging_charges = s; s = comma; comma = index(s,','); if (! comma) panic("no comma 3 in wand name4"); *comma++ = '\0'; p->max_safe = atoi(s); s = comma; comma = index(s,','); if (! comma) panic("no comma 4 in wand name4"); *comma++ = '\0'; p->expl_percent = atoi(s); p->expl_damage = comma; ot->priv = p; } return(ot->priv); } /* * The new method for wands. */ static OBJ *new_wand(int type, OBJ *o) { WAND *w; WANDTPRIV *tp; tp = wand_type_priv(type); w = malloc(sizeof(WAND)); w->flink = allwands; w->blink = 0; if (allwands) allwands->blink = w; allwands = w; w->flags = 0; w->charges = roll(tp->init_charges); w->type = type; o->private = w; return(o); } /* * The old method for wands. */ static void old_wand(OBJ *o) { WAND *w; w = wand_for_obj(o); if (w->flink) w->flink->blink = w->blink; if (w->blink) w->blink->flink = w->flink; else allwands = w->flink; free(o->private); } /* * The fmt_cond method for wands. The only conditional here is K, for * testing whether a wand's number of charges is known. */ static int fmt_cond_wand(char ch, INVOBJ *io) { switch (ch) { case 'K': return(wand_for_obj(io->v[0])->flags&WF_KNOWN); break; } panic("invalid conditional +%c for wand",ch); } /* * The fmt_spec method for wands. The only format here is C, for * generating the wand's charge count. */ static void fmt_spec_wand(FILE *f, char ch, INVOBJ *io) { switch (ch) { case 'C': fprintf(f,"%d",wand_for_obj(io->v[0])->charges); break; default: panic("invalid format %%+%c for wand",ch); break; } } /* * The identified method for wands. * * knowledge_show() knows that wands use OTF_KNOWN to indicate * knowledge of the material<->function mapping. */ static int identified_wand(INVOBJ *io) { return( (objtypes[io->v[0]->type].flags & OTF_KNOWN) && (wand_for_obj(io->v[0])->flags & WF_KNOWN) ); } /* * The identify method for wands. */ static INVOBJ *identify_wand(INVOBJ *io) { objtypes[io->v[0]->type].flags |= OTF_KNOWN; wand_for_obj(io->v[0])->flags |= WF_KNOWN; return(io); } /* * The OBJOPS for wands. */ #define format_wand std_format #define collapsible_wand never_collapsible #define identical_wand never_identical #define split_wand std_split #define moved_wand noop_moved #define cursable_wand 0 #define setcursed_wand setcursed_uncursable OBJOPS objops_wand = OBJOPS_INIT(wand); static void wand_ray(int dx, int dy, int (*cb)(LOC *, int), int ms) { ray(you->loc,dx,dy,cb,ms); resee(); } /* * Zap a wand. It is the caller's responsibility to ensure the * argument INVOBJ is exactly one wand. */ void wand_zap(INVOBJ *io) { OBJ *o; WAND *w; int k; JMP j; int dx; int dy; if (io->n != 1) panic("zapping plural wand"); o = io->v[0]; w = wand_for_obj(o); if (setjmp(j.b)) { pop_sigint_throw(); return; } while (1) { mvprintw(LINES-1,0,"In what direction? "); clrtoeol(); move(you->loc->y+dispoy,you->loc->x+dispox); push_sigint_throw(&j); k = tget(); pop_sigint_throw(); if ((k == '\33') || (k == '\7')) return; if (dirkey(k,&dx,&dy,0,0,0)) break; } if (w->charges < 1) { pline("Nothing happens."); return; } w->charges --; switch (o->type) { // case OBJ_WAND_OF_CANCELLATION: // case OBJ_WAND_OF_DARKNESS: // case OBJ_WAND_OF_DEATH: case OBJ_WAND_OF_DIGGING: wand_ray(dx,dy,&digging_cell,100); break; // case OBJ_WAND_OF_DISINTEGRATION: // case OBJ_WAND_OF_FIRE_BALL: // case OBJ_WAND_OF_FIRE_BOLT: // case OBJ_WAND_OF_ICE_BOLT: // case OBJ_WAND_OF_LIGHT: // case OBJ_WAND_OF_LIGHTNING: case OBJ_WAND_OF_MAGIC_MISSILE: wand_ray_sym = dirsym(dx,dy); wand_ray(dx,dy,&magic_missle_cell,25); break; case OBJ_WAND_OF_MAKE_INVISIBLE: ray(you->loc,dx,dy,&make_invisible_cell,0); break; // case OBJ_WAND_OF_POLYMORPH: case OBJ_WAND_OF_STRIKING: wand_ray(dx,dy,&striking_cell,50); break; // case OBJ_WAND_OF_TELEPORTATION: // case OBJ_WAND_OF_UNDEAD_TURNING: // case OBJ_WAND_OF_WISHING: } } /* * Charge a wand. It is the caller's responsibility to ensure the * argument INVOBJ is exactly one wand. * * Overcharging a wand risks explosion. Charging adds a number of * charges rolled based on the type. For each added charge: * - If the resulting charge count is <= the type's max safe * charge count, no explosion (for that charge). * - Otherwise, for each charge over the max (including the one * being added), the type's explosion percentage chance is * rolled. If it happens, the wand explodes; each charge in it * rolls the type's explosion damage roll, which add. * * Thus, for example, if we have a wand with 9 charges, of a type whose * max is 10, to which we're adding 3 charges, with a 5% explosion * chance: * * - First charge: charge count is 10, <= max, nothing happens. * - Second charge: 5% is rolled for charge 11. If it hits, wand * explodes for 11 rolls of the explosion damage roll, and * we're done. * - Third charge: 5% is rolled twice, once for charge 11 and once * for charge 12. If either one hits, the wand explodes for 12 * rolls of the explosion damage roll, and we're done. * - If none of the above rolls hit, the wand remains, with 12 * charges in it. In this example, the chance of that is * 95%*95%*95%=85.7375%. * * Thus, overcharging a wand is risky, especially for types with large * explosion chance rolls. But, with sufficient benevolence from the * Random Number God, wands can theoretically have arbitrarily many * charges in them. */ int wand_charge(INVOBJ *io) { OBJ *o; WAND *w; WANDTPRIV *tp; int i; int d; int ncharge; if (io->n != 1) panic("charging plural wand"); o = io->v[0]; w = wand_for_obj(o); tp = wand_type_priv(w->type); for (ncharge=roll(tp->charging_charges);ncharge>0;ncharge--) { w->charges ++; for (i=w->charges;i>tp->max_safe;i--) { if (rnd(100) < tp->expl_percent) { d = 0; for (i=w->charges;i>0;i--) d += roll(tp->expl_damage); w->charges = 0; pline("The overcharged wand explodes for %d damage!",d); (*you->ops->takedamage)(you,d); inv_remove(io->inv,io); invobjfree(io); return(1); } } } return(0); } /* * Forget all wands' charge-count-known status. Used by potion of * amnesia. */ void wand_amnesia(void) { WAND *w; for (w=allwands;w;w=w->flink) w->flags &= ~WF_KNOWN; }