I ran the fuzzer with MONITOR_HEAP enabled and heaputil found a dozen
or so un-free'd allocations, all made by the same dupstr() call in
special_handling() for "symset" and "roguesymset". (Reproducible with
a few tens of thousands of fuzzer moves, although you have to take
over from the fuzzer and make a clean exit rather than just interrupt
it or there'll be lots of other un-free'd memory.) I haven't actually
figured out how/why it was leaking, but reorganizing the code has made
the leak go away (according to a couple of even longer fuzzer runs) so
I'm settling for that.
A recently added impossible to check for an(Null) and an("") was
triggered by the fuzzer: Alphabet soup: 'an("")'. I reproduced it a
couple of times and tracked it do_screen_description(for '/' command)
matching the symbol from mapglyph to monster class #0, a placeholder
with symbol value '\0'. So mapglyph() returned a symbol of '\0', but
not necessary from showsyms[0 + SYM_OFF_M].
The pager lookup code's monster loop shouldn't have been attempting
to match against class #0, and since this fix I haven't been able to
reproduce the situation again. But I also didn't trigger it with a
bunch of temporary checks in mapglyph() so don't know what is really
going on under the hood.
Back out '#include "date.h"' so that cursinit.c won't be recompiled
every time any other file(s) need to be compiled. It doesn't need
patchlevel.h either. There is already a straightforward way to fetch
the copyright banner lines from version.c.
The splash screen (ascii art spelling "NetHack" preceding the normal
copyright lines) was invisible when showing white text on white-ish
background. Make it honor !guicolor.
"Shall I pick a character's role, race, gender and alignment for you?
[ynaq] (y) " was too wide to accept the answer on the same line on
an 80-column display so "(y) " was placed on the second line. That's
constructed in the core; change the construction to omit " a" when
using "character" rather than a role name. (tty shortens it by omitting
the default " (y)"; with " a" gone, it could revert to normal prompt.)
Also a bit of lint cleanup and some reformatting of cursinit.c....
The revised mkclass() [actually new mkclass_aligned()] has an extra
check which didn't used to be there, and attempting to create a
monster of class 'I' with ^G triggered impossible "mkclass found no
class 35 monsters" which the fuzzer escalates to panic.
Fixes#170
Monsters never throw athames or scalpels but some fake player monsters
on the Astral Plane are given those. Since they're stackable the
quantity usually gets boosted but there's no point in having more than
one if they won't be thrown.
This could have been fixed by letting monsters throw those two items,
but I prevented the quantity from being boosted instead.
When merging one stack into another and they have different obj->o_id
price adjustments, keep the o_id of whichever one commands the higher
shop price.
I misread part of the original code and the revision introduced a bug
based on that. obj->o_id price variations are used for all types of
non-IDed items, not just non-glass gems.
Player came across a stack of 2 gray stones in a shop and kicked one.
That one ended up with a different (in his case, lower) price once it
was separate. This behavior only applies to non-glass gems which add
a price variation derived from internal ID (obj->o_id) number. Make
splitting stacks always yield the same price per item in the new stack
as was being charged in the old stack by choosing a similar o_id. Do
it for all splits (that can vary price by ID, so just non-glass gems),
not just ones performed inside shops.
He picked up the lower priced one and dropped it back on the original
higher priced one; the combined stack took on the lower price. That
will no longer happen if they come from splitting a stack, but this
fix doesn't address merging with different prices when they start out
as separate stacks. (Unpaid items won't merge in inventory if prices
are different, but shop-owned items will merge on floor.)
It was possible to have the guaranteed luckstone at Mines' End become
merged with a random one and lose its specialness for achievement
tracking. Mark it 'nomerge' when created and clear that if/when the
achievement is recorded.
Fix another inconsistency with containers in shops: prices shown when
looking inside. Apply had them (because shop goods in containers are
flagged as 'unpaid' when hero carries the container), and loot did not
(because they aren't flagged that way).
stolen_value() treated hero-owned container holding shop-owned goods
as free for the taking.
The fix I'm working on which led to discovering this first added
stolen_value() then eventually stopped using it so I don't have an
example of where it is giving the wrong result.
get_cost_of_item() was giving different information from shop #chat
when dealing with containers owned by hero containing objects owned
by the shop. And when it was legitimately reporting a price of 0,
doname_with_price() wasn't reporting 'no charge' for items inside a
shop that were owned by hero or that shopkeeper didn't care about.
Extend the shop price reveal to far-look, but only when hero and item
being examined are inside the same shop.
Symset:Blank sets all the map symbols (except STRANGE_OBJECT) to
<space>. The status lines for !STATUS_HILITES force status to use '$'
instead of ' ' for the prefix before ":1234" for gold, but the status
lines for STATUS_HILITES did not. tty ended up with ":1234" for gold.
win32 and curses both ignore the prefix and construct their own, but
since win32 uses the map symbol for that it must also be ending up
with ":1234" (I assume; I haven't seen it). curses is forcing '$' for
the prefix, even on the rogue level.
This attempts to fix win32 without be able to test the result. I've
left curses alone.