From 60c1956850179f4860e59957138154ec65255998 Mon Sep 17 00:00:00 2001 From: PatR Date: Sun, 30 Jul 2023 14:08:30 -0700 Subject: [PATCH] revamp 'special' combat This is a re-creation of a project that was lost years ago while not quite finished. The old version included some instrumentation to measure how many hits it takes to kill things during actual play; that wasn't ready for prime time and this hasn't attempted to redo it. Changes: 1) improves martial arts and bare-handed combat: they now have a chance to hit twice when skill is better than 'basic'; 20% chance for second hit at skilled, 40% at expert, 60% at master, and 80% at grandmaster; when attacking more than once, strength bonus is handled as in #2; 2) nerfs two-weapon combat a bit: hitting twice uses only 3/4 strength bonus on each hit, but when both attacks hit that's 3/2 bonus from strength which is still more than you get for one hit at a time; 3) beefs up two-handed weapons: hitting via melee with a two-handed weapon uses 3/2 of stength bonus to reflect the increased influence of strength; isn't done for applied polearms though. The reduction in strength bonus for two-weapon has far less impact than it might sound, due to rounding up with the low values involved. | full 3/4 | +1 -> +1 | +2 -> +2 | +3 -> +2 | +4 -> +3 | +5 -> +4 | +6 -> +5 The small reduction also doesn't matter if/when current hit happens to deal a killing blow anyway. Rings of increase damage apply at full value to every hit, same as before. When hitting bare-handed (#1 without gloves), a silver ring on either hand continues to give a damage bonus against silver haters when you make an ordinary single attack. However if you attack twice, a silver ring only applies on the first hit when it is worn on the right hand and only applies on the second hit when worn on the left hand. (Two hits with a silver ring on each hand will give silver bonus for both.) We might conceivably need to add support for a count prefix of 1 to let player explicitly avoid a second bare-handed/martial-arts hit attempt (similar to how throw and fire accept a count to limit missile volley amount). Kicking has been ignored. --- doc/fixes3-7-0.txt | 1 + include/decl.h | 9 ++- include/you.h | 1 + src/decl.c | 3 + src/uhitm.c | 153 ++++++++++++++++++++++++++++++++++----------- 5 files changed, 127 insertions(+), 40 deletions(-) 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;