All ideas tagged "code internals"

#4455

 · 
vanilla

Add a “save selection” directive in the Lua parser that takes a previously defined selection and saves it into the level structure along with a name, allowing subsequently executed Lua scripts or other pieces of code to use that same selection after level creation is over.

#4430

 · 
SLASH'EM

Make it so that firearms are loaded with bullets (they become stored as part of the object) so that you don’t quiver them, which is a weird and unflavorful way to treat them.

#4374

 · 
vanilla

The extrainfo patch for servers should store a piece of metadata that indicates whether the player has mail turned on, so that a person spectating a game in dgamelaunch who tries to mail a player with mail turned off will get a message explaining that.

Track all alignment violations according to their source, which means when you anger a peaceful you tick up an “angered a peaceful” counter, when you rob a grave you increment “grave robbed”, etc. Enough incrementing in any counter (which may vary based on your role and alignment) starts to make your god angry and priests unfriendly. The idea is that if you have a large number of alignment violations but they’re spread out across multiple causes, you might skate by, but repeatedly and happily doing a bad thing will get you punished (and will allow your god to actually vocalize what you’ve been doing wrong). It would also be nice to show in the dumplog what your worst sins were.

#4177

 · 
vanilla

Create a new stat to track the maximum level a player has ever reached, named “u.ulevelpeak”, and use it when computing the difficulty monsters should generate at, so that the player has no incentive to drain levels from themself to get easier monsters.

ulevelpeak is used rather than the existing ulevelmax because ulevelmax represents the highest level it is possible for the player to return to via restore ability, and shrinks when doing things such as quaffing blessed full healing to restore lost levels and losing levels when polymorphing into one’s own race, so it’s unsuitable for the monster generation purpose.

#4045

 · 
vanilla

Replace the whole speed system with the one from Tales of Maj’Eyal, another roguelike. This has a strict concept of a “global speed”, i.e. a number of movement points you are given every global turn. Every possible action has an associated movement point cost, which allows for things like zapping a wand to take less time and be done more frequently than making a melee attack. This gets rid of the weird existing system where, in ToME terms, your most recent action defines how much global speed you get, particularly if riding is involved.

One way to start implementing this is to have all the action functions that currently return 0 for no time taken or 1 for time taken to instead return a bitmask of all types of actions taken: “moved”, “attacked”, “cast a spell”, etc. Then the main loop code that handles these return values deducts movement points corresponding to the slowest action in this bitmask.

#4042

 · 
vanilla

Resolve the quirkiness of statue traps - they are a silent trap on which a real statue potentially containing spellbooks can generate, which you can teleport off the trap to smash for its spellbooks (indicating it’s a true statue) and do not show up with telepathy or monster detection (indicating it’s a true statue), but reveal themselves as “You find a foo posing as a statue” (indicating it’s been a living monster all along). There are two basic options:

  1. Statue traps are posing monsters: Remove statue traps as a floor trap. In their place, put real but non-moving monsters that appear as statues of themselves, using the monster appearance system used by mimics. This means that you can see the monsters with telepathy and monster detection and you can’t exploit it to get spellbooks.
  2. Statue traps magically animate a statue when someone moves onto their space: Searching for traps may still reveal statue traps, but it does not activate the monster. This implementation might also actually remove statue traps as a floor trap, and instead use the otrapped field on the statue object, so relocating the statue doesn’t affect its trapped status. Trap detection should still show trapped statues.

#3785

 · 
vanilla

The debug fuzzer, when it levelports, should pick a random level from the entire dungeon rather than using random levelportation logic, since this tends to lead to it bouncing around the upper levels of the Dungeons of Doom rather than actually going anywhere significant and into new branches.

Or to make this a bit more configurable, there should be an additional iflag added that controls this levelporting behavior which can be enabled in a debugger similar to how iflags.debug_fuzzer is.

#3730

 · 
vanilla

Extrinsic properties are stored as an int rather than a bitfield, so that the game can count how many sources of an extrinsic you have at once, allowing interesting things such as letting regeneration items stack with each other.

#3455

 · 
vanilla

When the game detects a crash or some other event that an administrator should know about, it calls a hook for some possibly customizable alerting behavior like sending mail via the unix mail system. This would be a compile time option that defaults to being off.

#3347

 · 
vanilla

Add a ‘configure’ shell script in NetHack’s root directory, which will send it through a rudimentary series of prompts about the user’s system that will get it to run the setup.sh script on the correct hints file. This allows NetHack to be built on unix-like systems from scratch with the conventional “./configure && make && make install” command.

