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.
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.
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'.
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
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.
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.
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.
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);
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.
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).
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.
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.
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.
... 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.)
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.
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
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.
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.
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.