Commit Graph

438 Commits

Author SHA1 Message Date
RainRat
a3658f85ac fix typos 2024-02-28 20:15:56 -08:00
Erik Lunna
eb22a81088 Refactor, unify, and nerf item destruction
Note: Original change is from xNetHack by copperwater <aosdict@gmail.com>,
      but this commit comes from HACKEM-MUCHE by Erik Lunna, with
      some minor code formatting.

From xNetHack commit a0a6103bea:

'The original goal: nerf item destruction using a method I initially
 proposed for SpliceHack, in which the number of items subject to
 damage from any single source is limited by the amount of damage the
 effect caused. The intent was to be more fair all around and prevent
 aggravating situations where, for instance, a chest shock trap zaps
 you for 4 damage and immediately ten of your rings and wands blow up.

 Problem 1: no easy way to limit the items destroyed without biasing
 heavily towards the start of the invent chain. The old code was able
 to get away without bias by just indiscriminately destroying
 everything eligible with a 1/3 chance. Here, I had to introduce
 reservoir sampling in a somewhat more complex form than I've applied
 it elsewhere, since there are a pool of potential items.

 Problem 2: destroy_item no longer worked remotely like destroy_mitem,
 which still destroyed 1/3 of items indiscriminately. Commence the
 process of squishing them into one function that handles both the
 player and monsters. (Which required making a lot of adjustments to
 destroy_one_item, now named maybe_destroy_item, on nits such as
 messaging and when to negate damage. An annoying consequence of the
 merge is that in the player case, their HP is deducted and they can
 be killed directly, but for monsters they need to add up the
 destruction damage and return it.)

 Unifying destroy_item and destroy_mitem has some advantages: in
 addition to the obvious code duplication removal, it ensures monsters
 now take the same damage as players for destruction (previously they
 took a piddly 1 damage per destroyed item). Now when you hit
 something with Mjollnir and their coveted wand of death breaks apart
 and explodes, you at least get the satisfaction of knowing they took
 the standard amount of damage from it.  Monsters also now get
 symmetry with players in having extrinsic elemental resistance
 protect them from item destruction, and damage negation from item
 destruction if they were appropriately resistant.

 Problem 3: a lot of callers didn't preserve the "amount of incoming
 damage" that this refactor relies on. E.g. if the defender resisted
 that element, the local dmg variable would be set to 0. So I had to
 do some wrangling with callers to save that original damage
 value. The rule of thumb is: all *incoming* damage counts. So that
 includes the player's spellcasting bonus if applicable, but not
 things like half damage, negation due to resistance, or extra damage
 due to being vulnerable to cold/fire.

 Then I figured, while I'm here let's get rid of all those silly cases
 where destroy_items is called multiple times for various different
 object classes, and cut the object class parameter out of it. This
 has a few minor effects:

 - Places where different object classes previously rolled
   independently for destruction to happen at all now roll
   once. (Which, by my calculation, generally means less incidences of
   destruction - a fire attack now won't have three separate chances
   to hit your scrolls, potions, and spellbooks. On the flip side, a
   lucky roll will no longer save an entire object class in your
   inventory.)

 - Callers can no longer specify different probabilities for
   destroying different object classes. The only place this was really
   used was to call destroy_item with a slightly lower probability on
   SPBOOK_CLASS.  With the nerf in this commit, less of them ought to
   be destroyed anyway.

 - A very edge case of where explosion-vs-monster damage was totted up
   differently for golems, which could result in differences of a hit
   point here or there.

 - All object classes being processed in one go means that less items
   are destroyed than would be if they were still processed
   independently.  This is not really visible compared to the old
   baseline of just destroying 33% of everything, but would be a
   marked difference versus a copy of the game that still called
   destroy_items separately for different object classes. To
   compensate, I adjusted my planned damage-to-destruction-limit
   scaling factor down from 8 to 5.

 Not done: merging in ignite_items(), though that would probably be
 really easy now.'

Notes from porting from xNetHack:

- It might be necessary to reexamine at all the conditional checks for
calling destroy_items. Because item destruction is much more
restrained and uses the actual damage from an effect, we might now
need to check 'if (!rn2(3))' and similar in all the places item
destruction occurs.
2024-02-20 22:03:54 +02:00
nhmall
688ac6ffbe remove register from variable declarations 2024-02-19 16:30:07 -05:00
PatR
16f4bdb5a6 Revert "fix crash on NULL gi.invent"
This reverts commit 378648bd9c.

