Skip to content

Commit

Permalink
revamp 'special' combat
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
PatR committed Jul 30, 2023
1 parent ecd5be5 commit 60c1956
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 40 deletions.
1 change: 1 addition & 0 deletions doc/fixes3-7-0.txt
Expand Up @@ -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
Expand Down
9 changes: 6 additions & 3 deletions include/decl.h
Expand Up @@ -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;
Expand All @@ -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 */
};
Expand Down
1 change: 1 addition & 0 deletions include/you.h
Expand Up @@ -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;
Expand Down
3 changes: 3 additions & 0 deletions src/decl.c
Expand Up @@ -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 */
};
Expand Down
153 changes: 116 additions & 37 deletions src/uhitm.c
Expand Up @@ -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 *,
Expand Down Expand Up @@ -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;
Expand All @@ -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);
Expand All @@ -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;
}

Expand All @@ -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;
Expand All @@ -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;
}
Expand Down Expand Up @@ -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;
}
}

/*
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 */
Expand All @@ -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)] */

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -5540,22 +5617,24 @@ 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);
}

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

Expand Down

0 comments on commit 60c1956

Please sign in to comment.