isqrt adds some noticeable distortion artifacts to gradients (test case
I used is to draw a line from 10,10 to 20,15 with mindist = maxdist = 2
and see how the gradient is biased towards the upper right); changing
the distance calculations to use the square of the distance rather than
the raw distance avoids this. This makes radial gradients more radial,
and square gradients more square.
There still appears to be a bit of bias, but I think this is due to the
line algorithm not lining up perfectly with the tiles.
selection.gradient has some pretty unintuitive behavior, in that it
selects points that are NOT close to the defined center. I've used
gradient selections several times and so far all of them have had to be
negated, because I wanted to select points close to the center with a
decreasing probability further out.
This implements that behavior, and also fixes a bug in which the x,y
coordinates of the gradient center(s) were not converted properly when
used within a des.room or des.map. Also updated the lua documentation
for gradient.
I removed the "limited" argument, as it was previously used to control
whether the rest of the map outside the max given distance would be
included in the selection; now that the area beyond maxdist is naturally
never in the selection, it doesn't have much use. (And I can't think of
a reasonable use case for the inverse: wanting to select points close to
the center, with decreasing chance towards maxdist, but then select the
entire map beyond maxdist.)
Currently this does not affect any special levels or themed rooms
because none of them use selection.gradient.
The intuitive behavior of des.levregion or des.teleport_region when
"exclude" is left unspecified is that there is no exclusion area.
However, this wasn't actually the case: since l_get_lregion defaulted
the exclusion area to (0,0,0,0) and exclude_islev to 0, this meant that
the 0,0 space on the map would always be excluded from regions. In cases
where a region was specified with its inclusion area constrained to the
0,0 space of the map, this would create a "Couldn't place lregion"
impossible message.
This fixes that issue by defaulting the exclusion area to (-1,-1,-1,-1),
and if the exclusion area is left unspecified, forces exclude_islev=1.
This means that the exclusion zone will be outside the walkable space of
the level where it can't cause any problems.
If a level designer puts negative coordinates in their inclusion or
exclusion parameters, this might not work correctly, but negative region
coordinates aren't currently used anywhere and probably shouldn't be
supported anyway.
Items in initial hero inventory, or generated via lua in
special levels or themed rooms are not subject to this.
Code via xnethack by copperwater <aosdict@gmail.com>,
with some modifications.
The Gehennom changes broke the vibrating square, allowing hero to go
down into the Sanctum via stairs without performing the invocation.
Fix this by making the hellfill lua check for invocation level, and
placing down the vibrating square trap, instead of stairs.
Allow setting a per-level "temperature": hot, cold, or temperate
via special level flags. Currently it only affects some messages
in Gehennom, but it could be expanded to ice melting, water freezing,
or monster generation, for example.
Invalidates saves and bones.
Instead of just plain old boring mazes, spice up Gehennom by
occasionally adding lava, iron bars, or even mines-style levels
(with lava, of course).
Of the fixed Gehennom levels, only Asmodeus' lair has been changed
to add some random lava pools.
Also some lua fixes and changes:
- Fixed a selection negation bounding box being wrong.
- Fixed a selection negated and ORed returning wrong results.
- des.map now returns a selection of the map grids it touched.
- When using des.map contents-function the commands following the
map are not relative to it.
The consolidation of global variables from scattered source
files into decl.c and declared in decl.h was begun in 3.7.0.
Their placement in common files was done for centralized
initialization and potential re-initialization during a
"play again" scenario.
It wasn't really necessary for all of them to be housed in a
single huge structure to meet the "play again" requirement,
and the single huge structure has been a little unwieldy when
it comes to maintenance.
Following this commit, instead of one single extremely large structure
named 'g' to house all of the relocated global variables, they
are distributed into several ga through gz.
To make things easy for the developer, each variable is placed
into the struct corresponding to the starting letter of the variable.
That way, no lookup is required in order to know which struct houses
a particular variable, it is a simple match to the starting letter
for all the centralized global variables.
A global variable named 'amulets', would be found in ga.
ga.amulets
^ ^
A global varable named 'move', would be found in gm.
gm.moves
^ ^
A global variable named 'val_for_n_or_more' would be found in gv.
gv.val_for_n_or_more
^ ^
A global variable named 'youmonst' would be found in gy.
gy.youmonst
^ ^
Although gcc specifies support for declaring a function as
noreturn after the function name and parameters, other compilers
do so via an attribute at the start of the declaration. Add some
macro support for the attribute-at-the-beginning method:
o MS Visual Studio compiler
o Upcoming C23 standard (untested at this point)
Instead of using index() macro defined to strchr, use C99 strchr.
Instead of using rindex() macro defined to strrchr, use C99 strrchr.
If you want to try building on a platform that doesn't offer those
two functions, these are available:
define NOT_C99 /* to make some non-C99 code available */
define NEED_INDEX /* to define a macro for index() */
define NEED_RINDX /* to define a macro for rindex() */
Add a stack of 2 tins of spinach near the leader on the monk quest
start level and another stack of 2 blessed tins of spinach at a
random spot on the monk quest locate level, to compensate for the
inability to gain strength from giant corpses if they adhere to
vegan or vegetarian conduct. paxed supplied the 'tinplace' magic.
4 tins of spinach aren't nearly enough to get to 18/100, but by
uncursing the first pair, if necessary, and waiting until strength
is at least 18, they can be eaten to add 4..40 (average 22) points
of exceptional strength. (Players choosing either of those conducts
for other roles or foodless for any role are on their own as far as
boosting Str goes, same as before.)
The special level loader needed to be modified to handle tins of
spinach. It now accepts "spinach" as a fake monster type for an
object of type "tin". Also added support for empty tins since it
involved the same code, and use of fake monster type "empty" with
object type "egg" to be able to create generic (unhatchable) eggs.
(Wishing for "egg" produces those by default but it also accepts
explicit "empty egg" by coincidence.)
Noticed that an attempted terrain replacement wasn't taking hold even
though 'w' is supposed to mean "match any stone or wall"; this was
because w converts into non-terrain-type MATCH_WALL and replace_terrain
was doing a simple comparison on whether the potentially replaced
terrain matches that type. Add a special check here for w so it will
match the terrain types it's supposed to.
Note that using replace_terrain with 'w' now WILL match stone, since
this is the documented behavior of w, to match IS_STWALL rather than
just IS_WALL. If a level designer really wants to exclude stone, they
can work around this by either making a selection and filter out stone
terrain, or doing two replace_terrains with '-' and '|'.
Add two new themeroom functions that are called when generating
the level: pre_themerooms_generate and post_themerooms_generate,
calles before and after themerooms_generate.
Allow the buried treasure -themeroom to put down an engraving
anywhere on the level, hinting at the location of the treasure.
des.object contents function now gets the generated object passed
to it as a parameter.
Previously, the tetris-shaped rooms were always either
normal rooms, or turned into shops or other special rooms
in NetHack core. Now, the themed room lua code first picks
the themed room (which can be a themed or shaped), and some
of those will then pick a random filling (eg. ice floor,
traps, corpses, 3 altars).
Adds a new lua binding to create a selection picking locations
in current room.
The content-function in special level regions now get passed
the room data as a parameter.
mkmap creates mines-style full-level maps, so it should wipe
out all the room numbers in the level away. Also, it uses
temporary rooms for making sure the map is fully joined together;
those temporary rooms were left on the map, but should've
been cleared away.
When putting down map-parts on the level, don't remove the room
data which would be under that map; the map may have holes in them
(using the "x" map char), so a room may still exist there.
I don't think it matters if there is any room data which doesn't
have any room numbers referring to it in the level.
(Usually the special levels use map right after level_init anyway,
so there wouldn't be any rooms in the level)
Calling des.mineralize() with no arguments was equivalent to calling it
and manually specifying gem_prob = 0, gold_prob = 0, etc. Which meant
that no mineralization would actually happen.
Instead, make this match the intuitive behavior, and pass in -1
probabilities as defaults -- which the mineralize() function interprets
as the caller wanting to use the standard probabilities for a level of
that depth, as if it were not a special level.
This change does not affect any special level files since des.mineralize
is not currently used in any of them.
This is a large iteration on a previous implementation of making
nh.getmap() parse its coordinates as relative to the last defined map or
room rather than absolute to the entire level. Now, everything in the
nh.* and obj.* functions interprets coords as relative rather than
absolute. (By default; if no map or room has been defined, or if the lua
code is executing after level creation is done, they will interpret the
coordinates as absolute).
The general motivation is basically the same - routines that use
absolute coordinates are difficult to use in level creation routines,
because then the designer has to remember to convert the relative
coordinate to an absolute one (and that was impossible before
nh.abscoord was added, particularly in themed rooms). And once
nh.getmap() takes relative coordinates, it would be very strange to have
all the other functions (setting timers, burying objects, etc) remain
with absolute ones.
In a couple places, code is changed to account for coordinates that are
relative to a *room* (which uses g.coder->croom->[lx,ly] as an offset,
instead of relative to a *map*, which uses [xstart,ystart].
Specifically, selection.iterate did not account for this, and without
this the ice themed room timer was not being started in the proper
place.
All tests are updated to respect the new behavior. Most of the modified
functions are not actually used anywhere in level files; the one
exception is starting a timer in a themed room, and that has been
adjusted.
Documentation updated as well to clarify when various things are tossing
around relative and absolute coordinates, both in comments and in
lua.adoc.
- Add bounds, so that we don't process any locations outside
as those locations are known to be unset
- The bounds are only recalculated if needed
- Replace instances of selection_not where we actually want
a new selection with all locations set
By temporarily changing the type definition for each of xint16 and
coordxy to int32_t, the compiler was able to find several places where
the type definitions were wrong.
Fix wizard mode issues pointed out by the #wizmakemap fix. If a
shopkeeper or temple priest is on a different level and its home
level gets flipped, monst eshk or epri data became invalid and would
cause trouble if the shk or priest ever made it back to home level.
If a vault guard is maintaining a temporary corridor and the level
gets flipped, the data became invalid. If you used #wizfliplevel
while the guard was leading you out, the corridor spots would be
flipped along with the rest of the map but the guards's temporary
corridor data didn't match. Breaches in the vault walls would be
sealed, then the guard would just mill around, never finishing
leading the hero out.
One of the drivers of this change was that screen coordinates require a
type that can hold values greater than 127. Parameters to the window
port routines require a large type in order to be able to have values
a fair bit larger than COLNO and ROWNO passed to them, particularly for
their use to the right of the map window.
This splits the uses of xchar into 3 different situations, and adjusts
their type and size:
xchar
|
-----------------------
| | |
coordxy xint16 xint8
coordxy: Actual x or y coordinates for various things (moved to 16-bits).
xint16: Same data size as coordxy, but for non-coordinate use (16-bits).
xint8: There are only a few use cases initially, where it was very
plain to see that the variable could remain as 8-bits, rather
than be bumped to 16-bits. There are probably more such cases
that could be changed after additional review.
Note: This first changed all xchar variables to coordxy. Some were
reviewed and got changed to xint16 or xint8 when it became apparent that
their usage was not for coordinates.
This increments EDITLEVEL in patchlevel.h
selection_new() returns an address of malloc()'ed buffer.
If ov is null, this value is discarded without freeing the buffer.
To avoid this, move null-checks before calling selection_new().
Also, remove null-check of the return value of selection_new()
because it always returns non-null.
High altars and normal temple altars had identical altarmasks, so
there was no way to distinguish between the two based on the altarmask
alone. Instead, anywhere it was necessary to determine whether an altar
was a high altar included a check whether the hero was currently the
Astral Plane or Moloch's Sanctum, and assumed any temple altar was the
high altar.
Since there's an extra, unused bit in altarmask anyway, use it to
explicitly mark high altars -- the lua level files already distinguish
between normal temple altars and so-called 'sanctum' altars anyway, so
rather than throwing away this distinction when generating the level,
keep it in the altarmask and use it in place of various u.uz checks.
I think this would require incrementing EDITLEVEL again...
This is enough to prevent abuse by denying access to functions and
denial of service (RAM and instruction step limits), but not enough
to allow restricted use of things that require finer control (e.g.
filesystem access).
If something goes wrong, the whole thing can be turned off, for
now, in config.h (see NHL_SANDBOX).
None of the current functionality requires changes to build systems;
some of the possible future functionality may require some #defines
- TBD. There is lots of dead code (#ifdef notyet) for bits of that
additional functionality; we can rip it out if we don't want those
additions or we can complete (parts of) it depending on our needs.
All current uses of Lua are connected to sandboxes and guarded with
nhl_pcall (sandbox and lua_pcall wrapper); options and limits can
be set at the callsites in the passed nhl_sandbox_info. Some of
the error handling may be wrong - panic() vs. impossible() vs
silence.
Memory and instruction step limits should be tuned prior to release;
there's no point tuning them now.
Like the commit message for #743, the followup to it was misIDed
as #744. This one is for #744. There's no need to check a name
against all the "<class> of typename" unless it contains " of ".
and actually do so in the lua files.
Before this, it was not possible to specify (for example) "scroll of
teleportation" in des.object() because there is actually no object
defined in objects.h named "scroll of teleportation", so
find_objtype() failed to find it. Instead, one had to request
"teleportation", but that is ambiguous, and find_objtype() would find
the first defined item with that name instead (ring of teleportation).
In cases of ambiguity, I referred to the des files from 3.6.6 (before
the lua conversion).
Instead of hardcoding the lava terrain change in core, if the stairs
are created in a fixed location, force the terrain to room floor first.
Move the surrounding lava changing to room floor to the Val-goal lua
file.
placed on lava spot on Valkyrie goal level
Reported by k2, arriving at the final level of the Valkyrie quest
can issue a recently added impossible
| mkstairs: placing stairs up on molten lava at <68,13>
The report said it was easy to reproduce, but it took me multiple
tries (so not hard to do, but not a sure thing on any given attempt).
The stairs on that level are placed at specific coordinates that
are outside the pre-mapped area, so there's no guarantee that their
spot will be suitable for stairs. The underlying terrian changes
from lava to stair, but only after the warning about molten lava.
This hack solves that particular level but is not a general solution
for this type of thing. When about to make stairs on a lava spot,
change that spot to normal floor first. Plus 50:50 chance to change
each adjacent lava spot to floor too so that there's decent chance
for some elbow room upon arrival.
Also, turn the no-flip attribute off for that level so that 'fixed'
location of the stairs can occur in four different places.
Fixes#730
Noticed when I tried to create a male monster of a species that permits
both males and females (i.e. not a single-gender or neuter species),
half the time the monster ended up female anyway. This was because
get_table_montype picks a random monster gender for such species, and
lspo_monster just sets it to that, making it impossible to deliberately
have a monster of a certain gender.
This fixes that by defaulting the "female" table argument to random
instead of false, and then checking to see whether the level file set it
to something other than random. If so, it uses that value.
I debated whether this should allow a level designer to make a monster
of a gender that conflicts with their species, such as a male nymph, but
erred on the side of respecting the species. So attempting to specify a
male nymph, etc. will still result in a female one.
When creating stairs in a random location in a special level lua file,
there was a chance it overwrote eg. other stairs. Make randomly placed
stairs pick locations with room, corridor, or ice terrain.
Fixes github issue #702
Make selection rndcoord return a table with x and y keys.
Allow (most) coordinate parameters accept such a table.
Fix selection and des lua tests broken by the above changes and
an earlier change, because selections tried to set terrain
at column 0, and it now causes a complaint.