#3273

 · 
vanilla

Big rooms should use the ‘shortsighted’ level flag, which prevents monsters from homing in on the player from far away and would make it a bit more interesting than having all the monsters converge on the player once they arrive there.

#3248

 · 
vanilla

Allow lua files to specify a trap as “mapped”/”known”/”seen”/”premapped”/”visible”; when the trap is generated it will set its tseen value so that the player instantly sees it from a distance, like how most holes work.

#3247

 · 
vanilla

Because lua scripts can be loaded after level creation time in NetHack 3.7, take advantage of this to make some of the quests be throwing a feast for the player after they return to the home level with the quest artifact or with the nemesis dead, with all hostile monsters killed, a bunch of food spread around, etc.

#3184

 · 
vanilla

Every time a line is printed containing two adjacent spaces, the message is broken into two separate plines so that the (probably separate) statements can be broken up by the windowport better.

#3164

 · 
vanilla

Allow themed rooms to be specified as joined to the rest of the level, but only at preexisting doors on the edge of the room, and the level generator should not create new doors to connect it to the rest of the level. Internally, this means expanding a room’s ‘joined’ variable from a boolean to a larger type that can hold a third value. This would be useful for making themed rooms where it’s desirable to connect them to the map only at specific points.

#3118

 · 
vanilla

Augment struct mkroom with a short string (perhaps 3 or 8 characters). The purpose of this string is to store a unique identifier for non-special types of room, in particular different themed room types. This can then be used later in the game (e.g. for printing an entry message in certain themed rooms) without interfering with the actual room types as far as level generation is concerned.

This uses a string identifier mainly because that makes it straightforward to initialize the string in Lua, versus a numeric constant the name of which would not be exposed to Lua.

#3106

 · 
vanilla

Fuzz the turn on which Luck times out by hashing ubirthday together with (turns/300), and taking the result modulo 300. This means that Luck will still time out once every 300 turns, but the player can’t predict the exact turn on which it happens, and figuring out the exact turn it happens in one iteration is unhelpful for any future iterations.

If Luck is timing out once per 600 turns, still do the above, but do nothing on every other iteration. Determining which set of “every other” iterations to use could also be done by hashing ubirthday and seeing if the result is odd or even.

#3067

 · 
vanilla

Expand the behavior of the ‘unpaid’ object flag so that it tracks items that you have stolen from shops or looted from shops with the shopkeeper absent or had a pet steal. Unpaid items outside their shop can render as “stolen” in the object name, and attempting to sell them inside a shop (even if not the same one it was stolen from) will just cause the shopkeeper to claim it and give you no money.

#2911

 · 
vanilla

If a special level fails to load, its fallback is not to make a maze - instead, it creates a simple large rectangular open area stocked with some random monsters and objects (and stairs, if relevant). It is named the “Bug Room”.

#2888

 · 
vanilla

Traps track the turn on which they were created in the trap struct. This is used to determine the age of a web; creatures will tear through an older web more easily, and with less strength, than a young one.

(Possibly, when you spend time off-level, this trap timestamp updates to account for that time, because time is frozen when you aren’t on a level.)

#2883

 · 
EvilHack SporkHack

Iron safe additions:

  • When you crack an iron safe with a stethoscope, it sets an internal flag that indicates that you know the combination. (Opening it with opening magic won’t do this.) You can open any safe you have already cracked once, without needing to crack it again.
  • You can manually shut an iron safe with the close command.
  • Sometimes, the Oracle will tell you the combination of the next iron safe you will try to open as a minor consultation. The next time you open a safe that you don’t know the combination of, you get a message “You enter the combination the Oracle told you: X-Y-Z”, and the safe opens in one turn.
  • If you somehow manage to pick up and read an iron safe, it says “Croesus Safe & Lock Company, Fort Ludios, Yendor”.

Some way to close off the double-slow-digestion loophole by which you can survive indefinitely without food, by abusing the fact that ring hunger is counted on certain different turns for the left and right hand. Suggestions include:

  • Ring (and amulet) hunger is always counted on every 20th turn. This prevents the slow digestion trick, but makes it somewhat easier to avoid ring hunger from most other rings (though not significantly harder).
  • As above, but the hunger is offset by some random phase, determined by a hidden value that is constant in your game, such as ubirthday.
  • As above, but the random phase is different every 20 turns (e.g. by deterministic hashing of turns/20). Effectively, one turn in every 20-turn chunk will cause ring hunger, but it’s not possible to figure out which.
  • Have a random 1/20 chance of each ring causing hunger on every turn.

