diff --git a/doc/fixes3-7-0.txt b/doc/fixes3-7-0.txt index 35fb06ae0..4c9f07223 100644 --- a/doc/fixes3-7-0.txt +++ b/doc/fixes3-7-0.txt @@ -2202,6 +2202,7 @@ can now write unknown spellbook without need of Luck when spell is known; new status highlight rule type for hitpoints: "criticalhp" rule overrides hit point rules if current HP is so low that prayer will consider it to be major problem; applies to hitpointbar as well as HP status +high skill level in martial arts or bare-handed combat sometimes hits twice Platform- and/or Interface-Specific New Features diff --git a/include/decl.h b/include/decl.h index 5c61aa19d..0c8dd6307 100644 --- a/include/decl.h +++ b/include/decl.h @@ -941,11 +941,11 @@ struct instance_globals_t { /* rumors.c */ long true_rumor_size; /* rumor size variables are signed so that value -1 - can be used as a flag */ + * can be used as a flag */ unsigned long true_rumor_start; /* rumor start offsets are unsigned because - they're handled via %lx format */ + * they're handled via %lx format */ long true_rumor_end; /* rumor end offsets are signed because they're - compared with [dlb_]ftell() */ + * compared with [dlb_]ftell() */ /* sp_lev.c */ boolean themeroom_failed; @@ -958,6 +958,9 @@ struct instance_globals_t { /* topten.c */ winid toptenwin; + /* uhitm.c */ + int twohits; /* 0: single hit; 1: first of 2; 2: second of 2 */ + boolean havestate; unsigned long magic; /* validate that structure layout is preserved */ }; diff --git a/include/you.h b/include/you.h index 439f9aeda..db1c63bc4 100644 --- a/include/you.h +++ b/include/you.h @@ -503,6 +503,7 @@ struct you { struct _hitmon_data { int dmg; /* damage */ int thrown; + int twohits; /* 0: 1 of 1; 1: 1 of 2; 2: 2 of 2 */ int dieroll; struct permonst *mdat; boolean use_weapon_skill; diff --git a/src/decl.c b/src/decl.c index df1a36f18..2ba15b13f 100644 --- a/src/decl.c +++ b/src/decl.c @@ -797,6 +797,9 @@ const struct instance_globals_t g_init_t = { 1UL, /* timer_id */ /* topten.c */ WIN_ERR, /* toptenwin */ + /* uhitm.c */ + 0, /* twohits */ + /**/ TRUE, /* havestate*/ IVMAGIC /* t_magic to validate that structure layout has been preserved */ }; diff --git a/src/uhitm.c b/src/uhitm.c index 581ca8e6a..275823853 100644 --- a/src/uhitm.c +++ b/src/uhitm.c @@ -14,6 +14,7 @@ static boolean known_hitum(struct monst *, struct obj *, int *, int, int, static boolean theft_petrifies(struct obj *); static void steal_it(struct monst *, struct attack *); static boolean hitum_cleave(struct monst *, struct attack *); +static boolean double_punch(void); static boolean hitum(struct monst *, struct attack *); static void hmon_hitmon_barehands(struct _hitmon_data *, struct monst *); static void hmon_hitmon_weapon_ranged(struct _hitmon_data *, struct monst *, @@ -714,21 +715,37 @@ hitum_cleave( return (target && DEADMONSTER(target)) ? FALSE : TRUE; } +/* returns True if hero is fighting without a weapon and has sufficient + skill in bare-handeded combat or martial arts to attack twice */ +static boolean +double_punch(void) +{ + /* note: P_BARE_HANDED_COMBAT and P_MARTIAL_ARTS are equivalent */ + int skl_lvl = P_SKILL(P_BARE_HANDED_COMBAT); + + /* + * Chance to attempt a second bare-handed or martial arts hit: + * restricted (0), [not applicable; no one is restricted] + * unskilled (1) : 0% + * basic (2) : 0% + * skilled (3) : 20% + * expert (4) : 40% + * master (5) : 60% + * grandmaster (6) : 80% + */ + if (!uwep && skl_lvl > P_BASIC) + return (skl_lvl - P_BASIC) > rn2(5); + return FALSE; +} + /* hit target monster; returns TRUE if it still lives */ static boolean hitum(struct monst *mon, struct attack *uattk) { boolean malive, wep_was_destroyed = FALSE; struct obj *wepbefore = uwep; - int armorpenalty, attknum = 0, - x = u.ux + u.dx, y = u.uy + u.dy, - oldumort = u.umortality, - tmp = find_roll_to_hit(mon, uattk->aatyp, uwep, - &attknum, &armorpenalty), - dieroll = rnd(20), - mhit = (tmp > dieroll || u.uswallow); - - mon_maybe_unparalyze(mon); + int tmp, dieroll, mhit, armorpenalty, attknum = 0, + x = u.ux + u.dx, y = u.uy + u.dy, oldumort = u.umortality; /* Cleaver attacks three spots, 'mon' and one on either side of 'mon'; it can't be part of dual-wielding but we guard against that anyway; @@ -737,22 +754,34 @@ hitum(struct monst *mon, struct attack *uattk) && !u.uswallow && !u.ustuck && !NODIAG(u.umonnum)) return hitum_cleave(mon, uattk); + /* 0: single hit, 1: first of two hits; affects strength bonus and + silver rings; known_hitum() -> hmon() -> hmon_hitmon() will copy + gt.twohits into struct _hitmon_data hmd.twohits */ + gt.twohits = (uwep ? u.twoweap : double_punch()) ? 1 : 0; + + tmp = find_roll_to_hit(mon, uattk->aatyp, uwep, &attknum, &armorpenalty); + mon_maybe_unparalyze(mon); + dieroll = rnd(20); + mhit = (tmp > dieroll || u.uswallow); if (tmp > dieroll) exercise(A_DEX, TRUE); + /* gb.bhitpos is set up by caller */ malive = known_hitum(mon, uwep, &mhit, tmp, armorpenalty, uattk, dieroll); if (wepbefore && !uwep) wep_was_destroyed = TRUE; (void) passive(mon, uwep, mhit, malive, AT_WEAP, wep_was_destroyed); - /* second attack for two-weapon combat; won't occur if Stormbringer - overrode confirmation (assumes Stormbringer is primary weapon), - or if hero became paralyzed by passive counter-attack, or if hero - was killed by passive counter-attack and got life-saved, or if - monster was killed or knocked to different location */ - if (u.twoweap && !(go.override_confirmation - || gm.multi < 0 || u.umortality > oldumort - || !malive || m_at(x, y) != mon)) { + /* second attack for two-weapon combat or skilled unarmed combat; + won't occur if Stormbringer overrode confirmation (assumes + Stormbringer is primary weapon), or if hero became paralyzed by + passive counter-attack, or if hero was killed by passive + counter-attack and got life-saved, or if monster was killed or + knocked to different location */ + if (gt.twohits && !(go.override_confirmation + || gm.multi < 0 || u.umortality > oldumort + || !malive || m_at(x, y) != mon)) { + gt.twohits = 2; /* second of 2 hits */ tmp = find_roll_to_hit(mon, uattk->aatyp, uswapwep, &attknum, &armorpenalty); mon_maybe_unparalyze(mon); @@ -764,6 +793,7 @@ hitum(struct monst *mon, struct attack *uattk) if (mhit) (void) passive(mon, uswapwep, mhit, malive, AT_WEAP, !uswapwep); } + gt.twohits = 0; return malive; } @@ -790,7 +820,7 @@ hmon(struct monst *mon, static void hmon_hitmon_barehands(struct _hitmon_data *hmd, struct monst *mon) { - long silverhit = 0L; /* armor mask */ + long spcdmgflg, silverhit = 0L; /* worn masks */ if (hmd->mdat == &mons[PM_SHADE]) { hmd->dmg = 0; @@ -804,11 +834,32 @@ hmon_hitmon_barehands(struct _hitmon_data *hmd, struct monst *mon) /* Blessed gloves give bonuses when fighting 'bare-handed'. So do silver rings. Note: rings are worn under gloves, so you don't - get both bonuses, and two silver rings don't give double bonus. */ - hmd->dmg += special_dmgval(&gy.youmonst, mon, - (W_ARMG | W_RINGL | W_RINGR), &silverhit); - hmd->barehand_silver_rings = (((silverhit & W_RINGL) ? 1 : 0) - + ((silverhit & W_RINGR) ? 1 : 0)); + get both bonuses, and two silver rings don't give double bonus. + When making only one hit, both rings are checked (backwards + compatibility => playability), but when making two hits, only the + ring on the hand making the attack is checked. */ + spcdmgflg = uarmg ? W_ARMG + : ((hmd->twohits == 0 || hmd->twohits == 1) ? W_RINGR : 0L + | (hmd->twohits == 0 || hmd->twohits == 2) ? W_RINGL : 0L); + hmd->dmg += special_dmgval(&gy.youmonst, mon, spcdmgflg, &silverhit); + + /* copy silverhit info back into struct _hitmon_data *hmd */ + switch (hmd->twohits) { + case 0: /* only one hit being attempted; a silver ring on either hand + * applies but having silver rings on both is same as just one */ + hmd->barehand_silver_rings = (silverhit & (W_RINGR | W_RINGL)) ? 1 : 0; + break; + case 1: /* first of two or more hit attempts; right ring applies */ + hmd->barehand_silver_rings = (silverhit & W_RINGR) ? 1 : 0; + break; + case 2: /* second of two or more hit attempts; left ring applies */ + hmd->barehand_silver_rings = (silverhit & W_RINGL) ? 1 : 0; + break; + default: /* third or later of more than two hit attempts (poly'd hero); + * rings were applied on first and second hits */ + hmd->barehand_silver_rings = 0; + break; + } if (hmd->barehand_silver_rings > 0) hmd->silvermsg = TRUE; } @@ -1308,19 +1359,39 @@ hmon_hitmon_do_hit( static void hmon_hitmon_dmg_recalc(struct _hitmon_data *hmd, struct obj *obj) { - int dmgbonus = 0; + int dmgbonus = 0, strbonus, absbonus; /* * Potential bonus (or penalty) from worn ring of increase damage - * (or intrinsic bonus from eating same) or from strength. + * (or intrinsic bonus from eating same) or from strength. Strength + * bonus is increased for melee with two-handed weapons and decreased + * for dual attacks (but when both hit, the total for the two is more + * than the bonus for a regular single hit). */ if (hmd->get_dmg_bonus) { + /* for dual attacks, udaminc applies to both, and two-handed + weapons use it as-is */ dmgbonus = u.udaminc; /* throwing using a propellor gets an increase-damage bonus - but not a strength one; other attacks get both */ + but not a strength one; other attacks get both; + for dual attacks, 3/4 of the strength bonus is used; when + both attacks hit, overall bonus is 3/2 rather than doubled; + melee hit with two-handed weapon uses 3/2 strength bonus to + appoximately match double hit with two-weapon ('approximate' + becase udaminc skews in favor of two-weapon); the 3/2 factor + for two-handed strength does not apply to polearms unless + hero is simply bashing with one of those and does not apply + to jousting because lances are one-handed */ if (hmd->thrown != HMON_THROWN - || !obj || !uwep || !ammo_and_launcher(obj, uwep)) - dmgbonus += dbon(); + || !obj || !uwep || !ammo_and_launcher(obj, uwep)) { + strbonus = dbon(); + absbonus = abs(strbonus); + if (hmd->twohits) + strbonus = ((3 * absbonus + 2) / 4) * sgn(strbonus); + else if (hmd->thrown == HMON_MELEE && uwep && bimanual(uwep)) + strbonus = ((3 * absbonus + 1) / 2) * sgn(strbonus); + dmgbonus += strbonus; + } } /* @@ -1594,6 +1665,7 @@ hmon_hitmon( hmd.dmg = 0; hmd.thrown = thrown; + hmd.twohits = thrown ? 0 : gt.twohits; hmd.dieroll = dieroll; hmd.mdat = mon->data; hmd.use_weapon_skill = FALSE; @@ -5133,7 +5205,7 @@ hmonas(struct monst *mon) struct obj *weapon, **originalweapon; boolean altwep = FALSE, weapon_used = FALSE, odd_claw = TRUE; int i, tmp, dieroll, armorpenalty, sum[NATTK], - dhit = 0, attknum = 0, multi_claw = 0; + dhit = 0, attknum = 0, multi_claw = 0, multi_weap = 0; boolean monster_survived; /* not used here but umpteen mhitm_ad_xxxx() need this */ @@ -5145,11 +5217,14 @@ hmonas(struct monst *mon) for (i = 0; i < NATTK; i++) { sum[i] = M_ATTK_MISS; mattk = getmattk(&gy.youmonst, mon, i, sum, &alt_attk); + if (mattk->aatyp == AT_WEAP) + ++multi_weap; if (mattk->aatyp == AT_WEAP || mattk->aatyp == AT_CLAW || mattk->aatyp == AT_TUCH) ++multi_claw; } multi_claw = (multi_claw > 1); /* switch from count to yes/no */ + gt.twohits = 0; gs.skipdrin = FALSE; /* [see mattackm(mhitm.c)] */ @@ -5215,6 +5290,8 @@ hmonas(struct monst *mon) mon_maybe_unparalyze(mon); dieroll = rnd(20); dhit = (tmp > dieroll || u.uswallow); + if (multi_weap > 1) + ++gt.twohits; /* caller must set gb.bhitpos */ monster_survived = known_hitum(mon, weapon, &dhit, tmp, armorpenalty, mattk, dieroll); @@ -5540,6 +5617,7 @@ hmonas(struct monst *mon) } gv.vis = FALSE; /* reset */ + gt.twohits = 0; /* return value isn't used, but make it match hitum()'s */ return !DEADMONSTER(mon); } @@ -5547,15 +5625,16 @@ hmonas(struct monst *mon) /* Special (passive) attacks on you by monsters done here. */ int -passive(struct monst *mon, - struct obj *weapon, /* uwep or uswapwep or uarmg or uarmf or Null */ - boolean mhitb, - boolean maliveb, - uchar aatyp, - boolean wep_was_destroyed) +passive( + struct monst *mon, + struct obj *weapon, /* uwep or uswapwep or uarmg or uarmf or Null */ + boolean mhitb, + boolean maliveb, + uchar aatyp, + boolean wep_was_destroyed) { - register struct permonst *ptr = mon->data; - register int i, tmp; + struct permonst *ptr = mon->data; + int i, tmp; int mhit = mhitb ? M_ATTK_HIT : M_ATTK_MISS; int malive = maliveb ? M_ATTK_HIT : M_ATTK_MISS;