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.
This commit is contained in:
PatR
2023-07-30 14:08:30 -07:00
parent ecd5be59da
commit 60c1956850
5 changed files with 127 additions and 40 deletions

View File

@@ -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

View File

@@ -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 */
};

View File

@@ -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;

View File

@@ -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 */
};

View File

@@ -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;