/* * Implementation of weapons. * * Note that the curse-handling code here is more complicated than that * for armour because, unlike armour, weapons have nontrivial * identical and collapsible methods. */ #include #include #include "obj.h" #include "vars.h" #include "util.h" #include "dice.h" #include "pline.h" #include "fuses.h" #include "format.h" #include "effect.h" #include "structs.h" #include "objtypes.h" #include "obj-weapon.h" typedef struct weapon WEAPON; typedef struct wlist WLIST; typedef struct weptpriv WEPTPRIV; typedef struct wepeffect WEPEFFECT; /* * Private data for a weapon type. Just holds the ase to-hit and * damage rolls broken out from the name4 string. */ struct weptpriv { char *base_hit; char *base_dmg; } ; /* * Private data for a weapon. Consists of flags (eg, indicating * whether its plusses are known), its base to-hit and damage rolls, * its plusses to-hit and damage, the rolls for plusses to-hit and * damage, and a backpointer to the monster, if any, that's wielding * it. */ struct weapon { unsigned int flags; #define WF_KNOW_P 0x00000001 #define WF_KNOW_V 0x00000002 #define WF_KNOW_C 0x00000004 #define WF_CURSED 0x00000008 #define WF_ACTCURSE 0x00000010 #define WF_VAMP 0x00000020 const char *roll_base_hit; const char *roll_base_dmg; int bonus_hit; int bonus_dmg; char *roll_extra_hit; char *roll_extra_dmg; MONST *wielder; WEPEFFECT *eff; } ; /* * A list of weapons. This is part of the weapon curse implementation. */ struct wlist { WLIST *link; OBJ *wo; } ; /* * Private data for a wielded-weapon effect. */ struct wepeffect { MONST *m; EFFECT *eff; OBJ *o; } ; /* * The list of cursed weapons whose curses have been activated. Weapon * curses affect only the player, which is why we need only one list * here (or, to put it another way, why there's no monster pointer in * a WLIST). * * This also means we don't need to worry about killing off an * activated curse on monster death. */ static WLIST *active_wcurse = 0; /* * you_attacked as of last time the weapo ncurse code ran. This is * used to detect combat and boose the chance of weapon curse * activation. */ static unsigned int last_attack_w; /* * Return the WEAPON * for an OBJ * (or, if the OBJ isn't a weapon, * panic). */ static WEAPON *weapon_for_obj(OBJ *o) { if (objtypes[o->type].class != OC_WEAPON) panic("non-weapon where weapon needed"); return(o->private); } /* * Apply a wielded-weapon effect. Currently, the only one is vampiric * regeneration. */ static void apply_weapon(MONST *m, void *wev) { WEPEFFECT *we; we = wev; m->effflags |= MEF_VAMP_REGEN; } /* * Tear down a wielded-weapon effect. */ static void death_weapon(MONST *m __attribute__((__unused__)), void *wev) { free(wev); } static EFFECTOPS effops_weapon = EFFECTOPS_INIT(weapon); /* * Reset a weapon's roll_extra_hit and roll_extra_dmg strings. */ static void reset_extra_rolls(WEAPON *w) { reset_roll_string(&w->roll_extra_hit,w->bonus_hit); reset_roll_string(&w->roll_extra_dmg,w->bonus_dmg); } /* * Return a weapon type's private struct, creating it if necessary. */ static WEPTPRIV *weapon_type_priv(int type) { OBJTYPE *ot; WEPTPRIV *p; char *comma; ot = &objtypes[type]; if (ot->class != OC_WEAPON) panic("non-weapon to weapon_type_priv"); if (! ot->priv) { p = malloc(sizeof(WEPTPRIV)+strlen(ot->name4)+1); p->base_hit = (void *)(p+1); strcpy(p->base_hit,ot->name4); comma = index(p->base_hit,','); if (! comma) panic("no comma in weapon name4"); *comma++ = '\0'; p->base_dmg = comma; ot->priv = p; } return(ot->priv); } /* * The new method for weapons. */ static OBJ *new_weapon(int type, OBJ *o) { WEAPON *w; WEPTPRIV *tp; tp = weapon_type_priv(type); w = malloc(sizeof(WEAPON)); w->flags = 0; switch (type) { case OBJ_SHORT_SWORD: if (onein(5)) w->flags |= WF_VAMP; break; case OBJ_LONG_SWORD: if (onein(4)) w->flags |= WF_VAMP; break; case OBJ_TWO_HANDED_SWORD: if (onein(10)) w->flags |= WF_VAMP; break; } w->roll_base_hit = tp->base_hit; w->roll_base_dmg = tp->base_dmg; w->bonus_hit = 0; w->bonus_dmg = 0; w->roll_extra_hit = 0; w->roll_extra_dmg = 0; w->wielder = 0; w->eff = 0; reset_extra_rolls(w); o->private = w; return(o); } /* * The old method for weapons. */ static void old_weapon(OBJ *o) { WEAPON *w; w = weapon_for_obj(o); free(w->roll_extra_hit); free(w->roll_extra_dmg); free(w); } /* * Weapon-specific format conditionals. */ static int fmt_cond_weapon(char ch, INVOBJ *io) { switch (ch) { case 'C': return(weapon_for_obj(io->v[0])->flags&WF_CURSED); break; case 'V': return(weapon_for_obj(io->v[0])->flags&WF_VAMP); break; case 'W': return(!!weapon_for_obj(io->v[0])->wielder); break; case 'c': return(weapon_for_obj(io->v[0])->flags&WF_KNOW_C); break; case 'p': return(weapon_for_obj(io->v[0])->flags&WF_KNOW_P); break; case 'v': return(weapon_for_obj(io->v[0])->flags&WF_KNOW_V); break; } panic("invalid conditional +%c for weapon",ch); } /* * Weapon-specific formats. */ static void fmt_spec_weapon(FILE *f, char ch, INVOBJ *io) { WEAPON *w; w = weapon_for_obj(io->v[0]); switch (ch) { case 'P': if (w->bonus_hit == w->bonus_dmg) { fprintf(f,"%+d",w->bonus_hit); } else { fprintf(f,"%+d,%+d",w->bonus_hit,w->bonus_dmg); } break; default: panic("invalid format %%+%c for weapon",ch); break; } } /* * The collapsible method for weapons. * * Weapons are collapsible if they "look the same". This means this * code has to match the weapon format string in objtypelist. * * There are a bunch of reasons weapons may fail to collapse. We test * them. If none of those tests trip, they can collapse. Comments * with just a condition, on if (...) return(0), should be understood * to have an implicit trailing "...then don't collapse" */ static int collapsible_weapon(OBJ *o1, OBJ *o2) { WEAPON *w1; WEAPON *w2; int t1; int t2; w1 = weapon_for_obj(o1); w2 = weapon_for_obj(o2); // Wielded weapons never collapse with anything. if (w1->wielder || w2->wielder) return(0); t1 = o1->type; t2 = o2->type; #if 0 if ((t1 == OBJ_ARROW_OF_DEATH) && !(w1->flags & WF_KNOWN)) t1 = OBJ_ARROW; if ((t2 == OBJ_ARROW_OF_DEATH) && !(w2->flags & WF_KNOWN)) t2 = OBJ_ARROW; #endif // Different types never collapse. if (t1 != t2) return(0); // If plusses-knownness or curse-knownness differ... if ((w1->flags ^ w2->flags) & (WF_KNOW_P|WF_KNOW_C)) return(0); // If one is KNOW_V&VAMP and the other isn't... t1 = ((w1->flags & (WF_KNOW_V|WF_VAMP)) == (WF_KNOW_V|WF_VAMP)); t2 = ((w2->flags & (WF_KNOW_V|WF_VAMP)) == (WF_KNOW_V|WF_VAMP)); if (t1 != t2) return(0); // If curse status is known and cursednesses differ.. if ((w1->flags & WF_KNOW_C) && ((w1->flags & w2->flags) & WF_CURSED)) return(0); // If plusses are known and differ... if ( (w1->flags & WF_KNOW_P) && ( (w1->bonus_hit != w2->bonus_hit) || (w1->bonus_dmg != w2->bonus_dmg) ) ) return(0); // No reason to not collapse. return(1); } /* * The identical method for weapons. Weapons of different types are * never identical. Weapons with activated curses are never identical * to anything else; neither are wielded weapons, nor weapons with * differing flags or differing plusses. Otherwise, yes, they're * identical. */ static int identical_weapon(OBJ *o1, OBJ *o2) { WEAPON *w1; WEAPON *w2; if (o1->type != o2->type) return(0); w1 = weapon_for_obj(o1); w2 = weapon_for_obj(o2); if ( ((w1->flags | w2->flags) & WF_ACTCURSE) || w1->wielder || w2->wielder || (w1->flags != w2->flags) || (w1->bonus_hit != w2->bonus_hit) || (w1->bonus_dmg != w2->bonus_dmg) ) return(0); return(1); } /* * The split method for weapons. This exists because we need to copy * various state when splitting plural weapons. */ static OBJ *split_weapon(OBJ *old, int n) { OBJ *new; WEAPON *wold; WEAPON *wnew; new = std_split(old,n); wold = weapon_for_obj(old); wnew = weapon_for_obj(new); if (wold->flags & WF_ACTCURSE) panic("activated curse on plural weapon"); if (wold->wielder) panic("plural wielded weapon"); wnew->flags = wold->flags; wnew->roll_base_hit = wold->roll_base_hit; wnew->roll_base_dmg = wold->roll_base_dmg; wnew->bonus_hit = wold->bonus_hit; wnew->bonus_dmg = wold->bonus_dmg; reset_extra_rolls(wnew); return(new); } /* * The identified method for weapons. This actually means "a scroll of * identify would tell nothing new about it". */ static int identified_weapon(INVOBJ *io) { return((weapon_for_obj(io->v[0])->flags & (WF_KNOW_P|WF_KNOW_C|WF_KNOW_V)) == (WF_KNOW_P|WF_KNOW_C|WF_KNOW_V)); } /* * The identify method for weapons. If the object is singular, just * identify it. Otherwise, pick a representative, split it off into * its own INVOBJ, and identify it. */ static INVOBJ *identify_weapon(INVOBJ *io) { int i; int r; OBJ *o; if (io->n == 1) { weapon_for_obj(io->v[0])->flags |= WF_KNOW_P | WF_KNOW_C | WF_KNOW_V; return(io); } r = rnd(io->dispn); for (i=0;in;i++) { r -= io->v[i]->number; if (r < 0) break; } if (i >= io->n) panic("invobj overrun in weapon identify"); o = remove_obj_from_invobj(i,io); weapon_for_obj(o)->flags |= WF_KNOW_P | WF_KNOW_C | WF_KNOW_V; return(add_obj_to_inv(o,io->inv)); } /* * Set a weapon's curse status. */ static void setcursed_weapon(OBJ *o, int c) { WEAPON *w; w = weapon_for_obj(o); if (c) w->flags |= WF_CURSED; else w->flags &= ~WF_CURSED; } /* * The inuse method for weapons. */ static int inuse_weapon(INVOBJ *io) { return(!!weapon_for_obj(io->v[0])->wielder); } /* * Indicate whether a weapon is cursed. */ static int iscursed_weapon(OBJ *o) { return(weapon_for_obj(o)->flags&WF_CURSED); } /* * The OBJOPS vector for weapons. */ #define format_weapon std_format #define moved_weapon noop_moved #define cursable_weapon 1 OBJOPS objops_weapon = OBJOPS_INIT(weapon); /* * This is called from a fuse periodically when a weapon'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 weapon is silently put in place as the player's * weapon-in-use, warping it into the player's inventory if necessary. * If the player has attacked, this chance goes up to 50% (ie, in * combat it is far more likely for the curse to cause a switch). */ static void weapon_curse_tick(long int argli __attribute__((__unused__)), void *argvp __attribute__((__unused__))) { int n; OBJ *o; WEAPON *w; WLIST *wl; WLIST **wlp; INVOBJ *io; if (last_attack_w == you_attacked) { n = 100; } else { n = 2; last_attack_w = you_attacked; } if (onein(n)) { n = 0; wlp = &active_wcurse; while ((wl = *wlp)) { w = weapon_for_obj(wl->wo); if (w->flags & WF_CURSED) { if ((n < 1) || onein(n+1)) o = wl->wo; n ++; wlp = &wl->link; } else { w->flags &= ~WF_ACTCURSE; *wlp = wl->link; free(wl); } } if (n) { io = o->io; if (io->n > 1) io = inventory_split_specific(io,o,find_xwi(io->inv)); if (io->inv != &you->invent) { if (io->inv->type == IT_MON) weapon_stop_using(io->inv->u.m,io); inv_move_1(io->inv,io,&you->invent); } if (you->weapon != io->v[0]) weapon_wield(you,io->v[0]); } } if (active_wcurse) addfuse_rel(&weapon_curse_tick,0,0,TIMESCALE); } /* * Activate a weapon's curse, if it isn't already active. */ static void activate_wcurse(OBJ *o) { WEAPON *w; WLIST *wl; w = weapon_for_obj(o); last_attack_w = you_attacked; for (wl=active_wcurse;wl;wl=wl->link) { if (wl->wo == o) { if (w->flags & WF_ACTCURSE) return; panic("weapon in active curse list but not flagged ACTCURSE"); } } if (w->flags & WF_ACTCURSE) panic("weapon not in active curse list but flagged ACTCURSE"); w->flags |= WF_ACTCURSE; if (! active_wcurse) addfuse_rel(&weapon_curse_tick,0,0,TIMESCALE); wl = malloc(sizeof(WLIST)); wl->wo = o; wl->link = active_wcurse; active_wcurse = wl; } /* * Test whether a weapon is being wielded (return true) or not (false). */ int weapon_being_wielded(OBJ *o) { return(!!weapon_for_obj(o)->wielder); } /* * Make a monster wield a weapon. Anything currently wielded is * unwielded first; if o is nil, nothing is wielded. */ void weapon_wield(MONST *m, OBJ *o) { WEAPON *w; INVOBJ *io; int find_weapon(INVOBJ *io) { return(io->v[0]==m->weapon); } int find_it(INVOBJ *io) { return(io->v[0]==o); } if (o) { w = weapon_for_obj(o); if (w->wielder) panic("wielding already-wielded weapon"); } if (m->weapon) { WEAPON *ww; ww = weapon_for_obj(m->weapon); if (ww->wielder != m) panic("weapon wielder wrong"); io = inv_scan(&m->invent,&find_weapon); if (! io) panic("weapon isn't carried"); ww->wielder = 0; m->weapon = 0; inv_move_1(&m->invent,io,&m->invent); if (ww->eff) { effect_remove(m,ww->eff->eff); free(ww->eff); ww->eff = 0; } } m->weapon = o; if (o) { w->wielder = m; if ((w->flags & WF_CURSED) && (m == you)) { io = inv_scan(&you->invent,&find_it); if (! io) panic("wielding weapon not in inventory"); activate_wcurse(io->v[0]); } if (w->flags & WF_VAMP) { WEPEFFECT *e; e = malloc(sizeof(WEPEFFECT)); e->m = m; e->o = o; // Null out eff, and set it last, so that apply_weapno(), when // called from within effect_add(), finds e set up. e->eff = 0; e->eff = effect_add(m,e,&effops_weapon); w->eff = e; } } } /* * Return a weapon's to-hit bonus. */ int weapon_hit_bonus(OBJ *o) { WEAPON *w; if (! o) return(0); w = weapon_for_obj(o); return(roll(w->roll_base_hit)+roll(w->roll_extra_hit)); } /* * Return a weapon's damage bonus. */ int weapon_dmg_bonus(OBJ *o) { WEAPON *w; if (! o) return(0); w = weapon_for_obj(o); return(roll(w->roll_base_dmg)+roll(w->roll_extra_dmg)); } /* * Inventory test function for weapon_show(). */ static int weapon_show_test(INVOBJ *io) { if (objtypes[io->v[0]->type].class != OC_WEAPON) return(0); return(!!weapon_for_obj(io->v[0])->wielder); } /* * Show what weapon, if any, the monster is wielding. */ void weapon_show(MONST *m) { show_inventory(&m->invent,"",&weapon_show_test,"Not wielding anything"); } /* * Stop the monster from using the weapon object. The object must be a * weapon, must be carried by the monster, and must not be wielded by * any other monster, but is a no-op if none of those trip and the * weapon simply isn't wielded. */ void weapon_stop_using(MONST *m, INVOBJ *io) { WEAPON *w; w = weapon_for_obj(io->v[0]); if (io->inv != &m->invent) panic("weapon_stop_using, not in inventory"); if (! w->wielder) return; if (w->wielder != m) panic("weapon_stop_using, wielded by someone else"); if (m->weapon != io->v[0]) panic("weapon_stop_using, wielding something else"); w->wielder = 0; m->weapon = 0; } /* * Set a weapon's plusses. */ void weapon_set_plusses(OBJ *o, int plush, int plusd) { WEAPON *w; w = weapon_for_obj(o); w->bonus_hit = plush; w->bonus_dmg = plusd; reset_extra_rolls(w); } /* * Do an enchant-item on a weapon. * * At the moment, enchanting enchants only one of a group of weapons. * If we ever want it to, say, enchant a whole bunch of arrows at * once, this code will need some revising. */ void weapon_enchant(INVOBJ *io0) { WEAPON *w; INVOBJ *io; if (io0->dispn > 1) { io = inventory_split_n(io0,1,find_xwi(io->inv)); } else { io = io0; io0 = 0; } w = weapon_for_obj(io->v[0]); switch (rnd(5)) { case 0: case 1: w->bonus_hit += roll("10+6d6"); break; case 2: case 3: w->bonus_dmg += roll("10+6d6"); break; case 4: w->bonus_hit += roll("8+3d6"); w->bonus_dmg += roll("8+3d6"); break; } reset_extra_rolls(w); w->flags &= ~WF_KNOW_P; if (onein(3)) w->flags &= ~WF_CURSED; if (io0) inv_move_1(io->inv,io,io0->inv); }