Squirt gun: a weapon which can be loaded with a potion (using up the potion), giving it ammo for 5-10 shots. When you fire it, a liquid projectile flies through the air, and causes potion effects (identical to those of throwing a whole potion) to whatever monster it hits.

It would be silly to define a separate liquid projectile object for each type of potion; probably the best implementation would be to have a single “splash of potion” object and use some field on the object, like corpsenm, to track what sort of potion it is.

Loading water into the squirt gun can be used to discipline pets.

#2839

 · 
vanilla

Allow quest monster generation to be specified more precisely than having a fixed set of probabilities for two specific monsters and two monster classes (e.g. the Wizard quest has a certain distribution of vampire bats, B, xorns, W, and random monsters; it is not possible to change this distribution without changing it for every quest).

Implementation details: keep the definitions within struct Role, but do it with, say, a struct of { int, bool, int } repeated however many times a quest needs. The first int is the % chance out of a hundred for this struct to be selected. The third is either a PM_* monster index or S_* monster class constant. The second just informs it which of those options it is.

When generating a quest monster, the game can then roll a d100 and iterate through this array of possible monsters, decrementing as it goes, until it hits the right element; if it goes all the way to the end without hitting any monster or class it generates a normal random monster. (This is more or less the same as the algorithm for picking items generated in shops.)

#2759

 · 
vanilla

Define a “power level” or a “tier” for each artifact, and when gifting an artifact, use the player’s experience level and other stats to calculate an appropriate tier to gift an artifact from. The only reason it feels bad to get Giantslayer is because you know you could have gotten something much more powerful but randomly didn’t; if the more powerful artifacts were simply not possible to get due to you not having the right stats yet, this might not happen as much.

#2694

 · 
vanilla

When you fail to read a spellbook, increment its read counter. The chance of a book crumbling to dust is no longer a flat 1/3; a never-read book will never crumble, and the chance increases the more times the book is read.

#2632

 · 
vanilla

Revamp the scroll of scare monster: rather than being good indefinitely as long as it’s not picked up, it will increment an internal counter whenever it scares a monster, by an amount equal to that monster’s HD or difficulty. The scroll disintegrates on the floor after this “total difficulty of monsters scared” counter exceeds some value.

#2599

 · 
vanilla

Occasionally, when generating a level 6+ spellbook, set its opoisoned flag to true. You get “The book was coated in contact poison!” not as a random spellbook reading failure effect, but when you try to read a book whose opoisoned is set to true. Triggering the contact poison usually (but not always) clears the opoisoned flag, so it won’t be poisoned if you try to read it again.

Dipping a poisoned spellbook in any healing potion will neutralize the poison and use up the potion. Possibly, dipping it in a potion of sickness will poison the book, though this would be useless unless monsters read spellbooks.

#2562

 · 
vanilla

Scare system that works on the player: the game tracks a list of monsters (or monster IDs) you are currently scared of and for how much longer you are afraid of that monster. You cannot attack a monster you are scared of in melee, nor can you use a ranged attack towards it when you can detect it in the line of fire, nor can you move directly towards it. (Trying to do any of these things won’t cost a turn.)

#2529

 · 
vanilla

Track how many turns the hero has spent on a level. As they spend more time there, the monster spawn rate diminishes, provided they aren’t in the end game with its special spawning rules.

#2522

 · 
vanilla

You can occasionally get a free food appraisal when you eat something. This is based on a random number computed from the turn and (possibly) the object ID, weighed against your Wisdom somehow, such that higher Wisdom increases the chance that you get the food appraisal.

#2369

 · 
vanilla

Review objects that are oc_big. Certain ones like chests and large boxes don’t count as big, but should be.

#2350

 · 
vanilla

Instead of storing door traps as part of the door state, use the trap field of struct lev with a special new DOORTRAP trap to indicate that there is a trap on the door space.

#2339

 · 
vanilla

Split up the job of TELEPORT_REGIONs in special level definition files into it and NOENTRY_REGION. Now, TELEPORT_REGION defines a region that cannot be horizontally teleported into or out of, whereas NOENTRY_REGION defines a region where you cannot enter the level if arriving in an unusual way (level teleport, falling in from above, cursed potion of gain level).

#2327

 · 
object properties patch