The problem was triggered by marking the 'objlist' argument in
merge_choice() prototype with __attribute__((nonnull)) when it
shouldn't have been, then a followup which relied on that.  The
'objlist' argument might be Null.  Instead of passing its address to
force it to be non-Null, remove the attribute.
2024-02-01 14:25:23 -08:00
nhmall
378648bd9c fix crash on NULL gi.invent 2024-01-31 12:51:33 -05:00
Pasi Kallinen
57747535af Add m_next2u, analogous to m_next2m and next2u 2024-01-19 21:53:25 +02:00
nhmall
25a8c258e6 replace x >= LOW_PM with ismnum(x) shorthand macro 2024-01-11 14:01:10 -05:00
Mika Kuoppala
83fba62152 src/uhitm: Avoid touch_petrifies with invalid corpsenm
Before checking with touch_petrifies, check that corpsenm
is valid (>= LOW_PM)
2024-01-06 12:06:55 -08:00
nhmall
4e19221e55 variable 'display' causes shadow variable warnings in X11 build
display.botl      -> disp.botl
display.botlx     -> disp.botlx
display.time_botl -> disp.time_botl
2024-01-05 05:58:51 -05:00
nhmall
49a5d043c0 consistent use of TRUE vs 1 with botl and botlx 2024-01-04 23:48:38 -05:00
nhmall
22e52ee905 bundle the display-related hints, that tell bot() and others
that an update is required, into a struct. Remove it from
context since there is no reason to save those.
2024-01-04 23:16:27 -05:00
nhmall
01d6d94e30 non-Null handling for uhitm.c 2023-12-29 00:34:37 -05:00
nhmall
fcc91cec94 static analyzer bit in uhitm.c
src/uhitm.c(1172): warning: Reading invalid data from 'mons'.

Analyzer wasn't happy with the index into mons[] array only
being validated by '!= -1'.

Update the check for the index to include the full array
index range, including ensuring that it is also '< NUMMONS'.
2023-12-22 16:30:24 -05:00
nhmall
70dcab833d remove obj guard from stone_missile(obj) macro
Checking the callers:
toss_up() would have segfaulted prior to use of stone_missile() if obj were NULL.
thitu() now has a guard prior to use of stone_missile()
ohitmon() would have crashed from earlier dereference otmp->dknown if it were NULL,
   otmp arg is declared nonnull
thitm() now has a guard prior to use of stone_missile().
hmon_hitmon_do_hit() null obj takes a different code path than the code path
    using stone_missile(); comment asserting that added
2023-12-16 07:58:44 -05:00
nhmall
28bd51fecb Merge branch 'fix-uhitm' of https://github.com/argrath/NetHack into NetHack-3.7 2023-12-11 11:29:34 -05:00
nhmall
96fdc1234b add comments after assessing some subfunctions 2023-12-11 11:28:20 -05:00
SHIRAKATA Kentaro
5de1f6e5ed remove unnecessary null-check on hmon_hitmon_poison()
`obj` here is always non-null, otherwise it leads segv at earlier code.
2023-12-11 13:47:07 +09:00
Alex Smith
cb1eadd6b7 Don't pay the shopkeeper when displacing a pet in a shop
The recent commit to interpret walking into a shopkeeper as a "pay"
command was triggering in too many circumstances. Check to ensure
that the monster that we're walking into is a known shopkeeper
before activating the special case.
2023-12-09 02:03:31 +00:00
Pasi Kallinen
348f88c726 Walking into a shopkeeper tries to pay the bill 2023-12-08 18:31:16 +02:00
nhmall
e4e8eea4e8 track the handedness of the hero
Don't make either LEFT_HANDED or RIGHT_HANDED be an advantage
or a disadvantage.

use suggested macros
2023-12-02 11:23:43 -05:00
nhmall
d7fef5f194 avoid another magic number
Some of the hardcoded +1 scattered about are likely
invlet_gold or invlet_overflow, but I didn't hunt those down.
2023-11-30 11:15:32 -05:00
PatR
fcf1a42cac fix for two empty-handed hits
When using bare-handed combat or martial arts at better than 'basic'
skill, don't attempt a second hit if wearing a shield.
2023-11-12 10:21:51 -08:00
nhmall
6cbefc7c2d Revert "granular verbose message suppression mechanics"
This reverts commit be76727265.
2023-10-29 20:39:07 -04:00
Pasi Kallinen
69a4853851 Fix "no monster to remove" when tame nymph attacked
A tame nymph attacked another monster, stole an item and teleported
away, but dog_move() wasn't passed the information that the nymph
was done, and tried to move the nymph from the old location.

