Commit Graph

1990 Commits

Author SHA1 Message Date
Aaron Kimbrell
4ad7ddd9c6 feat: add missing force field, jetpack NPC and Skullkin volume scripts
Ported from the live server scripts that ship with the client:

- scripts\ai\LS\ForceFieldEffect.lua (LEGO Club and Starbase 3001 force
  fields): plays effect 3671 "cast" on whatever touches the phantom.
- scripts\ai\NP\L_NPC_NP_JETPACK_GUY.lua (jetpack ambient NPCs): plays
  the object's "launch" effect when used.
- scripts\02_server\Map\AM\L_SKELETON_SPAWNER_VOLUME.lua (Crux Prime
  Skullkin area): with 10 or more players in the volume the Skullkin
  engineer, miner and patroller networks maintain 2/6/6 instead of
  1/4/4, with a 60 second cooldown between changes and a catch-up check
  when it ends. Only players are counted, and the count never goes
  below zero.

WBL_Enemy_Grabbler.lua only sets wander variables for the old Lua
movement AI, so it joins the other alpha wander scripts in the excluded
list instead of logging as missing.

Refs #746

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 22:30:46 -05:00
Aaron Kimbrell
c485ab2d38 chore: remove finished TODOs from the Spider Queen script
The faction and immunity change, spawning the spider wave, knockbacks
and reading the animation time are all implemented right below their
TODOs, so the comments were stale. No behaviour change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 22:30:45 -05:00
Aaron Kimbrell
b316686538 fix: Crux Prime dropship computer respawns and no longer crashes
The dropship computer's OnDie activated the "next" spawner network
(pipe number + 1) when a player smashed it and only fell back to the
first network otherwise. The computer has no next network, so once a
player smashed it (easiest with area attacks like the Doom Hammer) it
never came back. The live script (L_DROPSHIP_COMPUTER.lua, shipped in
the client's server scripts) always resets its own network and
activates the first one, which is what it does now.

OnUse also dereferenced GetMission(979) without a null check, crashing
the world when a player without that mission used the built computer.
It now matches the live script: only players with the mission active
get the Nexus Talon data card, once. SsModularBuildServer had the same
unchecked GetMission and now checks the mission state.

Fixes #596

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 22:30:45 -05:00
Aaron Kimbrell
040d7ec067 fix: ignore whitespace around config keys and values
"client_location = ../client " or a value with a trailing space is a
common setup mistake that surfaces much later as an unrelated error (a
missing client folder, a failed database login). Keys and values are now
trimmed of spaces, tabs and line endings when the ini is read, which
also covers the \r of files saved with Windows line endings. Spaces
inside a value are kept, and lines with an empty key are ignored.

Verified with a new unit test that loads an ini with padded keys and
values.

Refs #1113

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 22:30:45 -05:00
Aaron Kimbrell
d671d26635 fix: character xml load and save no longer crash on bad data
Loading a character whose xml has a malformed flag threw out of
std::stoul, and a character whose xml failed to parse (or has no obj
tag) dereferenced null on load and again on every save.

- Flags are parsed with TryParse and a malformed one is logged and
  skipped instead of aborting the load.
- The quick parse checks for the obj and items tags before using them.
- Saving refuses to run when the document has no obj tag, logging that
  the character was not saved, instead of crashing and taking every
  other player's unsaved progress with it.
- The remaining obj-child lookups go through a helper that returns null
  when the root is missing.

Component LoadFromXml/UpdateXml still assume a valid document; refusing
to load a player with invalid xml belongs in the world server's load
path, which is being converted separately.

Refs #1332

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 22:30:45 -05:00
Aaron Kimbrell
536c36227c fix: read set_faction the way the client does
Level files set factions with set_faction, and most of the string-typed
values have a trailing space ("13:6 ", "13:-1 ") or hold several
factions separated by spaces ("13:4 6"). The server split only on ';'
and TryParse rejects trailing characters, so about 1,500 placed objects
(smashable lanterns, fences, candles, treasure chest spawners and so on)
silently kept their component faction instead.

LWODestroyableComponent::LoadConfigData in the 1.10.64 client reads
set_faction as a string, splits it on both ';' and ' ', and replaces its
faction list with the result. The server now does the same: split on
both, skip empty pieces, and replace the factions (SetFaction for the
first, AddFaction for the rest). This also removes the TODO about
splitting on spaces, and the faction list from the destructible table
no longer uses std::stoi on a token that TryParse already parsed.

Values in the shipped maps were counted with a scan of the .lvl files.
Needs an in-game check that objects placed with set_faction (for example
the AG Survival buff stations and NT treasure chests) are still targeted
or ignored as expected; this may also affect #1301.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 22:30:45 -05:00
Aaron Kimbrell
363e02e963 fix: enemies and summons drop targets that have died
FindTarget kept returning the current target while it stayed inside the
tether radius, even after it died, so enemies stood over dead players
(most visible in long respawn areas like the Battle of Nimbus Station)
and pet or summon AI hovered over enemies playing their death animation.

A dead current target now loses its threat and a new target is picked,
and dead entities are skipped both as proximity candidates and as threat
entries. Players who respawn are picked up again through the usual
proximity and threat paths.

Verified by building WorldServer and the test suite; needs an in-game
check (die to an enemy, the enemy should return to idle or pick another
player).

Fixes #1428

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 22:30:44 -05:00
Aaron Kimbrell
8a2ccb1ae7 fix: bound AMF3 decoding and stop hashing client keys
Client-sent AMF (CONTROL_BEHAVIORS) was decoded without limits on the
associative part of arrays, on nesting depth, or on the total number of
values. Associative entries went into an unordered_map with the standard
unseeded string hash, so a client could pick colliding keys and make
insertion quadratic, and deeply nested arrays recursed until the stack
overflowed, crashing the world server.

- The associative map is now an ordered std::map (O(log n) whatever the
  keys, deterministic serialization order).
- Each array allows at most 10,000 associative entries, the same as the
  existing dense limit, which is now checked before anything is read.
- Arrays may nest at most 32 deep and one deserializer reads at most
  100,000 values. Every limit throws, which the only caller already
  catches and drops the message.
- Inserting a duplicate key keeps the last value and no longer returns a
  reference to a value that was destroyed when the key already held null.

Verified with new unit tests for each limit (including a 100,000 deep
nesting that previously overflowed the stack) and the existing live
packet test, which still decodes.

Fixes #2035

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 22:30:44 -05:00
Aaron Kimbrell
e213a7aeff feat: web dashboard and playground work
The NexusDashboard-parity dashboard (dDashboardServer) and everything built on it on the experimental branch:
accounts, characters, properties and moderation tools, permissions shared with in-game slash commands, economy
reports, World 3D and property 3D views with client scenery, scheduled events (features, vanity changes, live
events, announcements, restarts), vanity files and events, the CDClient browser, the message inspector with saved
captures, chat filter tools, community challenges, live ops, the AI moderator helper, and the server-side changes
they need.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 22:30:43 -05:00
Aaron Kimbrell
854d02f509 docs: what live servers sent around world transfers
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 22:30:37 -05:00
Aaron Kimbrell
6f58a1f23f feat: worlds move their players to another instance, GM commands
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 22:30:37 -05:00
Aaron Kimbrell
b0f81a1c8f feat: master coordinates moving an instance's players (replace, merge)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 22:30:36 -05:00
Aaron Kimbrell
b1930ed4ed feat: instance migration messages and planning
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 22:30:36 -05:00
Aaron Kimbrell
00b7edfcf0 docs: how the client switches world servers, and instance migrations
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 22:30:36 -05:00
Aaron Kimbrell
1f1edf790f fix(behaviour): don't crash on MissionDialogueOK from an unknown responder
THIS CHANGES SERVER BEHAVIOUR (intentionally); no wire change.

The MissionDialogueOK handler (carried over verbatim from
GameMessages::HandleMissionDialogOK) dereferenced the responder without
a null check, so a client naming an object that doesn't exist crashed
the world server. It now logs and ignores the message; the script
callback is no longer called with a null player (that path always
ended in the crash).

Test: handling a MissionDialogueOK with an unknown responder crashes
without this change and passes with it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 22:30:36 -05:00
Aaron Kimbrell
d599b788d0 refactor: mission, flag and collectible game messages as structs
Converts OfferMission, NotifyMission, NotifyMissionTask, ResetMissions,
NotifyClientFlagChange and NotifyLevelRewards (sent with SendToClient:
the old functions never broadcast) and the received RespondToMission,
MissionDialogueOK, RequestLinkedMission, SetFlag and HasBeenCollected
to NetGameMsgs in MissionMessages.{h,cpp}. Callers are switched,
OfferMission's send-it-twice behaviour moves to MissionOfferComponent,
the received messages are registered in GameMessageHandler's map and
the old functions are deleted. Handlers are copied verbatim. The
byte-equality helper can now compare SendToClient messages.

No wire change and no change in recipients. Verified byte for byte
against a frozen verbatim copy of the old functions over an input grid
(including OfferMission's two packets), received messages compared with
the old handlers' read sequences and every truncated payload rejected,
hand computed golden bytes and round trips. Layouts confirmed against
the 1.10.64 client in Ghidra.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 22:30:36 -05:00
Aaron Kimbrell
2681cc51ff refactor: racing and vehicle game messages as structs
Converts the 12 racing / vehicle / modular assembly messages DLU sends
and the 8 it receives to NetGameMsgs in RacingMessages.{h,cpp}, adds
eRacingClientNotificationType for NotifyRacingClient's event type,
switches the callers (RacingControlComponent, CarBoostBehavior,
PossessorComponent, slash commands), registers the received messages
in GameMessageHandler's map and deletes the old functions. Handlers are
copied verbatim. Also moves the byte-equality test helpers into a
shared GameMessageTestUtils.h.

No wire change. Verified byte for byte against a frozen verbatim copy
of the old functions over an input grid, to one client and broadcast;
received messages compared with the old handlers' read sequences
(including all 16 optional-field combinations of
VehicleNotifyHitImaginationServer) and every truncated payload
rejected; hand computed golden bytes; round trips; a deliberate field
mutation made the tests fail. Layouts confirmed against the 1.10.64
client in Ghidra. Malformed received messages are
dropped (see the foundations commit).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 22:30:36 -05:00
Aaron Kimbrell
7fb75f0484 refactor: activity game messages as structs
Converts ActivityEnter, ActivityExit, ActivityStart, ActivityStop,
ActivityPause, StartActivityTime, RequestActivityEnter,
RequestActivityExit and ShowActivityCountdown to NetGameMsgs in
ActivityMessages.{h,cpp}, switches their callers (racing, shooting
gallery, survival, waves, AG course, NT combat challenge) and deletes
the old GameMessages::Send* / HandleRequestActivityExit functions.
REQUEST_ACTIVITY_EXIT is registered in GameMessageHandler's map.

No wire change. Verified by comparing every struct byte for byte with a
frozen verbatim copy of the old functions (tests/.../Legacy) over a
grid of inputs, both to one client and as a broadcast (a temporary test
proved the copy matched production before it was deleted), plus hand
computed golden bytes, round trips and a deliberate field mutation that
made the tests fail. Layouts confirmed against the 1.10.64 client in
Ghidra. Only difference: a broadcast no longer makes
the extra Send(UNASSIGNED, false) that RakNet already rejected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 22:30:35 -05:00
Aaron Kimbrell
ac858dd1e2 fix(wire): UnSmash only writes duration when it is not the default
THIS CHANGES THE BYTES SENT TO THE CLIENT (intentionally).

UnSmash::Serialize wrote the "duration != 3.0f" flag but guarded the
value with "builderID != 3.0f" (a typo), so the 32-bit duration was
always written, even after a 0 flag. The LEGO Universe 1.10.64 client
(GameMessage::UnSmash::Serialize/Deserialize @ 00dc0e80/00dc0f50,
default 3.0f @ 017e66b4) writes it only when
duration != 3.0f. The client read the flag and ignored the trailing 32
bits, so this removes 4 junk bytes from default UnSmash messages;
messages with a non-default duration are unchanged.

Uses WriteOptional/ReadOptional and adds Deserialize. Verified with
hand computed golden bits for every flag combination and round trips.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 22:30:35 -05:00
Aaron Kimbrell
350d35dab5 chore: foundations for struct based packets and game messages
First step of moving hand written bitstream code to struct based
messages (see docs/PacketArchitecture.md). No wire changes.

- GameMsg is now only a server-internal event (delivered to handlers
  registered with RegisterMsg); NetGameMsg is a wire message with
  Send(sysAddr) (UNASSIGNED broadcasts), SendToClient(sysAddr) (one
  client, never broadcasts), WritePacket, Serialize, Deserialize and
  Handle. Mixing them up is now a compile error. NetGameMsgEvent<T> /
  DeliverLocally carry a wire message to local handlers; loot drops,
  pickup, the object debugger, GM invisibility and model RequestUse
  use them. The GM invisibility message keeps its target, so its bytes
  are unchanged.
- Behaviour change: GameMessageHandler logs and drops messages whose
  Deserialize fails instead of handling them with default fields (the
  4 messages already registered: RequestUse, RequestServerObjectInfo,
  ShootingGalleryFire, PickupItem).
- LUBitStream (kept as on main) gets a virtual destructor (Mail deleted
  derived packets through the base) and WritePacket, also used by
  ChatPackets::SendRoutedMsg with identical bytes.
- BitStreamUtils::WriteOptional/ReadOptional for default-flag fields
  and WriteLengthPrefixed/ReadLengthPrefixed for length prefixed
  strings.
- Every message ID enumerator (MessageType::*, ServiceType, Mail's
  wire enums) is pinned with static_asserts, so renumbering or removing
  one fails to compile.
- Tests: dServerMock copies each sent packet (it kept a pointer to the
  caller's destroyed BitStream); PacketTestUtils.h compares packets bit
  for bit; helper tests use hand computed golden bytes and equality
  with the hand written patterns they replace; compile-time checks
  keep the wire/internal split in place.
- docs/PacketArchitecture.md: survey, target architecture,
  conventions, verification method and migration plan.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 22:30:35 -05:00
David Markowitz
66e2790193 doc: add notes about AI tooling (#2039) canary 2026-09-28 13:52:55 -07:00
David Markowitz
6f3a8d502f feat: logging structure improvements (#2037)
* feat: logging structure improvements

tested that logs are sorted by the folder argument and that the directories are created recursively.
tested that crash dumps follow the same exact name (+Crash_...name..._pid.log) so you can match the crash dump to the corresponding log file much easier

* Potential fix for pull request finding 'Use parsed cloneID when constructing the log folder'

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* const

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-09-26 21:35:58 -05:00
David Markowitz
129199e452 fix: actually reject empty properties (#2036) 2026-09-18 04:49:55 -05:00
David Markowitz
057de71773 feat: Add cheat detection knowledge (#2034)
* feat: Add cheat detection knowledge

tested that getting kicked correctly displays the reasons why and their corresponding values
tested that a user with developer permissions is not kicked for said funness
tested that logs are correct for those funness values which i could actually trigger

* fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-09-18 02:30:59 -07:00
David Markowitz
8f2004c9a3 fix: enemies on path not resetting aggro radius correctly (#2015)
tested that aggro is reset correctly and enemies no longer infinitely path to players.  Tether now properly calculates the time to tether instead of guessing
2026-07-17 23:05:17 -07:00
David Markowitz
35337291fa feat: debugger additions (#2013)
* feat: debugger additions

Add type field for links in flash
Add warning level for dangerous buttons
fix uninitialzied memory with jetpack variable
remove a bunch of duplicated position push code

tested that the ui is still functional and components with multiple physics components have all their details visible.
tested that jetpack is initialized now

* remove amf3 header

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* fixes

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-06-24 17:18:16 -05:00
Aaron Kimbrell
5d523a1e7b fix: handling of the same skill on multiple items (#1990)
* refactor: update behavior slot determination to use equipLocation instead of itemType

* fix: improve skill management in InventoryComponent to ensure correct client updates
2026-06-22 17:16:57 -07:00
David Markowitz
5745742c91 feat: dragon instance script (#2012)
* feat: dragon instance script

* Update FvDragonInstanceServer.h

* feat: implement ronin script

* Update CountdownDestroyAI.cpp

* default initialize

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* remove unused handlers

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* use float

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* fixes

* Update ScriptComponent.h

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-06-22 17:16:21 -07:00
Aaron Kimbrell
62f58f5307 chore: cleanup possession handling (#1984)
* feat: enhance possession mechanics with skill set integration and improved message handling

* fix: restore SetPossessor in Mount() and scope IsRacing to vehicles

SetPossessor was missing from Mount(), breaking direct possessions via
PossessableComponent::OnUse which bypasses HandlePossession. IsRacing
now only set/cleared when the mount has HavokVehiclePhysicsComponent,
preventing non-vehicle possessions from incorrectly affecting the
distance-driven statistic.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 21:39:51 -07:00
David Markowitz
83707e2210 feat: add the lightning orb script* (#2011)
* feat: add the lightning orb script*

Doesn't do anything because MPC don't work...

* Update LightningOrbServer.cpp
2026-06-21 00:30:44 -05:00
David Markowitz
ccc029424c fix: fv mpc in the tree (#2010)
tested that it now spawns in the lower path point instead.

fixes #741
2026-06-20 18:53:49 -05:00
David Markowitz
ccad52a5ce feat: testmap improvements (#2009)
* feat: testmap improvements

fixes #1191

removed the force flag because it would only work to let you softlock your character.

tested that taking the lego club door now spawns you at the lego club/ns lego club doors always vs letting you spawn where ever.

tested that a testmap no longer spawns you where you last were in a zone and instead spawns you at the spawn point

inspect allows you to inspect zoneControl and localCharacter now
updated docs with the new info

* Update Entity.cpp
2026-06-20 17:10:13 -05:00
David Markowitz
135aec8112 feat: hatchlings (#2008)
tested that hatchlings now function
fixes #759

Update MovementAIComponent.cpp

Update MovementAIComponent.cpp

Update HatchlingPets.cpp
2026-06-20 17:10:00 -05:00
David Markowitz
a1891955e2 fix: leave team when fully logged out (#2007)
* feat: spawner weights

* remove ref

* default weights to 1

* fix: remove team member if they've logged out

tested that if i logout, after 20 seconds the team member is removed.
2026-06-20 02:50:49 -07:00
David Markowitz
7456d6b5c1 feat: spawner weights (#2006)
* feat: spawner weights

* remove ref

* default weights to 1
2026-06-19 19:08:15 -07:00
David Markowitz
308412f46e fix: enemies snapping to the incorrect position if they had a path and trying to use the path if they were aggro'd to an enemy (#2005)
* fix: enemies snapping to the incorrect position if they had a path

tested that ags enemies no longer snap backwards a large amount

* fix: move the home point so we can aggro correctly
2026-06-19 02:12:47 -07:00
David Markowitz
56504d9447 fix: add range checks to npc combat skill behavior (#2003)
* fix: add range checks to npc combat skill behavior

tested that all enemies now cast skills smartly based on range to targets, and do not cast skills if they are out of range.

fixes an issue where the spider queen could attack you outside the normal range

fixes an issue where entering happy flower caused you to need to restart the client

fixes #965

* feedback
2026-06-19 01:27:49 -05:00
David Markowitz
ce9d4e823c feat: enemies now use weights on their attacks (#2004)
* feat: enemies now use weights on their attacks

tested that 8 times out of 10, in close range, spiders did a web attack instead of a melee attack, vs the prior behavior of always following a pattern

fixes #2002

* feedback
2026-06-19 01:27:14 -05:00
David Markowitz
0f17e1de3b feat: enemy npc pathing (#2000)
* feat: enemy npc pathing

they live 🎉
tested that enemies path all around the world should they have a path configured.
tested that the admiral in gf (at the first camp) paths now.
fixes #1546

* feedback
2026-06-17 23:07:36 -07:00
David Markowitz
c898356eba fix: enemies not interrupting QB's when they do damage (#1998)
tested that stromlings in AG now correctly interrupt quickbuilds if the player takes damage
2026-06-16 09:49:56 -05:00
David Markowitz
79bb48d3bc feat: implement a bunch of basic scripts that don't really do anything (#1999)
* feat: implement a bunch of basic scripts that don't really do anything

None of these do anything noticeable or break anything

* fixes
2026-06-16 09:49:30 -05:00
David Markowitz
c0d055e66e fix dragon fire trails (#1997)
tested that the fire breath attack works now (hack fix)
2026-06-15 09:44:24 -07:00
David Markowitz
7937951f7f fix: mech build not showing up (#1996)
tested that the mech build, the fv rock build, and the frakjaw builds (to get to the instancer) all function as intented
2026-06-15 02:20:43 -05:00
David Markowitz
0101933f5c chore: cleanup pointer management for LDF data (#1995)
* change network settings from vector to LwoNameValue

* move settings on Entity to managed memory

* Migrate more members

* chore: remove pointer leakage from raw ldf pointers

* feedback

* fix ci
2026-06-14 20:54:52 -07:00
David Markowitz
90db1ac699 fix: incorrect network variable being set (#1994) 2026-06-13 00:21:53 -07:00
Daniel Seiler
9f8d300340 fix(docker): Unset MAXIMUM_OUTGOING_BANDWIDTH by default (#1548) 2026-06-11 18:38:38 -07:00
David Markowitz
1e9b18fa9d chore: cleanup usage of pointers in the activity component (#1989)
* chore: cleanup usage of pointers in the activity component

* feedback
2026-06-11 09:12:43 -05:00
David Markowitz
e5b8e5c6b7 fix: buggy hitboxes during ag foot race (#1993)
* physics fixes

* check ptr
2026-06-11 09:12:31 -05:00
David Markowitz
707880b5fc fix: fv pipe quick build not spawning as it should (#1991)
tested that the pipe now spawns a ROCK that you can build.  This ROCK you build spawns the PIPE now.
new bug: if you start building the ROCK and stop, the pipe will spawn instead of the previous rock.
2026-06-11 09:12:06 -05:00
David Markowitz
90607bdd5c fix: setting smashable ignoring lnv settings (#1992)
tested that the computer build on crux no longer stays around for +12 seconds
2026-06-11 09:11:52 -05:00