For variants with object properties, store a bitfield as the property mask in the objclass struct, and a separate one in the obj struct. This lets you define which properties are inherent to the object type (e.g. acid resistance and poison resistance for an alchemy smock) and which abnormal properties it has, which is useful because all alchemy smocks shouldn’t show up as “alchemy smock of poison resistance and acid resistance”.

#2319

 · 
vanilla

The xlogfile shows which turn a conduct was first broken on, rather than compacting all conducts into a bitmask.

#2302

 · 
vanilla

Level flag that prevents mapping as long as the local “boss” is alive (nemesis if anywhere in the Quest, demon lords/Croesus on their own levels). Maybe make the ‘nommap’ field 2 bits instead of 1.

#2242

 · 
vanilla

Rolling boulder traps don’t expect a boulder at a specific spot: they look up to 9 squares in all 8 directions for boulders, and the first boulder it finds will be triggered. If no boulder is in range, it may create one by dropping it from the ceiling in a suitable location, then rolling it across the trap (but only one boulder per trap will be created like this, and some traps generate incapable of creating a new boulder).

#2176

 · 
vanilla

Add code that allows the game to determine whether a room is a “dead-end” room (contains only 1 door which connects to the rest of the level, but unlike shop-compatible rooms, it’s okay to have closets.)

#2150

 · 
vanilla

Trees’ fruit is generated along with the tree (possibly buried for code purposes so that it shows up on object detection but not when you just look at the tree, or perhaps just “embedded in the tree” is good enough). When you kick or chop down the tree, this fruit is released rather than generating some fruit on the spot.

#2141

 · 
vanilla

Add a level attribute which serves as a multiplier for the monster spawn rate on that level. Can be specified in des files.

#2126

 · 
vanilla

When an artifact is destroyed, unset it as existing so that it can be gifted or wished for again.

#2033

 · 
vanilla

Revamp ring explosion on charging them: a ring enchanted to a positive value has a enchantment in 7 chance of “vibrating unexpectedly” or something similar. This is a warning and, like weapon vibration, does not hurt the ring. If you get this message, the ring’s otrapped flag is set. Charging a ring with otrapped set always explodes it.

There is no other way to ascertain the otrapped value of a ring, other than getting this message. A ring that generates at +4 or above has an enchantment in 10 chance of already having otrapped set. Discharging the ring via cursed charging or drain life has a 50% chance of removing the otrapped flag per point lost.

#2018

 · 
vanilla

Every lock in the game has a deterministic chance (based on object ID if a box and xyz coordinates if a door) of being unopenable by a credit card. Attempting gives the message “It doesn’t look like this lock can be opened with a credit card.”

  • Boxes have a constant 60% chance of being like this, and doors are based on depth, starting at 10% or so on level 1 and increasing to 80% by level 50.
  • Possibly extend this behavior to locks that can’t be opened by a key either, requiring either a lock pick or magic.

#1997

 · 
vanilla

When a crysknife leaves your inventory, it starts a timer with 10-50 turns. The timer is canceled if it reenters your inventory. If the timer times out on an unfixed crysknife, it reverts to a worm tooth; if a fixed one times out, it loses one point of enchantment and restarts the timer, or if it’s at +0 it reverts to a worm tooth.

#1956

 · 
vanilla

Confused genocide (uncursed or blessed) does not genocide you. Instead, it sets a level flag that prevents any more monsters from spawning on that level via the normal mechanism for the remainder of the game. (Monsters already created are unaffected.)

#1789

 · 
vanilla

Monster speed system that tries to keep all the good qualities of 3.6.1’s while also not letting randomization make monsters super fast or super slow: add a 16 bit int to the monst struct, and every 12 turns set (speed % 12) of them to 1. Then on each turn, the monster gets (speed / 12) moves, plus 1 if the bit for that turn is 1. This 12-turn cycle would need to be phase shifted randomly per monster (probably using hashing based on monster ID) so that you can’t move-count based on the turn clock.

Research on an efficient algorithm to generate the series of 12 bits has found this, though it might be more entropy-efficient in the long run simply to generate all 4094 permutations of bits and randomly pick one with the correct number of bits whenever needed.

#1604

 · 
vanilla

Internally mark all of your starting gear with a special flag; if you die before receiving the quest, any items marked with this flag get degraded when placed into your bones pile: things like magical armor becoming nonmagical, wands lose charges, scrolls may fade, and spellbooks are made too faint to read more than once.

#1579

 · 
UnNetHack

