/* * Implementation of wands. */ #include #include #include #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 "damage.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" #define ROOT2 1.414213562373095048801688724209698078569671875376948073176679738 #define EPSILON 1e-6 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; DMGKIND expl_kind; } ; /* * 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; /* * Locations used by ball_cell(). * * When a ball ray hits a wall, the explosion is centred on the * previous location; ball_prevloc is used to hold the suitable * location for this case. After the ray finishes, the explosion * computation needs to know where ground zero is; ball_cell sets * ball_g0 to the appropriate location. If the maximum distance * expires without hitting anything, ball_g0 is set to nil. */ static LOC *ball_prevloc; static LOC *ball_g0; /* * 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,damage_simple(roll("8d2000"),DK_ORDINARY,0)).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)); } } /* * Factoring-out of damage-dealing wand common code for wands that * throw bolts. */ static int bolt_cell(LOC *lc, int n, const char *hitmsg, DMGKIND dk, ...) { va_list ap; 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; int (*testfn)(MONST *); const char *rollstr; vis = monst_cansee_monst(you,m); if (vis) pline("%s %s!",hitmsg,(*m->ops->name)(m)); va_start(ap,dk); while (1) { testfn = va_arg(ap,__typeof__(int (*)(MONST *))); rollstr = va_arg(ap,const char *); if ((*testfn)(m)) break; } va_end(ap); if ((*m->ops->takedamage)(m,damage_simple(roll(rollstr),dk,0)).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)); } } /* * Factoring-out of the ray-tracing portion of code for wands that * throw balls. */ static int ball_cell(LOC *lc, int n) { 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(); } if (lc->monst) { ball_g0 = lc; return(1); } else if (!occupiable(lc)) { ball_g0 = ball_prevloc; return(1); } else if ((n > 35) && onein(5)) { ball_g0 = 0; return(1); } ball_prevloc = lc; return(0); } /* * Common code for ball-style explosions that funnel through corridors * and such. * * Some values for cells for different explosion sizes: * * .*. ..***.. * *** 5 .*****. * .*. ******* * ******* 37 * ******* * ..*.. .*****. * .***. ..***.. * ***** 13 * .***. ...***... * ..*.. ..*****.. * .*******. * ********* * .***. ********* 57 * ***** ********* * ***** 21 .*******. * ***** ..*****.. * .***. ...***... * * Generate an explosion such as is appropriate for a wand of fireball. * g0 is the epicentre of the explosion (the name comes from "ground * zero"), cells is the number of cells the explosion is good for, and * effect is called for each affected cell. * * This algorithm generates `circles' that tend to be octagonal, but * the difference is slight, to the point where it's not visible until * the balls get much larger than anything of use here (multiple * hundreds of cells, at least - it's really obvious around several * thousand cells). And this algorithm is much easier to adapt to * confinement by walls and the like. */ static void ball_explosion(LOC *g0, int cells, void (*effect)(LOC *), int mse, int msa) { typedef struct ballptd BALLPTD; typedef struct ballpt BALLPT; struct ballpt { unsigned short int x; unsigned short int y; } ; struct ballptd { BALLPTD *link; double d; int npt; int apt; BALLPT *pts; } ; BALLPTD *dlist; int x; int y; BALLPT pt; LOC *l; LEVEL *lv; void addpt(LOC *l, double d) { BALLPTD *e; BALLPTD **ep; if (!l || ((d > 0) && !occupiable(l))) return; ep = &dlist; if (l->flags & LF_FLAG) return; do <"have"> { do <"add"> { while (1) { e = *ep; if (! e) break <"add">; if (fabs(e->d-d) < EPSILON) break <"have">; if (e->d > d) break <"add">; ep = &e->link; } } while (0); e = malloc(sizeof(BALLPTD)); e->d = d; e->npt = 0; e->apt = 0; e->pts = 0; e->link = *ep; *ep = e; } while (0); if (e->npt >= e->apt) e->pts = realloc(e->pts,(e->apt+=8)*sizeof(BALLPT)); e->pts[e->npt++] = (BALLPT){.x=l->x,.y=l->y}; } lv = g0->on; for (x=0;xcells[x][0]; for (y=0;yflags &= ~LF_FLAG; } dlist = 0; addpt(g0,0); // dlist can be nil; consider a ball of size >11 in a vault. while ((cells > 0) && dlist) { if (dlist->npt < 1) { BALLPTD *d; d = dlist; dlist = d->link; free(d->pts); free(d); continue; } x = (dlist->npt < 2) ? 0 : rnd(dlist->npt); pt = dlist->pts[x]; dlist->npt --; if (x != dlist->npt) dlist->pts[x] = dlist->pts[dlist->npt]; if ((pt.x >= LEV_X) || (pt.y >= LEV_Y)) panic("explosion off edge of level"); l = &lv->cells[pt.x][pt.y]; if (l->flags & LF_FLAG) continue; l->flags |= LF_FLAG; mvaddch(l->y+dispoy,l->x+dispox,'*'); if (mse) { move(you->loc->y+dispoy,you->loc->x+dispox); refresh(); poll(0,0,mse); } (*effect)(l); cells --; addpt(movecell(l,1,0,0),dlist->d+1); addpt(movecell(l,0,1,0),dlist->d+1); addpt(movecell(l,-1,0,0),dlist->d+1); addpt(movecell(l,0,-1,0),dlist->d+1); addpt(movecell(l,1,1,0),dlist->d+ROOT2); addpt(movecell(l,1,-1,0),dlist->d+ROOT2); addpt(movecell(l,-1,1,0),dlist->d+ROOT2); addpt(movecell(l,-1,-1,0),dlist->d+ROOT2); } if (msa) { move(you->loc->y+dispoy,you->loc->x+dispox); refresh(); poll(0,0,msa); } } static void ball_wand(LOC *start, int dx, int dy, int (*shcirc)(LOC *), void (*effect)(LOC *), int ms, int cells) { int c(LOC *lc, int n) { return((*shcirc)(lc) || ball_cell(lc,n)); } wand_ray_sym = dirsym(dx,dy); ray(start,dx,dy,&c,ms); if (ball_g0) ball_explosion(ball_g0,cells,effect,25,500); } static int monst_all(MONST *m) { (void)m; return(1); } static int monst_resist_magic(MONST *m) { (void)m; return(0); } /* * Ray function for wand of magic missle. */ static int magic_missle_cell(LOC *lc, int n) { return(bolt_cell(lc,n,"The missles hit",DK_ORDINARY,&monst_resist_magic,"0",&monst_all,"6d4000")); } /* * Ray function for wand of fire bolt. */ static int fire_bolt_cell(LOC *lc, int n) { if ((lc->flags & LF_WATER) && (n > 0)) { pline("The bolt fizzles as it hits the water!"); return(1); } return(bolt_cell(lc,n,"The bolt hits",DK_FIRE,&monst_all,"6d4000")); } /* * Ray function for wand of ice bolt. */ static int ice_bolt_cell(LOC *lc, int n) { if ((lc->flags & LF_WATER) && (n > 0)) { pline("The bolt fizzles as it hits the water!"); return(1); } return(bolt_cell(lc,n,"The bolt hits",DK_COLD,&monst_all,"6d4000")); } /* * Ray function for wand of lightning, not in water. */ static int lightning_bolt_cell(LOC *lc, int n) { return(bolt_cell(lc,n,"The bolt hits",DK_ELECTRIC,&monst_all,"8d4000")); } /* * Ball short-circuit function for wand of fire/ice/lightning ball. */ static int ball_short(LOC *lc) { return(lc->flags & LF_WATER); } /* * Ball effect function for wand of fire ball. */ static void fire_ball_effect(LOC *lc) { MONST *m; m = lc->monst; if (! m) return; // maybe affect objects? if ((*m->ops->takedamage)(m,damage_simple(roll("4d4000"),DK_FIRE,0)).flags & TDRV_DIED) { if (lc->flags & LF_VISIBLE) pline("%s dies!",(*m->ops->Name)(m)); } } /* * Ball effect function for wand of ice ball. */ static void ice_ball_effect(LOC *lc) { MONST *m; m = lc->monst; if (! m) return; // maybe affect objects? if ((*m->ops->takedamage)(m,damage_simple(roll("4d4000"),DK_COLD,0)).flags & TDRV_DIED) { if (lc->flags & LF_VISIBLE) pline("%s dies!",(*m->ops->Name)(m)); } } /* * Ball effect function for wand of lightning, in water. */ static void lightning_ball_effect(LOC *lc) { MONST *m; m = lc->monst; if (! m) return; // maybe affect objects? if ((*m->ops->takedamage)(m,damage_simple(roll("6d4000"),DK_ELECTRIC,0)).flags & TDRV_DIED) { if (lc->flags & LF_VISIBLE) pline("%s dies!",(*m->ops->Name)(m)); } } /* * lc has just been dug; handle what this might do to water-filled * areas. * * Specifically, we assume that every non-water-filled area has enough * drainage that it will not hold water. If the cell is adjacent to * only non-water-filled cells, nothing happens; if it is adjacent to * only water-filled cells, all that happens is that it becomes * water-filled. But, if it is adjacent to both water-filled and * non-water-filled, the water drains away, converting the * water-filled area into a non-water-filled area. * * The code is careful to handle adjacency correctly for LVF_WRAPAROUND * levels. At this writing, this is pure future-proofing, as the only * WRAPAROUND level with any diggable spots is the Plane of Earth, * which has no water-filled areas. (Unfortunately this also means * that code borders on untestable....) */ static void dug_water(LOC *lc) { int dx; int dy; int lx; int ly; LOC *a; unsigned int have; #define HAVE_W_S 0x00000001 #define HAVE_NW_S 0x00000002 #define HAVE_W_NS 0x00000004 #define HAVE_NW_NS 0x00000008 int drain; have = 0; for (dx=-1;dx<=1;dx++) { lx = lc->x + dx; if (lx > LEV_X-1) { if (! (lc->on->flags & LVF_WRAPAROUND)) panic("wrapping around non-WRAPAROUND level"); lx -= LEV_X; } else if (lx < 0) { if (! (lc->on->flags & LVF_WRAPAROUND)) panic("wrapping around non-WRAPAROUND level"); lx += LEV_X; } for (dy=-1;dy<=1;dy++) { if (!dy && !dx) continue; ly = lc->y + dy; if (ly > LEV_Y-1) { if (! (lc->on->flags & LVF_WRAPAROUND)) panic("wrapping around non-WRAPAROUND level"); ly -= LEV_Y; } else if (ly < 0) { if (! (lc->on->flags & LVF_WRAPAROUND)) panic("wrapping around non-WRAPAROUND level"); ly += LEV_Y; } a = &lc->on->cells[lx][ly]; if ((a->type == LOC_CAVE) || (a->type == LOC_DOOR)) { have |= (a->flags & LF_WATER) ? HAVE_W_NS : HAVE_NW_NS; } else if (a->type == LOC_SDOOR) { have |= (a->flags & LF_WATER) ? HAVE_W_S : HAVE_NW_S; } } } drain = 0; switch (have) { case 0: // This can happen - wand of digging while phasing through rock. // Nothing to do here. break; case HAVE_NW_NS: case HAVE_NW_S : case HAVE_NW_S | HAVE_NW_NS: // No water anywhere. Nothing to do here. break; case HAVE_W_NS : case HAVE_W_S : case HAVE_W_S | HAVE_W_NS : // All adjacencies are water. Just set it water. lc->flags |= LF_WATER; break; case HAVE_NW_S | HAVE_W_NS : case HAVE_W_S | HAVE_NW_NS: case HAVE_W_S | HAVE_NW_S : case HAVE_W_S | HAVE_NW_S | HAVE_W_NS : case HAVE_W_S | HAVE_NW_S | HAVE_NW_NS: // Have water and non-water - but at least one of them is only // because of secret doors. Drain, and reveal the secret doors // (you can see the water flooding into or out of them). search(lc,SEARCH_PERFECT); drain = 1; break; case HAVE_W_NS | HAVE_NW_NS: case HAVE_NW_S | HAVE_W_NS | HAVE_NW_NS: case HAVE_W_S | HAVE_W_NS | HAVE_NW_NS: case HAVE_W_S | HAVE_NW_S | HAVE_W_NS | HAVE_NW_NS: // Have obvious water and obvious non-water. Just drain. drain = 1; break; } if (drain) { for (lx=LEV_X-1;lx>=0;lx--) for (ly=LEV_Y-1;ly>=0;ly--) lc->on->cells[lx][ly].flags &= ~LF_FLAG; flagregion(lc->on,lc->x,lc->y,0); for (lx=LEV_X-1;lx>=0;lx--) for (ly=LEV_Y-1;ly>=0;ly--) { a = &lc->on->cells[lx][ly]; if (a->flags & LF_FLAG) a->flags &= ~(LF_FLAG|LF_WATER); } pline("The water drains away!"); } #undef HAVE_W_S #undef HAVE_NW_S #undef HAVE_W_NS #undef HAVE_NW_NS } /* * 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_setat(map,lc->x+dx,lc->y+dy,SYM_OOS,0); } dug_water(lc); 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); } } /* * Ray function for wand of cancellation. */ static int cancellation_cell(LOC *lc, int n __attribute__((__unused__))) { MONST *m; m = lc->monst; if (m) { m->baseflags |= MBF_CANCEL; return(1); } return(!occupiable(lc)); } /* * 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); s = comma; comma = index(s,','); if (! comma) { p->expl_damage = s; p->expl_kind = DK_ORDINARY; } else { *comma++ = '\0'; p->expl_damage = s; s = comma; p->expl_kind = damage_kind_by_name(s,DK_ORDINARY); } 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 #define inuse_wand inuse_never 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(); } static void wand_of_lightning(int dx, int dy) { if (you->loc->flags & LF_WATER) { ball_explosion(you->loc,21,&lightning_ball_effect,25,500); } else { wand_ray_sym = dirsym(dx,dy); wand_ray(dx,dy,&lightning_bolt_cell,25); } } /* * 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: ray(you->loc,dx,dy,&cancellation_cell,0); break; // case OBJ_WAND_OF_DARKNESS: // case OBJ_WAND_OF_DEATH: case OBJ_WAND_OF_DIGGING: objtypes[OBJ_WAND_OF_DIGGING].flags |= OTF_KNOWN; wand_ray(dx,dy,&digging_cell,100); break; // case OBJ_WAND_OF_DISINTEGRATION: case OBJ_WAND_OF_FIRE_BALL: objtypes[OBJ_WAND_OF_FIRE_BALL].flags |= OTF_KNOWN; ball_wand(you->loc,dx,dy,&ball_short,&fire_ball_effect,25,30); break; case OBJ_WAND_OF_ICE_BALL: objtypes[OBJ_WAND_OF_ICE_BALL].flags |= OTF_KNOWN; ball_wand(you->loc,dx,dy,&ball_short,&ice_ball_effect,25,30); break; case OBJ_WAND_OF_FIRE_BOLT: objtypes[OBJ_WAND_OF_FIRE_BOLT].flags |= OTF_KNOWN; wand_ray_sym = dirsym(dx,dy); wand_ray(dx,dy,&fire_bolt_cell,25); break; case OBJ_WAND_OF_ICE_BOLT: objtypes[OBJ_WAND_OF_ICE_BOLT].flags |= OTF_KNOWN; wand_ray_sym = dirsym(dx,dy); wand_ray(dx,dy,&ice_bolt_cell,25); break; case OBJ_WAND_OF_LIGHTNING: objtypes[OBJ_WAND_OF_LIGHTNING].flags |= OTF_KNOWN; wand_of_lightning(dx,dy); break; case OBJ_WAND_OF_MAGIC_MISSILE: objtypes[OBJ_WAND_OF_MAGIC_MISSILE].flags |= OTF_KNOWN; 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. * * Return value is true if the wand explodes, false if not. When * returning true, the wand will have already been destroyed. */ 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,damage_simple(d,DK_ORDINARY,0)); 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; }