Same with a tame leprechaun.
2023-10-21 16:05:15 +03:00
Pasi Kallinen
3946c5a01d Give barbed devils a sticking attack 2023-10-04 11:09:25 +03:00
Michael Meyer
8fa53d6ac5 Adjust seenres when observed attack succeeds
If a monster sees an elemental attack succeed from some other creature
or environmental danger, it will be willing to try those attacks again.
2023-08-29 12:05:48 +03:00
PatR
9d9042e94b fix #K3968 - objfree: obj not worn
New feature to sometimes hit twice for skilled martial-arts/bare-handed
was unconditionally using uswapwep for the second hit.  If it was a
breakable object, hitting could break it and produce impossible "objfree:
obj not free".

Only use uswapwep for u.twoweap; use Null for second bare-handed hit.
2023-08-01 10:28:21 -07:00
PatR
af387db16d bare-handed combat warning fix 2023-07-31 10:26:37 -07:00
nhmall
7cec01748e Revert "warning after commit 60c19568"
This reverts commit d32208562e.
2023-07-31 10:16:35 -04:00
nhmall
d32208562e warning after commit 60c19568
uhitm.c:843:63: warning: operator '?:' has lower precedence than '|'; '|' will be evaluated first [-Wbitwise-conditional-parentheses]
                   | (hmd->twohits == 0 || hmd->twohits == 2) ? W_RINGL : 0L);
                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ^
uhitm.c:843:63: note: place parentheses around the '|' expression to silence this warning
                   | (hmd->twohits == 0 || hmd->twohits == 2) ? W_RINGL : 0L);
                                                              ^
                                                             )
uhitm.c:843:63: note: place parentheses around the '?:' expression to evaluate it first
                   | (hmd->twohits == 0 || hmd->twohits == 2) ? W_RINGL : 0L);
2023-07-31 10:10:19 -04:00
PatR
60c1956850 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.
2023-07-30 14:08:30 -07:00
PatR
d75862450e uhitm.c formatting 2023-05-28 15:22:43 -07:00
PatR
9052bd5099 fix #K3925 - u.ustuck of long worm tail
Don't allow stick/wrap/engulf attacks directed at long worm tails
to succeed.  Achieved by making sure that 'notonhead' is up do date
in a bunch of places and utilizing the fairly recent can't-{stick,wrap,
engulf}-unsolid-monsters code.

Should prevent a 'sanity_check' warning about being too far from
u.ustuck that would happen when holding the tail while the head was
not adjacent to the hero.

Also don't let pet ranged attacks from choosing a long worm's tail
as target.  They'll still be able to target long worms provided that
the head is lined up and not shielded by tail segment(s).
2023-05-20 15:34:32 -07:00
PatR
f8e4c3e078 give fuzzer some protection against brainlessness
With the LifeSaved property (via amulet), hero being killed by
brainlessness gets killed twice.  But for explore|wizard mode where
the answer to "Die?" might be "no" and for the fuzzer where it's
always "no", a mind flayer's 3 drain-Int attacks or master flayer's
5 drain-Int attacks will usually kill the hero all over again (and
again, ...).  Skip remaining ones, like happens when one of them
hits and discovers that the target has no head or is mindless, for
the rest of the flayer's current move.

Monsters wearing life-saving don't get killed twice.  That doesn't
seem very fair, but this hasn't touched that.
2023-05-20 01:58:30 -07:00
Pasi Kallinen
f4f74b2e07 Purple worm burp wakes up monsters 2023-05-12 09:17:09 +03:00
Pasi Kallinen
ead2f94d6e Fix genetic engineer poly hit killing the defender
The polymorph hit can kill the monster, but this info wasn't
propagated back.  If the defending dead monster retaliated,
the game issued an impossible.
2023-05-10 09:34:28 +03:00
Pasi Kallinen
e18548c821 Fix knockback impossible
When monster attacked another monster, and the retaliation attack
knocked back the attacking monster, the variables holding the
attacking monster coordinates were out of sync and caused
"no monster to remove" warning.