Fix the problem with Aleaxes creating a copy set of your gear which can then be used as polyfodder by flagging the armor that gets generated with an Aleax to polymorph only into non-magical items.

#1571

 · 
vanilla

Merge magic cancellation and monster magic resistance into a single stat “magical protection” which determines how easy it is for the magical portion of attacks to reach you and deal effects. All races have a low innate MP, but armor can raise it higher. Most monsters’ innate MP resembles their current monster magic resistance.

#1552

 · 
vanilla

Damage reduction from AC happens before half physical damage instead of afterward.

#1537

 · 
vanilla

Refactor to-hit mechanics into a simple 1d(attacker’s bonuses) versus 1d(defender’s bonuses) computation, where the attacker hits if their roll is higher.

#1521

 · 
vanilla

Adjust NetHack’s monster speed system to accommodate the additional property that if your speed is greater than a monster’s, it is impossible for that monster to move 2 turns in a row before you get to act.

#1512

 · 
vanilla

Remove the special damage bonus skill tables for bare-handed and martial arts combat. In place of them, scale up the die size based on skill: bare hands damage is a d2 at unskilled, a d3 at basic, etc. and martial arts doubles this die size, for a d4 at unskilled, a d6 at basic, and so on up to a d14 at grand master. Note that this still gives pretty poor damage output. Possibly it should be 2 dice being rolled (with the die size changing as described here).

#1495

 · 
vanilla

Monster generation in the Quest, when calculating your experience level, takes the maximum of your actual level and the minimum level required to receive the Quest.

Make it possible to leave bones on certain levels that have a branch entrance in them (mainly mine entrance, sokoban entrance, quest), by virtue of removing the branch entrance before saving it as bones. If branch upstairs are removed in this manner, replace it with a pile of rocks; if downstairs are removed in this manner, replace them with a pit.

Various ideas to give the bonus from certain sources of speed only as a bonus to movement, not to getting more turns to attack, use items, etc.

  • First, to classify: most agree that speed boots, intrinsic speed, and steed speed should be sources that only give extra movement opportunities, while polyform speed should give extra full turns. Less agreement on magical sources of very fast speed (the potion and haste self), but general opinion seems to be that these should also not be limited to movement.
  • Add a second pool of movement points that coexists with regular movement points, but can be used ONLY for moving. Speed boots and its fellow sources add to this pool, and any unused points in it are discarded at the end of the player’s turn. When you make a movement, points are deducted from this pool before being deducted from the “core” pool. Note that this approach can cause core points to accumulate to the point where the player can eventually get multiple non-movement actions on their turn.
  • Reduce the movement point cost if you are taking a movement action and have sources of movement-only speed. (This might interact oddly if general-purpose sources of speed continue to add movement points as usual.)

#1454

 · 
vanilla

Remove your experience level as a factor from the logic that determines how large a newly generated monster group will be. Instead, scale the group size based on depth. (And to take this further, groups created by magical means such as create monster should be independent of both factors).

#1378

 · 
vanilla

Allow the game administrator to put a random value that will be mixed into the game’s seed into the sysconf file, so that RNG-predicting manipulation can’t be used.

Rather than making weapons train 1-to-1 on all successful hits, make them train differently:

  • Weapons train only when their natural damage die rolls a 1 / rolls a max (ignoring other weapon damage bonuses), so that weaker weapons train faster than stronger ones, and weapon skill for stronger weapons comes in later in the game rather than having all players reach Expert relatively early in the game. This has the oddity of incentivizing orcish weapons over regular, elven or dwarvish though.
  • Make the enchantment of your weapon irrelevant to training or even make training slower with a positively enchanted weapon; if the weapon has magical homing assist technology, you’re not really building skill in how to use it that much.

A bunch of scattered ideas for handling candles, given that the hero almost always wants to have only one lit at a time:

  • Track all candles separately unless they have the exact same number of turns remaining.
  • Merge all candles automatically, but still keep track of each one’s lifetime. (This would enable lighting multiple candles and then having one of the stack burn out.) When you light part but not all of a stack, the game internally picks the shortest candles.
  • When candles merge, their lifetimes are averaged together.
  • You can’t light more than one candle at once at all. (Not a great idea since it would destroy the mechanic of lighting a large amount of candles for more light).
  • When candles merge, the candle with the shortest life automatically sacrifices as much of its lifetime as needed to “top up” the rest of the stack. That is, if the lifetime is 400, a candle with 50 turns remaining will merge with a candle with 375 turns remaining, making a stack of 2 candles with 25 turns remaining; when those 25 turns are up, it becomes a stack of 1 candle with 400 turns remaining.

