From f2bae62eab73f0a0148df6162564ab5dd861124a Mon Sep 17 00:00:00 2001 From: jwalz Date: Sat, 5 Jan 2002 21:05:53 +0000 Subject: [PATCH] *** empty log message *** --- src/weapon.c | 1224 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1224 insertions(+) create mode 100644 src/weapon.c diff --git a/src/weapon.c b/src/weapon.c new file mode 100644 index 000000000..b35aaee27 --- /dev/null +++ b/src/weapon.c @@ -0,0 +1,1224 @@ +/* SCCS Id: @(#)weapon.c 3.3 2000/09/17 */ +/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ +/* NetHack may be freely redistributed. See license for details. */ + +/* + * This module contains code for calculation of "to hit" and damage + * bonuses for any given weapon used, as well as weapons selection + * code for monsters. + */ +#include "hack.h" + +/* Categories whose names don't come from OBJ_NAME(objects[type]) + */ +#define PN_BARE_HANDED (-1) /* includes martial arts */ +#define PN_TWO_WEAPONS (-2) +#define PN_RIDING (-3) +#define PN_POLEARMS (-4) +#define PN_SABER (-5) +#define PN_HAMMER (-6) +#define PN_WHIP (-7) +#define PN_ATTACK_SPELL (-8) +#define PN_HEALING_SPELL (-9) +#define PN_DIVINATION_SPELL (-10) +#define PN_ENCHANTMENT_SPELL (-11) +#define PN_CLERIC_SPELL (-12) +#define PN_ESCAPE_SPELL (-13) +#define PN_MATTER_SPELL (-14) + +#ifndef OVLB + +STATIC_DCL NEARDATA const short skill_names_indices[]; +STATIC_DCL NEARDATA const char *odd_skill_names[]; +STATIC_DCL NEARDATA const char *barehands_or_martial[]; + +#else /* OVLB */ + +STATIC_VAR NEARDATA const short skill_names_indices[P_NUM_SKILLS] = { + 0, DAGGER, KNIFE, AXE, + PICK_AXE, SHORT_SWORD, BROADSWORD, LONG_SWORD, + TWO_HANDED_SWORD, SCIMITAR, PN_SABER, CLUB, + MACE, MORNING_STAR, FLAIL, + PN_HAMMER, QUARTERSTAFF, PN_POLEARMS, SPEAR, + JAVELIN, TRIDENT, LANCE, BOW, + SLING, CROSSBOW, DART, + SHURIKEN, BOOMERANG, PN_WHIP, UNICORN_HORN, + PN_ATTACK_SPELL, PN_HEALING_SPELL, + PN_DIVINATION_SPELL, PN_ENCHANTMENT_SPELL, + PN_CLERIC_SPELL, PN_ESCAPE_SPELL, + PN_MATTER_SPELL, + PN_BARE_HANDED, PN_TWO_WEAPONS, +#ifdef STEED + PN_RIDING +#endif +}; + +/* note: entry [0] isn't used */ +STATIC_VAR NEARDATA const char *odd_skill_names[] = { + "no skill", + "bare hands", /* use barehands_or_martial[] instead */ + "two weapon combat", + "riding", + "polearms", + "saber", + "hammer", + "whip", + "attack spells", + "healing spells", + "divination spells", + "enchantment spells", + "clerical spells", + "escape spells", + "matter spells", +}; +/* indexed vis `is_martial() */ +STATIC_VAR NEARDATA const char *barehands_or_martial[] = { + "bare handed combat", "martial arts" +}; + +STATIC_OVL +void give_may_advance_msg(skill) +int skill; +{ + You_feel("more confident in your %sskills.", + skill == P_NONE ? + "" : + skill <= P_LAST_WEAPON ? + "weapon " : + skill <= P_LAST_SPELL ? + "spell casting " : + "fighting "); +} + +#endif /* OVLB */ + +STATIC_DCL boolean FDECL(can_advance, (int, BOOLEAN_P)); +STATIC_DCL int FDECL(slots_required, (int)); + +#ifdef OVL1 + +STATIC_DCL char *FDECL(skill_level_name, (int,char *)); +STATIC_DCL void FDECL(skill_advance, (int)); + +#endif /* OVL1 */ + +#define P_NAME(type) ((skill_names_indices[type] > 0) ? \ + OBJ_NAME(objects[skill_names_indices[type]]) : \ + (type == P_BARE_HANDED_COMBAT) ? \ + barehands_or_martial[martial_bonus()] : \ + odd_skill_names[-skill_names_indices[type]]) + +#ifdef OVLB +static NEARDATA const char kebabable[] = { + S_XORN, S_DRAGON, S_JABBERWOCK, S_NAGA, S_GIANT, '\0' +}; + +/* + * hitval returns an integer representing the "to hit" bonuses + * of "otmp" against the monster. + */ +int +hitval(otmp, mon) +struct obj *otmp; +struct monst *mon; +{ + int tmp = 0; + struct permonst *ptr = mon->data; + boolean Is_weapon = (otmp->oclass == WEAPON_CLASS || is_weptool(otmp)); + + if (Is_weapon) + tmp += otmp->spe; + +/* Put weapon specific "to hit" bonuses in below: */ + tmp += objects[otmp->otyp].oc_hitbon; + +/* Put weapon vs. monster type "to hit" bonuses in below: */ + + /* Blessed weapons used against undead or demons */ + if (Is_weapon && otmp->blessed && + (is_demon(ptr) || is_undead(ptr))) tmp += 2; + + if (is_spear(otmp) && + index(kebabable, ptr->mlet)) tmp += 2; + + /* trident is highly effective against swimmers */ + if (otmp->otyp == TRIDENT && is_swimmer(ptr)) { + if (is_pool(mon->mx, mon->my)) tmp += 4; + else if (ptr->mlet == S_EEL || ptr->mlet == S_SNAKE) tmp += 2; + } + + /* Picks used against xorns and earth elementals */ + if (is_pick(otmp) && + (passes_walls(ptr) && thick_skinned(ptr))) tmp += 2; + +#ifdef INVISIBLE_OBJECTS + /* Invisible weapons against monsters who can't see invisible */ + if (otmp->oinvis && !perceives(ptr)) tmp += 3; +#endif + + /* Check specially named weapon "to hit" bonuses */ + if (otmp->oartifact) tmp += spec_abon(otmp, mon); + + return tmp; +} + +/* Historical note: The original versions of Hack used a range of damage + * which was similar to, but not identical to the damage used in Advanced + * Dungeons and Dragons. I figured that since it was so close, I may as well + * make it exactly the same as AD&D, adding some more weapons in the process. + * This has the advantage that it is at least possible that the player would + * already know the damage of at least some of the weapons. This was circa + * 1987 and I used the table from Unearthed Arcana until I got tired of typing + * them in (leading to something of an imbalance towards weapons early in + * alphabetical order). The data structure still doesn't include fields that + * fully allow the appropriate damage to be described (there's no way to say + * 3d6 or 1d6+1) so we add on the extra damage in dmgval() if the weapon + * doesn't do an exact die of damage. + * + * Of course new weapons were added later in the development of Nethack. No + * AD&D consistency was kept, but most of these don't exist in AD&D anyway. + * + * Second edition AD&D came out a few years later; luckily it used the same + * table. As of this writing (1999), third edition is in progress but not + * released. Let's see if the weapon table stays the same. --KAA + * October 2000: It didn't. Oh, well. + */ + +/* + * dmgval returns an integer representing the damage bonuses + * of "otmp" against the monster. + */ +int +dmgval(otmp, mon) +struct obj *otmp; +struct monst *mon; +{ + int tmp = 0, otyp = otmp->otyp; + struct permonst *ptr = mon->data; + boolean Is_weapon = (otmp->oclass == WEAPON_CLASS || is_weptool(otmp)); + + if (otyp == CREAM_PIE) return 0; + + if (bigmonst(ptr)) { + if (objects[otyp].oc_wldam) + tmp = rnd(objects[otyp].oc_wldam); + switch (otyp) { + case IRON_CHAIN: + case CROSSBOW_BOLT: + case MORNING_STAR: + case PARTISAN: + case RUNESWORD: + case ELVEN_BROADSWORD: + case BROADSWORD: tmp++; break; + + case FLAIL: + case RANSEUR: + case VOULGE: tmp += rnd(4); break; + + case ACID_VENOM: + case HALBERD: + case SPETUM: tmp += rnd(6); break; + + case BATTLE_AXE: + case BARDICHE: + case TRIDENT: tmp += d(2,4); break; + + case TSURUGI: + case DWARVISH_MATTOCK: + case TWO_HANDED_SWORD: tmp += d(2,6); break; + } + } else { + if (objects[otyp].oc_wsdam) + tmp = rnd(objects[otyp].oc_wsdam); + switch (otyp) { + case IRON_CHAIN: + case CROSSBOW_BOLT: + case MACE: + case WAR_HAMMER: + case FLAIL: + case SPETUM: + case TRIDENT: tmp++; break; + + case BATTLE_AXE: + case BARDICHE: + case BILL_GUISARME: + case GUISARME: + case LUCERN_HAMMER: + case MORNING_STAR: + case RANSEUR: + case BROADSWORD: + case ELVEN_BROADSWORD: + case RUNESWORD: + case VOULGE: tmp += rnd(4); break; + + case ACID_VENOM: tmp += rnd(6); break; + } + } + if (Is_weapon) { + tmp += otmp->spe; + /* negative enchantment mustn't produce negative damage */ + if (tmp < 0) tmp = 0; + } + + if (objects[otyp].oc_material <= LEATHER && thick_skinned(ptr)) + /* thick skinned/scaled creatures don't feel it */ + tmp = 0; + if (ptr == &mons[PM_SHADE] && objects[otyp].oc_material != SILVER) + tmp = 0; + + /* "very heavy iron ball"; weight increase is in increments of 160 */ + if (otyp == HEAVY_IRON_BALL && tmp > 0) { + int wt = (int)objects[HEAVY_IRON_BALL].oc_weight; + + if ((int)otmp->owt > wt) { + wt = ((int)otmp->owt - wt) / 160; + tmp += rnd(4 * wt); + if (tmp > 25) tmp = 25; /* objects[].oc_wldam */ + } + } + +/* Put weapon vs. monster type damage bonuses in below: */ + if (Is_weapon || otmp->oclass == GEM_CLASS || + otmp->oclass == BALL_CLASS || otmp->oclass == CHAIN_CLASS) { + int bonus = 0; + +#ifdef STEED + /* KMH -- Lances are especially made for riding */ + if (otmp == uwep && u.usteed && + objects[otmp->otyp].oc_skill == P_LANCE) + bonus += d(2,10); +#endif + + if (otmp->blessed && (is_undead(ptr) || is_demon(ptr))) + bonus += rnd(4); + if (is_axe(otmp) && is_wooden(ptr)) + bonus += rnd(4); + if (objects[otyp].oc_material == SILVER && hates_silver(ptr)) + bonus += rnd(20); + + /* if the weapon is going to get a double damage bonus, adjust + this bonus so that effectively it's added after the doubling */ + if (bonus > 1 && otmp->oartifact && spec_dbon(otmp, mon, 25) >= 25) + bonus = (bonus + 1) / 2; + + tmp += bonus; + } + + if (tmp > 0) { + /* It's debateable whether a rusted blunt instrument + should do less damage than a pristine one, since + it will hit with essentially the same impact, but + there ought to some penalty for using damaged gear + so always subtract erosion even for blunt weapons. */ + tmp -= greatest_erosion(otmp); + if (tmp < 1) tmp = 1; + } + + return(tmp); +} + +#endif /* OVLB */ +#ifdef OVL0 + +STATIC_DCL struct obj *FDECL(oselect, (struct monst *,int)); +#define Oselect(x) if ((otmp = oselect(mtmp, x)) != 0) return(otmp); + +STATIC_OVL struct obj * +oselect(mtmp, x) +struct monst *mtmp; +int x; +{ + struct obj *otmp; + + for (otmp = mtmp->minvent; otmp; otmp = otmp->nobj) { + if (otmp->otyp == x && + /* never select non-cockatrice corpses */ + !((x == CORPSE || x == EGG) && + !touch_petrifies(&mons[otmp->corpsenm])) && + (!otmp->oartifact || touch_artifact(otmp,mtmp))) + return otmp; + } + return (struct obj *)0; +} + +static NEARDATA const int rwep[] = +{ DWARVISH_SPEAR, SILVER_SPEAR, ELVEN_SPEAR, SPEAR, ORCISH_SPEAR, + JAVELIN, SHURIKEN, YA, SILVER_ARROW, ELVEN_ARROW, ARROW, + ORCISH_ARROW, CROSSBOW_BOLT, SILVER_DAGGER, ELVEN_DAGGER, DAGGER, + ORCISH_DAGGER, KNIFE, FLINT, ROCK, LOADSTONE, LUCKSTONE, DART, + /* BOOMERANG, */ CREAM_PIE + /* note: CREAM_PIE should NOT be #ifdef KOPS */ +}; + +static NEARDATA const int pwep[] = +{ HALBERD, BARDICHE, SPETUM, BILL_GUISARME, VOULGE, RANSEUR, GUISARME, + GLAIVE, LUCERN_HAMMER, BEC_DE_CORBIN, FAUCHARD, PARTISAN, LANCE +}; + +static struct obj *propellor; + +struct obj * +select_rwep(mtmp) /* select a ranged weapon for the monster */ +register struct monst *mtmp; +{ + register struct obj *otmp; + int i; + +#ifdef KOPS + char mlet = mtmp->data->mlet; +#endif + + propellor = &zeroobj; + Oselect(EGG); /* cockatrice egg */ +#ifdef KOPS + if(mlet == S_KOP) /* pies are first choice for Kops */ + Oselect(CREAM_PIE); +#endif + if(throws_rocks(mtmp->data)) /* ...boulders for giants */ + Oselect(BOULDER); + + /* Select polearms first; they do more damage and aren't expendable */ + /* The limit of 13 here is based on the monster polearm range limit + * (defined as 5 in mthrowu.c). 5 corresponds to a distance of 2 in + * one direction and 1 in another; one space beyond that would be 3 in + * one direction and 2 in another; 3^2+2^2=13. + */ + if (dist2(mtmp->mx, mtmp->my, mtmp->mux, mtmp->muy) <= 13 && couldsee(mtmp->mx, mtmp->my)) { + for (i = 0; i < SIZE(pwep); i++) { + /* Only strong monsters can wield big (esp. long) weapons. + * Big weapon is basically the same as bimanual. + * All monsters can wield the remaining weapons. + */ + if (((strongmonst(mtmp->data) && (mtmp->misc_worn_check & W_ARMS) == 0) + || !objects[pwep[i]].oc_bimanual) && + (objects[pwep[i]].oc_material != SILVER + || !hates_silver(mtmp->data))) { + if ((otmp = oselect(mtmp, pwep[i])) != 0) { + propellor = otmp; /* force the monster to wield it */ + return otmp; + } + } + } + } + + /* + * other than these two specific cases, always select the + * most potent ranged weapon to hand. + */ + for (i = 0; i < SIZE(rwep); i++) { + int prop; + + /* shooting gems from slings; this goes just before the darts */ + /* (shooting rocks is already handled via the rwep[] ordering) */ + if (rwep[i] == DART && !likes_gems(mtmp->data) && + m_carrying(mtmp, SLING)) { /* propellor */ + for (otmp = mtmp->minvent; otmp; otmp = otmp->nobj) + if (otmp->oclass == GEM_CLASS && + (otmp->otyp != LOADSTONE || !otmp->cursed)) { + propellor = m_carrying(mtmp, SLING); + return otmp; + } + } + + /* KMH -- This belongs here so darts will work */ + propellor = &zeroobj; + + prop = (objects[rwep[i]]).oc_skill; + if (prop < 0) { + switch (-prop) { + case P_BOW: + propellor = (oselect(mtmp, YUMI)); + if (!propellor) propellor = (oselect(mtmp, ELVEN_BOW)); + if (!propellor) propellor = (oselect(mtmp, BOW)); + if (!propellor) propellor = (oselect(mtmp, ORCISH_BOW)); + break; + case P_SLING: + propellor = (oselect(mtmp, SLING)); + break; + case P_CROSSBOW: + propellor = (oselect(mtmp, CROSSBOW)); + } + if ((otmp = MON_WEP(mtmp)) && otmp->cursed && otmp != propellor + && mtmp->weapon_check == NO_WEAPON_WANTED) + propellor = 0; + } + /* propellor = obj, propellor to use + * propellor = &zeroobj, doesn't need a propellor + * propellor = 0, needed one and didn't have one + */ + if (propellor != 0) { + /* Note: cannot use m_carrying for loadstones, since it will + * always select the first object of a type, and maybe the + * monster is carrying two but only the first is unthrowable. + */ + if (rwep[i] != LOADSTONE) { + /* Don't throw a cursed weapon-in-hand or an artifact */ + if ((otmp = oselect(mtmp, rwep[i])) && !otmp->oartifact + && (!otmp->cursed || otmp != MON_WEP(mtmp))) + return(otmp); + } else for(otmp=mtmp->minvent; otmp; otmp=otmp->nobj) { + if (otmp->otyp == LOADSTONE && !otmp->cursed) + return otmp; + } + } + } + + /* failure */ + return (struct obj *)0; +} + +/* Weapons in order of preference */ +static NEARDATA short hwep[] = { + CORPSE, /* cockatrice corpse */ + TSURUGI, RUNESWORD, DWARVISH_MATTOCK, TWO_HANDED_SWORD, BATTLE_AXE, + KATANA, UNICORN_HORN, CRYSKNIFE, TRIDENT, LONG_SWORD, + ELVEN_BROADSWORD, BROADSWORD, SCIMITAR, SILVER_SABER, + MORNING_STAR, ELVEN_SHORT_SWORD, DWARVISH_SHORT_SWORD, SHORT_SWORD, + ORCISH_SHORT_SWORD, MACE, AXE, DWARVISH_SPEAR, SILVER_SPEAR, + ELVEN_SPEAR, SPEAR, ORCISH_SPEAR, FLAIL, BULLWHIP, QUARTERSTAFF, + JAVELIN, AKLYS, CLUB, PICK_AXE, +#ifdef KOPS + RUBBER_HOSE, +#endif /* KOPS */ + WAR_HAMMER, SILVER_DAGGER, ELVEN_DAGGER, DAGGER, ORCISH_DAGGER, + ATHAME, SCALPEL, KNIFE, WORM_TOOTH + }; + +struct obj * +select_hwep(mtmp) /* select a hand to hand weapon for the monster */ +register struct monst *mtmp; +{ + register struct obj *otmp; + register int i; + boolean strong = strongmonst(mtmp->data); + boolean wearing_shield = (mtmp->misc_worn_check & W_ARMS) != 0; + + /* prefer artifacts to everything else */ + for(otmp=mtmp->minvent; otmp; otmp = otmp->nobj) { + if (otmp->oclass == WEAPON_CLASS + && otmp->oartifact && touch_artifact(otmp,mtmp) + && ((strong && !wearing_shield) + || !objects[otmp->otyp].oc_bimanual)) + return otmp; + } + + if(is_giant(mtmp->data)) /* giants just love to use clubs */ + Oselect(CLUB); + + /* only strong monsters can wield big (esp. long) weapons */ + /* big weapon is basically the same as bimanual */ + /* all monsters can wield the remaining weapons */ + for (i = 0; i < SIZE(hwep); i++) { + if (hwep[i] == CORPSE && !(mtmp->misc_worn_check & W_ARMG)) + continue; + if (((strong && !wearing_shield) + || !objects[hwep[i]].oc_bimanual) && + (objects[hwep[i]].oc_material != SILVER + || !hates_silver(mtmp->data))) + Oselect(hwep[i]); + } + + /* failure */ + return (struct obj *)0; +} + +/* Called after polymorphing a monster, robbing it, etc.... Monsters + * otherwise never unwield stuff on their own. Shouldn't print messages. + */ +void +possibly_unwield(mon) +register struct monst *mon; +{ + register struct obj *obj; + struct obj *mw_tmp; + + if (!(mw_tmp = MON_WEP(mon))) + return; + for(obj=mon->minvent; obj; obj=obj->nobj) + if (obj == mw_tmp) break; + if (!obj) { /* The weapon was stolen or destroyed */ + MON_NOWEP(mon); + mon->weapon_check = NEED_WEAPON; + return; + } + if (!attacktype(mon->data, AT_WEAP)) { + setmnotwielded(mon, mw_tmp); + MON_NOWEP(mon); + mon->weapon_check = NO_WEAPON_WANTED; + obj_extract_self(obj); + /* flooreffects unnecessary, can't wield boulders */ + place_object(obj, mon->mx, mon->my); + stackobj(obj); + if (cansee(mon->mx, mon->my)) { + pline("%s drops %s.", Monnam(mon), + distant_name(obj, doname)); + newsym(mon->mx, mon->my); + } + return; + } + /* The remaining case where there is a change is where a monster + * is polymorphed into a stronger/weaker monster with a different + * choice of weapons. This has no parallel for players. It can + * be handled by waiting until mon_wield_item is actually called. + * Though the monster still wields the wrong weapon until then, + * this is OK since the player can't see it. (FIXME: Not okay since + * probing can reveal it.) + * Note that if there is no change, setting the check to NEED_WEAPON + * is harmless. + * Possible problem: big monster with big cursed weapon gets + * polymorphed into little monster. But it's not quite clear how to + * handle this anyway.... + */ + if (!(mw_tmp->cursed && mon->weapon_check == NO_WEAPON_WANTED)) + mon->weapon_check = NEED_WEAPON; +} + +/* Let a monster try to wield a weapon, based on mon->weapon_check. + * Returns 1 if the monster took time to do it, 0 if it did not. + */ +int +mon_wield_item(mon) +register struct monst *mon; +{ + struct obj *obj; + + /* This case actually should never happen */ + if (mon->weapon_check == NO_WEAPON_WANTED) return 0; + switch(mon->weapon_check) { + case NEED_HTH_WEAPON: + obj = select_hwep(mon); + break; + case NEED_RANGED_WEAPON: + (void)select_rwep(mon); + obj = propellor; + break; + case NEED_PICK_AXE: + obj = m_carrying(mon, PICK_AXE); + /* KMH -- allow other picks */ + if (!obj) obj = m_carrying(mon, DWARVISH_MATTOCK); + break; + default: impossible("weapon_check %d for %s?", + mon->weapon_check, mon_nam(mon)); + return 0; + } + if (obj && obj != &zeroobj) { + struct obj *mw_tmp = MON_WEP(mon); + if (mw_tmp && mw_tmp->otyp == obj->otyp) { + /* already wielding it */ + mon->weapon_check = NEED_WEAPON; + return 0; + } + /* Actually, this isn't necessary--as soon as the monster + * wields the weapon, the weapon welds itself, so the monster + * can know it's cursed and needn't even bother trying. + * Still.... + */ + if (mw_tmp && mw_tmp->cursed && mw_tmp->otyp != CORPSE) { + if (canseemon(mon)) { + char welded_buf[BUFSZ]; + const char *mon_hand = mbodypart(mon, HAND); + + if (bimanual(mw_tmp)) mon_hand = makeplural(mon_hand); + Sprintf(welded_buf, "%s welded to %s %s", + (mw_tmp->quan == 1L) ? "is" : "are", + mhis(mon), mon_hand); + + if (obj->otyp == PICK_AXE) { + pline("Since %s weapon%s %s,", + s_suffix(mon_nam(mon)), + plur(mw_tmp->quan), welded_buf); + pline("%s cannot wield that %s.", + mon_nam(mon), xname(obj)); + } else { + pline("%s tries to wield %s.", Monnam(mon), + doname(obj)); + pline("%s %s %s!", + s_suffix(Monnam(mon)), + xname(mw_tmp), welded_buf); + } + mw_tmp->bknown = 1; + } + mon->weapon_check = NO_WEAPON_WANTED; + return 1; + } + mon->mw = obj; /* wield obj */ + setmnotwielded(mon, mw_tmp); + mon->weapon_check = NEED_WEAPON; + if (canseemon(mon)) { + pline("%s wields %s!", Monnam(mon), doname(obj)); + if (obj->cursed && obj->otyp != CORPSE) { + pline("%s %s to %s %s!", + The(xname(obj)), + (obj->quan == 1L) ? "welds itself" + : "weld themselves", + s_suffix(mon_nam(mon)), mbodypart(mon,HAND)); + obj->bknown = 1; + } + } + if (artifact_light(obj) && !obj->lamplit) { + begin_burn(obj, FALSE); + if (canseemon(mon)) + pline("%s glows brilliantly in %s %s!", + The(xname(obj)), + s_suffix(mon_nam(mon)), mbodypart(mon,HAND)); + } + obj->owornmask = W_WEP; + return 1; + } + mon->weapon_check = NEED_WEAPON; + return 0; +} + +int +abon() /* attack bonus for strength & dexterity */ +{ + int sbon; + int str = ACURR(A_STR), dex = ACURR(A_DEX); + + if (Upolyd) return(adj_lev(&mons[u.umonnum]) - 3); + if (str < 6) sbon = -2; + else if (str < 8) sbon = -1; + else if (str < 17) sbon = 0; + else if (str <= STR18(50)) sbon = 1; /* up to 18/50 */ + else if (str < STR18(100)) sbon = 2; + else sbon = 3; + +/* Game tuning kludge: make it a bit easier for a low level character to hit */ + sbon += (u.ulevel < 3) ? 1 : 0; + + if (dex < 4) return(sbon-3); + else if (dex < 6) return(sbon-2); + else if (dex < 8) return(sbon-1); + else if (dex < 14) return(sbon); + else return(sbon + dex-14); +} + +#endif /* OVL0 */ +#ifdef OVL1 + +int +dbon() /* damage bonus for strength */ +{ + int str = ACURR(A_STR); + + if (Upolyd) return(0); + + if (str < 6) return(-1); + else if (str < 16) return(0); + else if (str < 18) return(1); + else if (str == 18) return(2); /* up to 18 */ + else if (str <= STR18(75)) return(3); /* up to 18/75 */ + else if (str <= STR18(90)) return(4); /* up to 18/90 */ + else if (str < STR18(100)) return(5); /* up to 18/99 */ + else return(6); +} + + +/* copy the skill level name into the given buffer */ +STATIC_OVL char * +skill_level_name(skill, buf) +int skill; +char *buf; +{ + const char *ptr; + + switch (P_SKILL(skill)) { + case P_UNSKILLED: ptr = "Unskilled"; break; + case P_BASIC: ptr = "Basic"; break; + case P_SKILLED: ptr = "Skilled"; break; + case P_EXPERT: ptr = "Expert"; break; + /* these are for unarmed combat/martial arts only */ + case P_MASTER: ptr = "Master"; break; + case P_GRAND_MASTER: ptr = "Grand Master"; break; + default: ptr = "Unknown"; break; + } + Strcpy(buf, ptr); + return buf; +} + +/* return the # of slots required to advance the skill */ +STATIC_OVL int +slots_required(skill) +int skill; +{ + int tmp = P_SKILL(skill); + + /* The more difficult the training, the more slots it takes. + * unskilled -> basic 1 + * basic -> skilled 2 + * skilled -> expert 3 + */ + if (skill <= P_LAST_WEAPON || skill == P_TWO_WEAPON_COMBAT) + return tmp; + + /* Fewer slots used up for unarmed or martial. + * unskilled -> basic 1 + * basic -> skilled 1 + * skilled -> expert 2 + * expert -> master 2 + * master -> grand master 3 + */ + return (tmp + 1) / 2; +} + +/* return true if this skill can be advanced */ +STATIC_OVL boolean +can_advance(skill, speedy) +int skill; +boolean speedy; +{ + return !P_RESTRICTED(skill) + && P_SKILL(skill) < P_MAX_SKILL(skill) && ( +#ifdef WIZARD + (wizard && speedy) || +#endif + (P_ADVANCE(skill) >= + (unsigned) practice_needed_to_advance(P_SKILL(skill)) + && u.skills_advanced < P_SKILL_LIMIT + && u.weapon_slots >= slots_required(skill))); +} + +STATIC_OVL void +skill_advance(skill) +int skill; +{ + u.weapon_slots -= slots_required(skill); + P_SKILL(skill)++; + u.skill_record[u.skills_advanced++] = skill; + /* subtly change the advance message to indicate no more advancement */ + You("are now %s skilled in %s.", + P_SKILL(skill) >= P_MAX_SKILL(skill) ? "most" : "more", + P_NAME(skill)); +} + +static struct skill_range { + short first, last; + const char *name; +} skill_ranges[] = { + { P_FIRST_H_TO_H, P_LAST_H_TO_H, "Fighting Skills" }, + { P_FIRST_WEAPON, P_LAST_WEAPON, "Weapon Skills" }, + { P_FIRST_SPELL, P_LAST_SPELL, "Spellcasting Skills" }, +}; + +/* + * The `#enhance' extended command. What we _really_ would like is + * to keep being able to pick things to advance until we couldn't any + * more. This is currently not possible -- the menu code has no way + * to call us back for instant action. Even if it did, we would also need + * to be able to update the menu since selecting one item could make + * others unselectable. + */ +int +enhance_weapon_skill() +{ + int pass, i, n, len, longest, to_advance; + char buf[BUFSZ], buf2[BUFSZ]; + menu_item *selected; + anything any; + winid win; + boolean speedy = FALSE; + + +#ifdef WIZARD + if (wizard && yn("Advance skills without practice?") == 'y') + speedy = TRUE; +#endif + + do { + /* find longest available skill name, count those that can advance */ + for (longest = 0, to_advance = 0, i = 0; i < P_NUM_SKILLS; i++) { + if (!P_RESTRICTED(i) && (len = strlen(P_NAME(i))) > longest) + longest = len; + if (can_advance(i, speedy)) to_advance++; + } + + win = create_nhwindow(NHW_MENU); + start_menu(win); + + /* List the skills, making ones that could be advanced + selectable. List the miscellaneous skills first. + Possible future enhancement: list spell skills before + weapon skills for spellcaster roles. */ + for (pass = 0; pass < SIZE(skill_ranges); pass++) + for (i = skill_ranges[pass].first; + i <= skill_ranges[pass].last; i++) { + /* Print headings for skill types */ + any.a_void = 0; + if (i == skill_ranges[pass].first) + add_menu(win, NO_GLYPH, &any, 0, 0, ATR_BOLD, + skill_ranges[pass].name, MENU_UNSELECTED); + + if (P_RESTRICTED(i)) continue; + /* + * Sigh, this assumes a monospaced font. + * The 12 is the longest skill level name. + * The " " is room for a selection letter and dash, "a - ". + */ +#ifdef WIZARD + if (wizard) + Sprintf(buf2, " %s%-*s %-12s %4d(%4d)", + to_advance == 0 || can_advance(i, speedy) ? "" : " " , + longest, P_NAME(i), + skill_level_name(i, buf), + P_ADVANCE(i), practice_needed_to_advance(P_SKILL(i))); + else +#endif + Sprintf(buf2, " %s %-*s [%s]", + to_advance == 0 || can_advance(i, speedy) ? "" : " ", + longest, P_NAME(i), + skill_level_name(i, buf)); + + any.a_int = can_advance(i, speedy) ? i+1 : 0; + add_menu(win, NO_GLYPH, &any, 0, 0, ATR_NONE, buf2, MENU_UNSELECTED); + } + + Strcpy(buf, to_advance ? "Pick a skill to advance:" : "Current skills:"); +#ifdef WIZARD + if (wizard && !speedy) Sprintf(eos(buf), " (%d slot%s available)", + u.weapon_slots, plur(u.weapon_slots)); +#endif + end_menu(win, buf); + n = select_menu(win, to_advance ? PICK_ONE : PICK_NONE, &selected); + destroy_nhwindow(win); + if (n > 0) { + n = selected[0].item.a_int - 1; /* get item selected */ + free((genericptr_t)selected); + skill_advance(n); + /* check for more skills able to advance, if so then .. */ + for (n = i = 0; i < P_NUM_SKILLS; i++) { + if (can_advance(i, speedy)) { + if (!speedy) You_feel("you could be more dangerous!"); + n++; + break; + } + } + } + } while (speedy && n > 0); + return 0; +} + +/* + * Change from restricted to unrestricted, allowing P_BASIC as max. This + * function may be called with with P_NONE. Used in pray.c. + */ +void +unrestrict_weapon_skill(skill) +int skill; +{ + if (skill < P_NUM_SKILLS && P_RESTRICTED(skill)) { + P_SKILL(skill) = P_UNSKILLED; + P_MAX_SKILL(skill) = P_BASIC; + P_ADVANCE(skill) = 0; + } +} + +#endif /* OVL1 */ +#ifdef OVLB + +void +use_skill(skill,degree) +int skill; +int degree; +{ + boolean advance_before; + + if (skill != P_NONE && !P_RESTRICTED(skill)) { + advance_before = can_advance(skill, FALSE); + P_ADVANCE(skill)+=degree; + if (!advance_before && can_advance(skill, FALSE)) + give_may_advance_msg(skill); + } +} + +void +add_weapon_skill(n) +int n; /* number of slots to gain; normally one */ +{ + int i, before, after; + + for (i = 0, before = 0; i < P_NUM_SKILLS; i++) + if (can_advance(i, FALSE)) before++; + u.weapon_slots += n; + for (i = 0, after = 0; i < P_NUM_SKILLS; i++) + if (can_advance(i, FALSE)) after++; + if (before < after) + give_may_advance_msg(P_NONE); +} + +void +lose_weapon_skill(n) +int n; /* number of slots to lose; normally one */ +{ + int skill; + + while (--n >= 0) { + /* deduct first from unused slots, then from last placed slot, if any */ + if (u.weapon_slots) { + u.weapon_slots--; + } else if (u.skills_advanced) { + skill = u.skill_record[--u.skills_advanced]; + if (P_SKILL(skill) <= P_UNSKILLED) + panic("lose_weapon_skill (%d)", skill); + P_SKILL(skill)--; /* drop skill one level */ + /* Lost skill might have taken more than one slot; refund rest. */ + u.weapon_slots = slots_required(skill) - 1; + /* It might now be possible to advance some other pending + skill by using the refunded slots, but giving a message + to that effect would seem pretty confusing.... */ + } + } +} + +int +weapon_type(obj) +struct obj *obj; +{ + /* KMH -- now uses the object table */ + int type; + + if (!obj) + /* Not using a weapon */ + return (P_BARE_HANDED_COMBAT); + if (obj->oclass != WEAPON_CLASS && obj->oclass != TOOL_CLASS && + obj->oclass != GEM_CLASS) + /* Not a weapon, weapon-tool, or ammo */ + return (P_NONE); + type = objects[obj->otyp].oc_skill; + return ((type < 0) ? -type : type); +} + +int +uwep_skill_type() +{ + if (u.twoweap) + return P_TWO_WEAPON_COMBAT; + return weapon_type(uwep); +} + +/* + * Return hit bonus/penalty based on skill of weapon. + * Treat restricted weapons as unskilled. + */ +int +weapon_hit_bonus(weapon) +struct obj *weapon; +{ + int type, wep_type, skill, bonus = 0; + static const char bad_skill[] = "weapon_hit_bonus: bad skill %d"; + + wep_type = weapon_type(weapon); + /* use two weapon skill only if attacking with one of the wielded weapons */ + type = (u.twoweap && (weapon == uwep || weapon == uswapwep)) ? + P_TWO_WEAPON_COMBAT : wep_type; + if (type == P_NONE) { + bonus = 0; + } else if (type <= P_LAST_WEAPON) { + switch (P_SKILL(type)) { + default: impossible(bad_skill, P_SKILL(type)); /* fall through */ + case P_ISRESTRICTED: + case P_UNSKILLED: bonus = -4; break; + case P_BASIC: bonus = 0; break; + case P_SKILLED: bonus = 2; break; + case P_EXPERT: bonus = 3; break; + } + } else if (type == P_TWO_WEAPON_COMBAT) { + skill = P_SKILL(P_TWO_WEAPON_COMBAT); + if (P_SKILL(wep_type) < skill) skill = P_SKILL(wep_type); + switch (skill) { + default: impossible(bad_skill, skill); /* fall through */ + case P_ISRESTRICTED: + case P_UNSKILLED: bonus = -9; break; + case P_BASIC: bonus = -7; break; + case P_SKILLED: bonus = -5; break; + case P_EXPERT: bonus = -3; break; + } + } else if (type == P_BARE_HANDED_COMBAT) { + /* + * b.h. m.a. + * unskl: +1 n/a + * basic: +1 +3 + * skild: +2 +4 + * exprt: +2 +5 + * mastr: +3 +6 + * grand: +3 +7 + */ + bonus = P_SKILL(type); + bonus = max(bonus,P_UNSKILLED) - 1; /* unskilled => 0 */ + bonus = (bonus * (martial_bonus() ? 2 : 1)) / 2; + } + +#ifdef STEED + /* KMH -- It's harder to hit while you are riding */ + if (u.usteed) { + switch (P_SKILL(P_RIDING)) { + case P_ISRESTRICTED: + case P_UNSKILLED: bonus -= 2; break; + case P_BASIC: bonus -= 1; break; + case P_SKILLED: break; + case P_EXPERT: break; + } + if (u.twoweap) bonus -= 2; + } +#endif + + return bonus; +} + +/* + * Return damage bonus/penalty based on skill of weapon. + * Treat restricted weapons as unskilled. + */ +int +weapon_dam_bonus(weapon) +struct obj *weapon; +{ + int type, wep_type, skill, bonus = 0; + + wep_type = weapon_type(weapon); + /* use two weapon skill only if attacking with one of the wielded weapons */ + type = (u.twoweap && (weapon == uwep || weapon == uswapwep)) ? + P_TWO_WEAPON_COMBAT : wep_type; + if (type == P_NONE) { + bonus = 0; + } else if (type <= P_LAST_WEAPON) { + switch (P_SKILL(type)) { + default: impossible("weapon_dam_bonus: bad skill %d",P_SKILL(type)); + /* fall through */ + case P_ISRESTRICTED: + case P_UNSKILLED: bonus = -2; break; + case P_BASIC: bonus = 0; break; + case P_SKILLED: bonus = 1; break; + case P_EXPERT: bonus = 2; break; + } + } else if (type == P_TWO_WEAPON_COMBAT) { + skill = P_SKILL(P_TWO_WEAPON_COMBAT); + if (P_SKILL(wep_type) < skill) skill = P_SKILL(wep_type); + switch (skill) { + default: + case P_ISRESTRICTED: + case P_UNSKILLED: bonus = -3; break; + case P_BASIC: bonus = -1; break; + case P_SKILLED: bonus = 0; break; + case P_EXPERT: bonus = 1; break; + } + } else if (type == P_BARE_HANDED_COMBAT) { + /* + * b.h. m.a. + * unskl: 0 n/a + * basic: +1 +3 + * skild: +1 +4 + * exprt: +2 +6 + * mastr: +2 +7 + * grand: +3 +9 + */ + bonus = P_SKILL(type); + bonus = max(bonus,P_UNSKILLED) - 1; /* unskilled => 0 */ + bonus = ((bonus + 1) * (martial_bonus() ? 3 : 1)) / 2; + } + +#ifdef STEED + /* KMH -- Riding gives some thrusting damage */ + if (u.usteed && type != P_TWO_WEAPON_COMBAT) { + switch (P_SKILL(P_RIDING)) { + case P_ISRESTRICTED: + case P_UNSKILLED: break; + case P_BASIC: break; + case P_SKILLED: bonus += 1; break; + case P_EXPERT: bonus += 2; break; + } + } +#endif + + return bonus; +} + +/* + * Initialize weapon skill array for the game. Start by setting all + * skills to restricted, then set the skill for every weapon the + * hero is holding, finally reading the given array that sets + * maximums. + */ +void +skill_init(class_skill) +struct def_skill *class_skill; +{ + struct obj *obj; + int skmax, skill; + + /* initialize skill array; by default, everything is restricted */ + for (skill = 0; skill < P_NUM_SKILLS; skill++) { + P_SKILL(skill) = P_ISRESTRICTED; + P_MAX_SKILL(skill) = P_ISRESTRICTED; + P_ADVANCE(skill) = 0; + } + + /* Set skill for all weapons in inventory to be basic */ + for (obj = invent; obj; obj = obj->nobj) { + skill = weapon_type(obj); + if (skill != P_NONE) + P_SKILL(skill) = P_BASIC; + } + + /* set skills for magic */ + if (Role_if(PM_HEALER) || Role_if(PM_MONK)) { + P_SKILL(P_HEALING_SPELL) = P_BASIC; + } else if (Role_if(PM_PRIEST)) { + P_SKILL(P_CLERIC_SPELL) = P_BASIC; + } else if (Role_if(PM_WIZARD)) { + P_SKILL(P_ATTACK_SPELL) = P_BASIC; + P_SKILL(P_ENCHANTMENT_SPELL) = P_BASIC; + } + + /* walk through array to set skill maximums */ + for (; class_skill->skill != P_NONE; class_skill++) { + skmax = class_skill->skmax; + skill = class_skill->skill; + + P_MAX_SKILL(skill) = skmax; + if (P_SKILL(skill) == P_ISRESTRICTED) /* skill pre-set */ + P_SKILL(skill) = P_UNSKILLED; + } + + /* High potential fighters already know how to use their hands. */ + if (P_MAX_SKILL(P_BARE_HANDED_COMBAT) > P_EXPERT) + P_SKILL(P_BARE_HANDED_COMBAT) = P_BASIC; + + /* Roles that start with a horse know how to ride it */ +#ifdef STEED + if (urole.petnum == PM_PONY) + P_SKILL(P_RIDING) = P_BASIC; +#endif + + /* + * Make sure we haven't missed setting the max on a skill + * & set advance + */ + for (skill = 0; skill < P_NUM_SKILLS; skill++) { + if (!P_RESTRICTED(skill)) { + if (P_MAX_SKILL(skill) < P_SKILL(skill)) { + impossible("skill_init: curr > max: %s", P_NAME(skill)); + P_MAX_SKILL(skill) = P_SKILL(skill); + } + P_ADVANCE(skill) = practice_needed_to_advance(P_SKILL(skill)-1); + } + } +} + +void +setmnotwielded(mon,obj) +register struct monst *mon; +register struct obj *obj; +{ + if (!obj) return; + if (artifact_light(obj) && obj->lamplit) { + end_burn(obj, FALSE); + if (canseemon(mon)) + pline("%s in %s %s stops glowing.", The(xname(obj)), + s_suffix(mon_nam(mon)), mbodypart(mon,HAND)); + } + obj->owornmask &= ~W_WEP; +} + +#endif /* OVLB */ + +/*weapon.c*/