Propagate back the knockback hit, so the current monster cannot
do anything further.
2023-05-07 12:57:09 +03:00
Pasi Kallinen
fe3dcb4416 Genetic engineer uses up turn when polying a monster
The fuzzer encountered a "no monster to remove" impossible,
this should fix it, even though I did not manage to reproduce
it manually.
2023-05-06 20:16:28 +03:00
PatR
1c94bdac89 blindness overhaul
I was working on this at the time 3.6.0 was released and set it aside
until later.  Later has finally arrived.  Redo the Blind, Blinded,
Blindfolded,&c macros to make more complete use of intrinsic property
handling.  Blinded was being treated as a number which could be added
to or subtracted from; now that has to be done via TIMEOUT mask
because it has FROMOUTSIDE (OPTIONS:blind) and FROMFORM (poly'd into
!haseyes() form) bits included.  Object definitions for blindfold and
towel now specify the BLINDED property; overriding blindness via the
Eyes of the Overworld is accomplished via props[BLINDED].blocked.

Code generated for the scores of Blind and !Blind tests throughout
the program should be smaller.

One bug that has been fixed is that putting on the Eyes of the
Overworld cured permanent blindness (from OPTIONS:blind).  The
u.uroleplay.blind flag was cleared and stayed so after taking them
off.  Putting the Eyes on still breaks blind-from-birth conduct but
now blindness will resume when they are removed.

This was untested at the time it was set aside and is only lightly
tested now.  A large number of the changes here are just to switch
from Blinded to BlindedTimeout for current timed value and to call
set_itimeout() for setting a new value.
2023-04-27 14:53:28 -07:00
nhmall
826ce951e7 get rid of NetHack macro conflict with curses routine delay_output() 2023-04-21 08:25:53 -04:00
copperwater
dad1c3f8b7 Fix: breathless monsters always generate in water in special levels
... unless explicitly specified to generate at a specific point or
within a specific area. But if they are permitted to generate anywhere
on the level, and it contains water, they always end up in the water. I
noticed this when trying to explicitly specify ghouls to generate
anywhere on a level with a minimal amount of water.

This was due to the definition of "amphibious" being conflated with
"breathless", such that all breathless monsters counted as amphibious.
There are plenty of breathless monsters in the game that decidedly don't
normally inhabit water, such as undead, but they would pass the
amphibious() check in pm_to_humidity and thus the game decides that they
must generate in wet terrain if there is any available.

This fix takes the approach of changing amphibious() so that it no
longer checks the M1_BREATHLESS flag and only considers M1_AMPHIBIOUS,
then updating the places where amphibious() and Amphibious are used
accordingly. I also added a new macro cant_drown() which wraps up
swimming, amphibiousness, and breathlessness because these three things
are frequently checked together in the context of whether something
should drown.

Places where amphibious() or Amphibious did NOT have an extra
breathless() or Breathless check added on, and thus where behavior has
been changed:
- The pm_to_humidity function (to fix the bug).
- Player vs water in goodpos; it didn't seem like being polymorphed into
  a breathless non-amphibious monster should make it fair game to
  randomly teleport into water even though it's technically safe.
- Awarding extra experience when killing an eel. (So the hero will get
  the extra experience if they are polymorphed into a breathless
  non-amphibious monster and don't have magical breathing. Very much an
  edge case.)
2023-04-16 09:57:41 +03:00
PatR
1a2d844a22 fix #K3902 - hug attacks against unsolid targets
Prevent hug attacks (owlbear, python, pit fiend, several others),
attacks for wrap damage (eel and kraken, trapper and lurker above),
attacks for stick-to damage (mimic, lichen), and attacks for digestion
damage (purple worm) from succeeding against unsolid monsters (ghosts,
lights, vortices, most elementals).

Polymorph of an engulf or hold target into unsolid form has been
addressed by a couple of previous updates.
2023-04-14 13:27:33 -07:00
PatR
bbd76562b0 redo fix for issue #1003 - energy drain
The fix in commit 14d003c4ba prevented
current energy from ending up 1 point above maximum energy but it
didn't preserve the intent of splitting the drain with up to half
coming out of maximum and the remainder out of current.  This restores
that intent but now only does so when maximum is more than the full
drain amount rather than when it is more than the up-to-half portion,
becoming less harsh when hero's max energy is very low.  If current
is also very low then max energy will be reduced anyway, but by less.

Some unrelated formatting of invent.c has gotten mixed in.