#1229

 · 
vanilla

Make the Castle not a graveyard level, and compensate for this by altering wraith corpse drop odds somehow.

For most calculations that involve the depth of the Sanctum (score, level difficulty with the Amulet) use a constant 50 in place of the Sanctum depth.

#796

 · 
vanilla

Make the amount of bonus movement points from temporary speed directly dependent on its remaining duration, with it gradually decreasing back to normal as the speed wears off. Also provide diminishing returns where giving yourself more speed boosts on top of already having temporary speed gives you less extra duration than it otherwise would, until eventually you can’t get any more duration than 1 per turn. These bonus movement points should also stack directly with all other speed sources, and be visible to the user in the status bar.

Move angelic and demonic maledictions out of quest.txt/quest.lua; they have nothing to do with the quest. They should probably still live somewhere in the dat folder.

Add two fields to struct trap: an object, which represents the “ammo” of the trap (darts, arrows, potions of oil), and a number to represent the “level” of the trap (which can affect a lot of things, such as a to-hit bonus the trap gets, how hard it is to untrap, or what the skill was of the player when they set the trap).

#722

 · 
vanilla

All sinks spawn with a ring buried beneath them. If you kick the sink and get the “Flupp! Muddy water” message, the sink will spit out one of the rings buried under it. This removes the need for the S_LRING flag, and also allows players a non-destructive way to retrieve rings they dropped down the sink.

#709

 · 
vanilla

Change rne() so that it doesn’t depend on the player’s experience level: instead it depends on the current level depth. Specifically, change its theoretical maximum from max(XL/3, 5) to max(depth/5, 5).

Merge the damage versus small and damage versus large stats of weapons, since they don’t really matter that much and add mostly pointless complexity. Damage should be more dependent on skill than on the size of the target monster. The only problem is how to stop the highest-damage weapons from becoming used by everyone because they’re so optimal; perhaps skill should do something drastic like add an extra die with every skill level, allowing you to have weapons that are decent at low skill (by having multiple small base dice) but poorer at high skill (because adding more small dice doesn’t work as well).

#621

 · 
dNetHack

“Coffin” boxes should use corpsenm instead of spe to represent whether they contain a vampire. Useful because it could be used for other creatures (including Schroedinger’s Cat).

The number of levels a trap door will take you down is deterministic for every trap door. Possibly also extend the trapdoor and migration code to allow deterministically placing coordinates of things that fall through a trap door as well.

#614

 · 
vanilla

Rename AD_SEDU to AD_CHRM because it’s not really seduction.

#613

 · 
vanilla

Strictly separate the AD_SITM and AD_SEDU stealing attacks (hostile theft vs. charming to hand over an item), and remove the is_animal checks from the stealing code.

#607

 · 
vanilla

Merge dart and arrow traps into “missile” traps, that fire a random sort of missile. Also, make such traps store their ammo in the trap to be fired one by one or released when untrapped, not to be created one by one when triggering, as described in #768.

When the game looks up the scoreboard to generate a player name for a corpse, statue, or ghost, ignore any games that ended in an ascension.

#290

 · 
vanilla

Add a field to struct rm (or possibly reuse the flags field which is 5 bits) that determines the type of tree that is on that space, if typ == TREE. This enables you to control what kind of tree it is, and what kind of fruits you will get by kicking it, and whether you can get bees from it. Also add a level flag that allows a level designer to specify that all trees on the level should be barren.

#242

 · 
vanilla

Cut all map sizes in special level files down to at most 20 lines tall, so that there is extra space for messages and status line in an 80x24 terminal. Few levels currently use all 21 lines.

#193

 · 
vanilla

Add some region code preventing monsters from generating in (or even better, moving into) the pit corridors in Sokoban until the level is solved.

#157

 · 
vanilla

Race and polyform should not be distinct; they should be merged into a single permonst or corpsenm-like variable. Role is independent of race, and perhaps every monster should be described with a monster/role pair, with the role being null for most monsters (i.e. gnomish wizards are just gnomes that are also wizards). This might eliminate the entire concept of player race, internally, but transferring some of the racial effects on players (e.g. elves’ sleep resistance at XL 4) might be tricky.

#6

 · 
dNetHack

Blank spellbooks in flooded libraries generate with the “number of times read” counter full, so they can’t be polypiled into readable books.