/* * The implementation of rings. * * Note that the curse-handling code here depends on rings using * never_collapsible and never_identical, so that we can manipulate * INVOBJs without worrying about their getting collapsed. */ #include #include "obj.h" #include "mon.h" #include "util.h" #include "dice.h" #include "vars.h" #include "pline.h" #include "fuses.h" #include "param.h" #include "effect.h" #include "format.h" #include "structs.h" #include "objtypes.h" #include "obj-ring.h" typedef struct ring RING; typedef struct rlist RLIST; typedef struct rtconc RTCONC; typedef struct ringeffect RINGEFFECT; /* * Private data for a ring. */ struct ring { RING *flink; RING *blink; int plusses; unsigned int flags; #define RF_KNOWN 0x00000001 #define RF_CURSED 0x00000002 MONST *wornby; union { RINGEFFECT *most; // most rings struct { // rings of protection char *rollstr; BONUSROLL *roll; } prot; } ; } ; /* * A(n entry in a) list of rings. This is used by the cursed-ring * implementation. */ struct rlist { RLIST *link; OBJ *ro; } ; /* * A tconc of RLISTs. */ struct rtconc { RLIST *head; RLIST **tail; } ; /* * Private data for a ring-generated effect, for most rings. */ struct ringeffect { MONST *m; EFFECT *eff; OBJ *o; } ; /* * List of all rings. */ static RING *allrings = 0; /* * The list of cursed rings whose curses have been activated. The * reason there is only one of these, rather than one per monster (or * a monster pointer in each RLIST), is that ring curses affect only * the player. */ static RLIST *active_rcurse = 0; /* * you_were_attacked as of last time the ring curse code ran. This is * used to detect combat and boost the chance of a cursed ring of * protection switching in. */ static unsigned int last_attacked_r; /* * Return the RING * for an OBJ * (or, if the OBJ isn't a ring, panic). */ static RING *ring_for_obj(OBJ *o) { if (objtypes[o->type].class != OC_RING) panic("non-ring where ring needed"); return(o->private); } /* * Apply the effect for a ring (most rings). */ static void apply_ring(MONST *m, void *rev) { RINGEFFECT *re; re = rev; switch (re->o->type) { // case OBJ_RING_OF_FIRE_RESISTANCE: case OBJ_RING_OF_PROTECTION: panic("applying ring of protection"); break; case OBJ_RING_OF_REGENERATION: m->heal += 150; break; case OBJ_RING_OF_SEE_INVISIBLE: m->effflags |= MEF_SEE_INVISIBLE; break; // case OBJ_RING_OF_SLOW_DIGESTION: // case OBJ_RING_OF_STEALTH: case OBJ_RING_OF_TELEPORT_CONTROL: m->effflags |= MEF_TELEPORT_CTL; break; case OBJ_RING_OF_VAMPIRIC_REGENERATION: m->effflags |= MEF_VAMP_REGEN; break; case OBJ_RING_OF_WATER_BREATHING: m->effflags |= MEF_BREATHE_WATER; break; } } /* * Tear down the effect for a ring (most rings). */ static void death_ring(MONST *m __attribute__((__unused__)), void *rev) { free(rev); } static EFFECTOPS effops_ring = EFFECTOPS_INIT(ring); /* * Return the answer to the question "do rings of this this type have * plusses?". */ int ringtype_has_plusses(int type) { switch (type) { case OBJ_RING_OF_PROTECTION: return(1); break; } return(0); } /* * Remove all rings a monster is wearing. Used by, for example, * monster death. */ void ring_remove_all(MONST *m) { int i; for (i=MAXRINGS-1;i>=0;i--) if (m->rings[i]) ring_remove(m,m->rings[i]); } /* * Tear down the protection bonus for a ring of protection. Nothing to * do here. */ static void ring_prot_unbonus(BONUSROLL *br __attribute__((__unused__))) { } /* * Return the answer to the question "is this ring being worn?". */ int ring_being_worn(OBJ *o) { return(!!ring_for_obj(o)->wornby); } /* * Set a ring's plusses. This is the internal version, which takes a * RING *. */ static void rplusses(RING *r, int p) { r->plusses = p; reset_roll_string(&r->prot.rollstr,p); } /* * The new method for rings. */ static OBJ *new_ring(int type, OBJ *o) { RING *r; int p; r = malloc(sizeof(RING)); r->flink = allrings; r->blink = 0; if (allrings) allrings->blink = r; allrings = r; r->flags = 0; r->wornby = 0; if (ringtype_has_plusses(type)) { switch (type) { case OBJ_RING_OF_PROTECTION: if (onein(3)) { p = - roll("d20"); r->flags |= RF_CURSED; } else { p = roll("d26+d25-1"); } r->prot.rollstr = 0; rplusses(r,p); break; default: panic("unimplemented plus-bearing ring type"); break; } } else { if (onein(5)) r->flags |= RF_CURSED; } o->private = r; return(o); } /* * The old method for rings. */ static void old_ring(OBJ *o) { RING *r; r = ring_for_obj(o); if (r->wornby) panic("freeing worn ring"); if (r->flink) r->flink->blink = r->blink; if (r->blink) r->blink->flink = r->flink; else allrings = r->flink; free(r); } /* * The type-speceific format conditionals for rings: * * C is this ring cursed? * * K are this ring's plusses (if any) and curse status * known? * * P is this ring of a type that has plusses? * * W is this ring being worn? */ static int fmt_cond_ring(char ch, INVOBJ *io) { switch (ch) { case 'C': return(ring_for_obj(io->v[0])->flags&RF_CURSED); break; case 'K': return(ring_for_obj(io->v[0])->flags&RF_KNOWN); break; case 'P': return(ringtype_has_plusses(io->v[0]->type)); break; case 'W': return(ring_being_worn(io->v[0])); break; } panic("invalid conditional +%c for ring",ch); } /* * The type-speceific format specifiers for rings: * * P Insert this ring's plusses, as "%+d". */ static void fmt_spec_ring(FILE *f, char ch, INVOBJ *io) { switch (ch) { case 'P': if (! ringtype_has_plusses(io->v[0]->type)) panic("invalid ring plusses"); fprintf(f,"%+d",ring_for_obj(io->v[0])->plusses); break; default: panic("invalid format %%+%c for ring",ch); break; } } /* * The identified method for rings. * * knowledge_show() knows that rings use OTF_KNOWN to indicate * knowledge of the material<->function mapping. */ static int identified_ring(INVOBJ *io) { return((objtypes[io->v[0]->type].flags&OTF_KNOWN)&&(ring_for_obj(io->v[0])->flags&RF_KNOWN)); } /* * The identify method for rings. */ static INVOBJ *identify_ring(INVOBJ *io) { objtypes[io->v[0]->type].flags |= OTF_KNOWN; ring_for_obj(io->v[0])->flags |= RF_KNOWN; return(io); } /* * Set a ring's curse status. */ static void setcursed_ring(OBJ *o, int c) { if (c) ring_for_obj(o)->flags |= RF_CURSED; else ring_for_obj(o)->flags &= ~RF_CURSED; } /* * The OBJOPS for rings. */ #define format_ring std_format #define collapsible_ring never_collapsible #define identical_ring never_identical #define split_ring std_split #define moved_ring noop_moved #define cursable_ring 1 OBJOPS objops_ring = OBJOPS_INIT(ring); /* * This is called from a fuse periodically when a ring's curse is * active. (There is only one fuse calling this, not one per * activated curse.) Every tick, there is a 1% chance that a randomly * chosen cursed ring is silently forced into one of the player's ring * slots, warping it into the player's inventory and replacing an * existing ring if necessary. If a type-specific condition is met, * this chance goes up to 50% (eg, when attacked, cursed rings of * protection are much more likely to trigger). * * When replacing an already-worn ring, prefer replacing uncursed * rings, of course. */ static void ring_curse_tick(long int argli __attribute__((__unused__)), void *argvp __attribute__((__unused__))) { int trig_attacked; int nc; int nu; RLIST *rl; RING *r; int i; RTCONC high; RTCONC low; RTCONC *list; RTCONC *trig; OBJ *to; int c; trig_attacked = (last_attacked_r != you_were_attacked); last_attacked_r = you_were_attacked; high.tail = &high.head; low.tail = &low.head; while ((rl = active_rcurse)) { active_rcurse = rl->link; r = ring_for_obj(rl->ro); if (r->flags & RF_CURSED) { switch (rl->ro->type) { case OBJ_RING_OF_PROTECTION: list = trig_attacked ? &high : &low; break; default: panic("unimplemented cursed ring type"); break; } *list->tail = rl; list->tail = &rl->link; } else { free(rl); } } *high.tail = 0; *low.tail = 0; do { if (high.head && onein(2)) { trig = &high; active_rcurse = low.head; } else if (low.head && onein(100)) { trig = &low; active_rcurse = high.head; } else { *high.tail = low.head; active_rcurse = high.head; break; } nc = 0; while ((rl = trig->head)) { trig->head = rl->link; nc ++; if (onein(nc)) to = rl->ro; rl->link = active_rcurse; active_rcurse = rl; } if (! nc) panic("can't pick cursed ring"); if (ring_for_obj(to)->wornby == you) break; if (to->io->inv != &you->invent) { if (to->io->inv->type == IT_MON) ring_stop_using(to->io->inv->u.m,to->io); inv_move_1(to->io->inv,to->io,&you->invent); } if (you->rings[MAXRINGS-1]) { nc = 0; nu = 0; c = -1; for (i=MAXRINGS-1;i>=0;i--) { if (ring_for_obj(you->rings[i])->flags & RF_CURSED) { nc ++; if (onein(nc)) c = i; } else if (! nc) { nu ++; if (onein(nu)) c = i; } } if (c < 0) panic("can't find ring slot to replace"); ring_remove(you,you->rings[c]); } ring_put(you,to); if (! active_rcurse) panic("switched cursed ring but none active"); } while (0); addfuse_rel(&ring_curse_tick,0,0,TIMESCALE); } /* * Activate a ring's curse, if it isn't already active. */ static void activate_rcurse(OBJ *o) { RLIST *rl; last_attacked_r = you_were_attacked; for (rl=active_rcurse;rl;rl=rl->link) if (rl->ro == o) return; if (! active_rcurse) addfuse_rel(&ring_curse_tick,0,0,TIMESCALE); rl = malloc(sizeof(RLIST)); rl->ro = o; rl->link = active_rcurse; active_rcurse = rl; } /* * Put a ring on a monster. */ void ring_put(MONST *m, OBJ *o) { RING *r; int i; INVOBJ *io; int chkcurse; int find_it(INVOBJ *io) { return(io->v[0]==o); } r = ring_for_obj(o); if (m->rings[MAXRINGS-1]) panic("putting on too many rings"); if (r->wornby) panic("putting on already-worn ring"); for (i=MAXRINGS-1;(i>=0)&&!m->rings[i];i--) ; i ++; pline("using rings[%d]",i); if ((i < 0) || (i >= MAXRINGS) || m->rings[i]) panic("impossible ring slot"); m->rings[i] = o; r->wornby = m; chkcurse = 0; switch (o->type) { case OBJ_RING_OF_PROTECTION: r->prot.roll = add_bonus(r->prot.rollstr,&m->bonus_def,&ring_prot_unbonus); chkcurse = 1; break; default: { RINGEFFECT *e; e = malloc(sizeof(RINGEFFECT)); e->m = m; e->o = o; // Null out eff, and set it last, so that apply_ring(), when // called from within effect_add(), finds e set up. e->eff = 0; e->eff = effect_add(m,e,&effops_ring); r->most = e; } break; } if (chkcurse) { if ((r->flags & RF_CURSED) && (m == you)) { io = inv_scan(&you->invent,&find_it); if (! io) panic("putting on ring not in inventory"); activate_rcurse(io->v[0]); } } } /* * Remove a ring from a monster. */ void ring_remove(MONST *m, OBJ *o) { RING *r; int i; r = ring_for_obj(o); if (! r->wornby) panic("removing non-worn ring"); if (r->wornby != m) panic("removing ring from wrong monster"); for (i=MAXRINGS-1;(i>=0)&&(m->rings[i]!=o);i--) ; if (i < 0) panic("worn ring isn't in rings[]"); pline("found in rings[%d]",i); for (;irings[i] = m->rings[i+1]; m->rings[MAXRINGS-1] = 0; r->wornby = 0; switch (o->type) { case OBJ_RING_OF_PROTECTION: if (r->prot.roll) { remove_bonus(r->prot.roll); r->prot.roll = 0; } break; default: effect_remove(m,r->most->eff); free(r->most); r->most = 0; break; } } /* * Inventory test function for ring_show(). */ static int ring_show_test(INVOBJ *io) { if (objtypes[io->v[0]->type].class != OC_RING) return(0); return(!!ring_for_obj(io->v[0])->wornby); } /* * Show what ring(s), if any, the monster is wearing. This really * makes sense only when the monster is the player. */ void ring_show(MONST *m) { show_inventory(&m->invent,"",&ring_show_test,"Not wearing any rings"); } /* * Stop the monster from using the ring. The object must be a ring, * must be carried by the monster, and must not be worn by any other * monster (this last is a can't-happen), but is a no-op if none of * those trip and the ring simply isn't being worn. */ void ring_stop_using(MONST *m, INVOBJ *io) { OBJ *o; o = io->v[0]; if (io->inv != &m->invent) panic("ring_stop_using, not in inventory"); if (! ring_for_obj(o)->wornby) return; ring_remove(m,o); } /* * Set a ring's plusses. It is an error to call this with a ring of a * type that doesn't have plusses. * * This is just like rplusses except that it takes an OBJ * instead of * a RING * (and checks correspondingly). */ void ring_set_plusses(OBJ *o, int plus) { if (! ringtype_has_plusses(o->type)) panic("setting nonexistent ring plusses"); rplusses(ring_for_obj(o),plus); } /* * Forget all rings' plusses-known status. Used by potion of anmesia. */ void ring_amnesia(void) { RING *r; for (r=allrings;r;r=r->flink) r->flags &= ~RF_KNOWN; }