Revises #1003
2023-04-08 16:40:21 -07:00
nhmall
5f69dc6228 make attack result macros more distinguishable from makemon macros
Use the MM_ prefix only for the makemon macros, and change these five as follows:

 MM_MISS 0x0     -> M_ATTK_MISS      /* aggressor missed */
 MM_HIT 0x1      -> M_ATTK_HIT       /* aggressor hit defender */
 MM_DEF_DIED 0x2 -> M_ATTK_DEF_DIED  /* defender died */
 MM_AGR_DIED 0x4 -> M_ATTK_AGR_DIED  /* aggressor died */
 MM_AGR_DONE 0x8 -> M_ATTK_AGR_DONE  /* aggressor is done with their turn */

include/hack.h:#define NO_MM_FLAGS     0x000000L /* use this rather than plain 0 */
include/hack.h:#define MM_NOWAIT       0x000002L /* don't set STRAT_WAITMASK flags */
include/hack.h:#define MM_NOCOUNTBIRTH 0x000004L /* don't increment born count (for revival) */
include/hack.h:#define MM_IGNOREWATER  0x000008L /* ignore water when positioning */
include/hack.h:#define MM_ADJACENTOK   0x000010L /* acceptable to use adjacent coordinates */
include/hack.h:#define MM_ANGRY        0x000020L /* monster is created angry */
include/hack.h:#define MM_NONAME       0x000040L /* monster is not christened */
include/hack.h:#define MM_EGD          0x000100L /* add egd structure */
include/hack.h:#define MM_EPRI         0x000200L /* add epri structure */
include/hack.h:#define MM_ESHK         0x000400L /* add eshk structure */
include/hack.h:#define MM_EMIN         0x000800L /* add emin structure */
include/hack.h:#define MM_EDOG         0x001000L /* add edog structure */
include/hack.h:#define MM_ASLEEP       0x002000L /* monsters should be generated asleep */
include/hack.h:#define MM_NOGRP        0x004000L /* suppress creation of monster groups */
include/hack.h:#define MM_NOTAIL       0x008000L /* if a long worm, don't give it a tail */
include/hack.h:#define MM_MALE         0x010000L /* male variation */
include/hack.h:#define MM_FEMALE       0x020000L /* female variation */
include/hack.h:#define MM_NOMSG        0x040000L /* no appear message */

include/hack.h:#define MM_NOEXCLAM     0x400000L /* more sedate "<mon> appears." mesg for ^G */
include/hack.h:#define MM_IGNORELAVA   0x800000L /* ignore lava when positioning */
2023-03-19 12:19:34 -04:00
PatR
1c24f208f3 genetic engineer attacks
When a genetic engineer polymorphs someone it normally teleports away.
Also set mspec_used so that it can't polymorph someone [else] on its
next turn, it case of a no-teleport level or it happens to randomly
land adjacent to the target.
2023-03-17 16:00:44 -07:00
nhmall
da0e2571a0 fix a spelling mistake in the code 2023-03-16 22:44:42 -04:00
nhmall
de79240dea some comment spelling fixes 2023-03-16 22:27:01 -04:00
PatR
9d11d6bf13 fix #K3884 - sequencing of "You avoid harm" by MC
Many damage handling routines were calling mhitm_mgc_atk_negated()
in advance so that the result could be used for u-vs-m and m-vs-u,
and m-vs-m variations of an attack.  But the monster versus you case
called hitmsg() to deliver a "<mon> <bites, claws, &c> you" message
after that.  When the negation checking routine recently began
issuing messages for some types of damage, they would be delivered
before that hit message when your armor/jewelry negated its damage.
|You avoid harm.      [when MC is about to block the shock]
|The grid bug bites.  [bite for electric damage]
or
|The fire ant bites.  [for physical damage]
|You avoid harm.      [when MC is about to block the fire]
|The fire ant bites.  [second bite for fire damage]

This fixes the sequencing issue at cost of making the code become
even more complicated.  It will probably require further refinement.
2023-03-15 13:59:06 -07:00
PatR
01af084f43 non-digest engulfing
Be more consistent with the engulf attack feedback by creatures who
fold themselves around the victim (trapper, lurker above) rather than
swallow or directly engulf.

Replace an instance of a non-literal format string and the warnings
manipulation it needed with a literal one.
2023-03-13 12:49:22 -07:00
PatR
d86f966ee6 uhitm.c formatting
Shorten or wrap a bunch of too-wide lines in uhitm.c.
2023-03-11 15:51:08 -08:00