In the special case of gravel that's at the bottom
of a mapchunk, we have no way to tell if it's held
up from below, so for safety, we don't want to
leave it potentially floating, but we also don't want
to carve out air voids underground just because
a gravel deposit sits at the bottom of a chunk, so
replace the bottom gravel with stone, so that the
column will be supported. Underground, a little
extra stone that just so happens to hold up all
gravel that sits at certain y levels every 80m will
probably not seem remarkable (I was unable to
find obvious examples of this effect while testing
for far longer than it took to notice the air voids).
Adding cobble/sandstone just arbitrarily holding
up gravelfalls looks awkward. Instead, just clear
them out, like they fell long ago when the stone
weakened into gravel but were scattered long ago.
Instead of wasting compute cycles running falling
node physics on freshly generated terrain where
the player would only see the aftermath anyway,
just shore them up with non-falling alternatives.
Nodes are checked for falling automatically upon
mapblocks being loaded, and also every now and
then in the background. In most cases this should
mean that nodes that should fall will fall as soon as
they are loaded, or pretty shortly therafter, and in
rare cases where something is missed, they should
"catch up" eventually.
Processing falling stuff is surprisingly expensive,
especially when loading an ocean floor where nearly
every node is a sand node, so a lot of complex
optimizations and limits were put in place to prevent
this from lagging the game. Pre-checks are done to
avoid calling MT's builtin falling check code, which
is surprisingly inefficient (even by MT standards) and
limits prevent it from either taking too much time
itself or spawning falling_node entities beyond a
safe limit.
Spontaneous node falling was intended to serve two
purposes originally:
- Prevent falling nodes from remaining nonsensically
suspended indefinitely from bad mapgen.
- Add a bit of tension to the game, back when it was
envisioned as more "hardcore" in more traditional
a sense.
The feature no longer fits with the tone and themes
of the game today. Sudden cave-ins caused by the
"sound/vibrations" of players disturbing an area are
largely indifferent to most players, confusing to
some, and very much hated by a few.
Snuff, ignite, and growtrees now use server priv
instead of debug, to be more consistent with other
server maintenance cheats like quell and stasis.
The "debug" priv may be granted to give players
read-only access to more info in the world, so use
the "server" priv for more read/write access.
Before, non-node items (tools and craftitems) were
exempted from opacity entirely and were always
transparent. Now, they use the same optic_opaque
and optic_transparent groups, are opaque by default,
and honor the sunlight_propagates heuristic (even
though this is rarely used for non-node items).
The following things were nodes that were defined as
sunlight_propagates = true but NOW will be opaque:
- shaftcraft items like sticks, staves, bars, rods, ladders,
frames, and torches
- flora such as rushes and flowers
- sedges, but only at max growth (make it now possible
to detect and automate sedge growth)
Instead of using variadics, hard-code array accesses for the
expected arity of door_operate_core, throwing away any
"future-proofing" that variadics were supposed to offer.
This is because in Lua 5.1, the length of arrays that contain
nil values anywhere in the sequence is apparently undefined, and
implementations are free to return the position of any value that
is followd by a nil, so length of {1, nil, 3} could be 1 or 3.
PUC lua seems to generally return 3, but LuaJIT takes more
liberties here (presumably exploiting UB for optimizations).
The problem arises when trying to unpack({1, nil, 3}) and LuaJIT
sees the length as 1 so return only <1>, instead of <1, nil, 3>.
This means that calling nodecore.door_operate(pos, ni, dir),
which was INTENDED for this API (it would look up node for you if
you didn't provide one) could result in the dir param being
lost due to packing the args into an array to defer them, and then
that array getting only partially unpacked.
Reference to WONTFIX bug where LuaJIT claims this is allowed UB:
https://github.com/LuaJIT/LuaJIT/issues/527
The solution for now is to stop relying on unpack (which is sort
of easy in its own way since I never really liked it). In the
specific case of nodecore.door_operate in particular, adding
more positional args (it already has 3) would be bad for other
reasons (should switch to a named passing style).
Some external rotation mods may change the way
node rotation works and put doors into a state where
they are one of the 12 "reversed" rotations instead of
the 12 "canonical" ones. Renormalize door facing
before processing rotations to ensure that these doors
are corrected automatically.
Deprecate the old APIs that used to close over the
metadata about valid node rotations. Now expose
this data in the node definition, and use a generic
non-closure function to handle the callback.
nodedef.spindata contains:
qty = number of allowed rotations total
cycle = table keyed on current param2 and valued with
next param2 for next rotation
equiv = table keyed by each possible param2 (24 keys)
and valued with canonical equivalent for that param2
...along with possibly other downstream bugs.
We had stripped the metadata off the wield item in the
pummel state tracking to make it so that changes to
wield item metadata (especially things that could be
changed by AISM while the player is using the thing)
would not cancel a pummel, but forgot that the wield
metadata that gets put into the pummel state is ALSO
fed to the recipe check. Use a separate key for this
stripped only-for-comparison version, and keep the
proper itemstack with full meta for recipe checks.
NodeCore will probably never use this itself, but it
can be used for mod content (i.e. Winter's waterlogged
containers) that will never allow infiltration of air
whether the sides are open or not.
While we don't need to include these in the .tr file, if the
translators verified that the translated string matches the
source string (e.g. lines with only a URL or other technical
information) it still counts toward translation completion %.
Optic checks follow a plan-then-commit phased process,
but ablation processing was happening (and modifying
the map) during the "plan" phase, which was almost
certainly the root cause of the optics duplication bug
that was just mitigated.
Add a mandatory "defer" function to the optic_check
protocol that nodes can use to peform additional
custom logic AFTER the commit phase has completed,
and move all door ablation actions there.
The "optic family" mitigation is left in place to also
provide a heuristic for verifying the problem has been
resolved.
Optic nodes are each assigned a "family" of nodes that
are equivalent things with different power states. Optic
commit is not allowed to replace any node with another
node outside of the source node's family. This at least
prevents optic_commit replacing an air node with an
optic node (the way the dupe manifests). Log a warning
whenever this happens.
Unfortunately we still don't yet know the actual cause of
the problem, so this is just a workaround, not an actual
fix.
All rotatable node selection boxes should be as symmetrical as
possible so that rotating a node without moving the cursor won't
change which node the cursor is pointed at by resizing the
rotated selection box out of the way, which can cause repeated
right-clicking to miss and hit another node.
Prisms were corrected for this some time ago. Lenses were
apparently close enough that this was missed, or considered not
important enough, but this change makes things much more
consistent, including making the lens and prism hitboxes match.
Show a textual fuzzy description of how long a player has been
offline (in terms of in-game time, not realtime) in player ghost
looktips instead of just "Offline Player", if the last seen time
is actually known. Can be disabled by a setting.
When placing an optic node into an existing beam path, it
immediately acquires the correct state rather than being placed
as "inactive" and needing one tick to activate. This would cause
a "down" pulse to travel down networks when placing a "vampiric
tap" into an existing beam path, even if it was placed with the
correct initial rotation. This reduces a source of noise in
optic circuit building that doesn't appear to add any meaningful
challenge, only nuisance.
Prevent stack from doing selection-reanimation
by just setting the time at which annealing would
happen once rather than keeping a count of
elapsed time.