Skip to content

Rapid Piping Device 2: Atmosia's Revenge#124

Open
rebaserHEAD wants to merge 7 commits into
Triad-Sector:mainfrom
rebaserHEAD:feat/rcd-rpd-overhaul
Open

Rapid Piping Device 2: Atmosia's Revenge#124
rebaserHEAD wants to merge 7 commits into
Triad-Sector:mainfrom
rebaserHEAD:feat/rcd-rpd-overhaul

Conversation

@rebaserHEAD
Copy link
Copy Markdown
Contributor

About the PR

Ports the Rapid Piping Device (RPD) from funky-station and generalizes a few RCD systems along the way:

  • New RPD tool. Sibling to the RCD, not a discriminator. Builds and removes pipes, vents, scrubbers, pumps, mixers, filters, valves, ports, heat exchangers, gas-pipe sensors, air sensors, and air alarms.
  • Pipe-layer placement. Cursor position inside the clicked tile picks Primary / Secondary / Tertiary. Three guide circles render on the cursor tile so the operator can see which layer they're aiming at; client ghost and server commit share RPDLayerMath.PickLayer so they can't disagree.
  • Color picker. Stains spawned pipes/atmos hardware via PipeColorVisuals. The wire payload carries only the palette key — the server re-derives the color so a misbehaving client can't desync the (key, color) pair.
  • R-key flip toggle (RCD feature). Generalized at the RCD layer so any recipe with a MirrorPrototype (gas filter, mixer, future asymmetric airlocks) can be flipped before placement.
  • RCD extensibility events. RCDDeconstructAttemptEvent, RCDObjectSpawnAttemptEvent, RCDObjectSpawnedEvent let sibling systems gate deconstruction, rewrite the spawn prototype, and decorate freshly spawned entities without RCDSystem knowing about them. RPDSystem hangs off these.
  • Atmos hardware opt-in. Pipes, the gas-pipe sensor, the air sensor, and the air alarm now declare RCDDeconstructable { rpd: true, deconstructable: false }. RPDs can chew them; plain RCDs bounce (same behavior as before the PR, since these prototypes had no RCDDeconstructable at all).

Why / Balance

What if Atmos was actually tolerable and dare I say fun? Now I don't have to accidentally ignite myself in the plasma tank trying to get my steel back after a botched pipe placement.

Also because ricky said "IsRCD" is bad, so I did this instead.

Media

image image

Requirements

  • I have read relevant guidelines/documentation to this PR found on our devwiki.
  • I have added media to this PR or it does not require an ingame showcase.
  • I can confirm this PR contains either no AI-generated content, or AI-generated content that meets our guidelines.

How to test

  1. Spawn an RPD and a stack of compressed matter. Load it.
  2. Open the RPD menu (use-in-hand). Pick a recipe and a palette color.
  3. Build a pipe; confirm the color is applied. Hover the cursor near the tile edge before clicking and confirm the pipe lands on the matching layer (cursor center = Primary, NE/E edge = Secondary, SW/W edge = Tertiary; guide dots show which is which).
  4. Select a gas filter or gas mixer. Press R; confirm the placement ghost flips. Build it; confirm the mirrored variant spawns.
  5. Switch to deconstruct. Confirm RPD can remove gas pipes, vents, scrubbers, pumps, mixers, filters, ports, valves, heat exchangers, gas-pipe sensors, air sensors, and air alarms.
  6. Spawn a plain RCD. Confirm it cannot deconstruct any of the atmos targets in step 5 (popup: "not on the whitelist").
  7. Swap between two RPDs (or RPD + RCD) while a flip is active; confirm the flip state syncs to whichever tool you just picked up.

Breaking changes

  • RCDDeconstructableComponent.RpdDeconstructable — new field, defaults false. Existing prototypes are unaffected.
  • RCDPrototype.MirrorPrototype and NoLayers — new fields, optional. Existing recipes are unaffected.
  • RCDComponent.UseMirrorPrototype — new networked field, defaults false.
  • New Content.Shared.RPD.* namespaces, new RPDComponent / RPDSystem. Additive only.
  • Three new ByRef events on RCDSystem: RCDDeconstructAttemptEvent, RCDObjectSpawnAttemptEvent, RCDObjectSpawnedEvent. Additive; existing RCD consumers are unaffected.

Changelog

🆑

  • add: Added the Rapid Piping Device (RPD) — a specialized engineering tool that builds and removes pipes, atmos hardware, air sensors, and air alarms. Supports per-tile layer selection, color staining, and a flip toggle (R) for asymmetric recipes like the gas filter and mixer.

…criminator)

Brings in the funky-station RPD subsystem as the foundation for a coherent
RPD-as-feature port:
  - RCDComponent.IsRpd / UseMirrorPrototype fields
  - RCDDeconstructableComponent.RpdDeconstructable whitelist
  - RCDPrototype.MirrorPrototype (flipped variants) and NoLayers
  - RCDSystem deconstruct guards (RPD only chews RPD-whitelisted atmos
    hardware; never tiles) and mirror-aware ConstructObject spawn
  - RPD entity in tools.yml (parent: BaseItem, size: Huge, back/backStorage
    slots, melee Blunt 12)
  - RPD/Empty/Recharging variants and 27 construction recipes under
    Resources/Prototypes/_Triad/RPD/rpd.yml
  - RCDMenu.xaml piping/atmos/pumps/vents/sensors categories + locale
  - RCDDeconstructable (rpd: true) added to gas pipes
  - RPD seeded in the atmospherics locker fill

Adapted to Triad's no-CachedPrototype pattern (RCDSystem reads ProtoId via
_protoManager.Index directly). HeatPump and GasTemperatureGate omitted
because the upstream atmos prototypes aren't in this fork yet. Funky's
fractional cost: 0.5 rounded to 1; RPD maxCharges set to 45 to preserve
effective build count.

Color picker UI and quadrant-based pipe-layer placement land in the next
two commits. Sources: funky-station PRs Triad-Sector#62, #1244, #1289, #1338, #1345,
#1458, plus Monolith PR #3930 for distribution patterns.
Wraps the existing RCDMenu radial with a 13-color palette strip (waste,
distro, air, mix, plus the basic primary/secondary palette). Selecting a
color sends RCDColorChangeMessage to the server-side RCDSystem, which
stores it on the per-tool RCDComponent.PipeColor. On ConstructObject the
chosen color is applied to the spawned entity via PipeColorVisuals.Color
so newly-built pipes inherit the operator's color choice. "default" leaves
pipes unpainted.

Adds:
  - RPDMenu / RPDMenu.xaml.cs / RPDMenuBoundUserInterface in Content.Client
  - RCDColorChangeMessage + RpdUiKey in Content.Shared/RCD/RCDEvents.cs
  - RCDComponent.PipeColor tuple field
  - OnColorChange handler + appearance application in RCDSystem
  - RPD entity now opens via RpdUiKey + RPDMenuBoundUserInterface (was RcdUiKey)

Source: funky-station PR #1244.
Adds the cursor-quadrant-picks-layer UX from funky-station PR #1338.
Holding an RPD on a layer-capable recipe switches the placement ghost to
AlignRPDAtmosPipeLayers, which:
  - Draws three guide circles (Primary/Secondary/Tertiary) on the cursor
    tile so the operator can see which layer they're about to commit to.
  - Computes the target layer from the cursor's offset inside the tile:
    near-center → Primary, NE quadrant → Secondary, SW quadrant → Tertiary
    (mirrored when the grid is rotated). The math accounts for grid
    rotation and player eye rotation.
  - Updates the placement ghost prototype via
    SharedAtmosPipeLayersSystem.TryGetAlternativePrototype so the preview
    matches what will spawn.
  - Pushes the client eye rotation to the server via RPDEyeRotationEvent
    because eye rotation isn't networked natively. Server reproduces the
    same layer pick when the placement commits.

NoLayers-flagged prototypes (vents, scrubbers, alarms) skip the placement
mode swap entirely and place via the standard AlignRCDConstruction.

Adds:
  - RCDComponent.LastKnownEyeRotation field
  - RPDEyeRotationEvent in Content.Shared/RCD/RCDEvents.cs
  - OnRPDEyeRotationEvent handler in RCDSystem
  - Cursor-quadrant layer math in OnAfterInteract
  - TryGetAlternativePrototype hook in ConstructObject spawn
  - AlignRPDAtmosPipeLayers placement mode (Content.Client/RCD)
  - RCDConstructionGhostSystem now picks per-tool placement mode

Source: funky-station PR #1338.
Adds the client-side flip key binding that activates the
RCDComponent.UseMirrorPrototype state plumbed in earlier. Pressing
EditorFlipObject (default R) while holding an RCD/RPD on a recipe with
a MirrorPrototype defined toggles the variant, rebuilds the placement
ghost with the flipped prototype, and mirrors the state to the server
via RCDConstructionGhostFlipEvent so the next ConstructObject spawns
the right entity. Recipes without a MirrorPrototype no-op.

In practice this is the gas filter / gas mixer flip toggle ports
already use upstream — placement was previously stuck on the
non-mirrored variants because the field existed but had no input
binding. Source: funky-station RPD subsystem (PR #1244).
Drops the `IsRpd: bool` discriminator and replaces the IsRpd branches in
RCDSystem with three by-ref extensibility events (RCDDeconstructAttempt,
RCDObjectSpawnAttempt, RCDObjectSpawned). RPDSystem subscribes to them
plus AfterInteractEvent and contributes RPD-specific behavior without
RCDSystem knowing it exists. Per-entity state (PipeColor, CurrentLayer,
LastKnownEyeRotation) moves onto the new RPDComponent, closing the
multi-user race where AlignRPDAtmosPipeLayers's _currentLayer was static
on the client system.

New shared module Content.Shared/RPD/ holds the component/system plus
RPDLayerMath (single source of truth for cursor-quadrant -> AtmosPipeLayer
math, called from both the client placement preview and the server
spawn), RPDPalette (server-validated color palette), and RPDEvents.
Client-side RPD files move from Content.Client/RCD/ to Content.Client/RPD/.
- ManifoldGas recipe sprite now points at manifold.rsi (its own RSI in
  this fork; Funky/upstream had it under pipe.rsi/pipeManifold).
- RPD entity sprite gets `state: icon` so the picker shows the actual
  tool sprite instead of a blank cell.
- Clothing slots changed from Back/BackStorage (Funky enum) to
  Back/SuitStorage (this fork's SlotFlags); equipped-BACKSTORAGE.png
  renamed to equipped-SUITSTORAGE.png and meta.json updated.
- Replaces all 30 pre-rendered single-frame PNGs under
  Textures/Interface/Radial/RPD/ with code-driven Frame0 extracts from
  the actual entity RSIs. Recipe-spoke icons already used Frame0; the
  top-level category buttons in RCDMenu.xaml have their TextureRect
  children built in code (ApplyRPDCategoryIcons) for the same reason —
  XAML's TexturePath would render a multi-direction sprite sheet. Sprite
  swaps in the atmos hardware now propagate to the picker automatically.
- Adds rpd-component-deconstruct-target-invalid locale string.
Two bugs that together caused every pipe placement to land on the Primary
layer regardless of cursor position:

1. RPDSystem.OnAfterInteract bailed at `if (args.Handled)` because
   RCDSystem's handler ran first and set args.Handled = true after
   queueing the DoAfter. CurrentLayer stayed at its default (Primary),
   so RCDObjectSpawnAttemptEvent always picked the Primary variant.
   Subscribing with `before: typeof(RCDSystem)` lets us commit
   CurrentLayer onto the RPDComponent before RCDSystem captures the
   click; the DoAfter then completes against the right layer.

2. Server-side mouseDiff was `location.Position - tileCenter - (0.5, 0.5)`,
   biasing the cursor offset toward the SW quadrant by half a tile.
   Client-side AlignRPDAtmosPipeLayers.AlignPlacementMode used the
   correct `raw - tileCenter` formula. Drop the spurious offset so the
   server math matches the ghost.
@Triad-Sector Triad-Sector changed the title Feat/rcd rpd overhaul Feat/Rcd Rpd Overhaul May 24, 2026
@github-actions
Copy link
Copy Markdown

RSI Diff Bot; head commit 47fb74d merging into 7991bb3
This PR makes changes to 1 or more RSIs. Here is a summary of all changes:

Resources/Textures/Objects/Tools/rpd.rsi

State Old New Status
equipped-BACKPACK Added
equipped-SUITSTORAGE Added
icon Added
inhand-left Added
inhand-right Added

Resources/Textures/Objects/Tools/rpd_storage_64x.rsi

State Old New Status
storage Added

@rebaserHEAD rebaserHEAD changed the title Feat/Rcd Rpd Overhaul Rapid Piping Device 2: Atmosia's Revenge May 24, 2026
@rebaserHEAD
Copy link
Copy Markdown
Contributor Author

Oh I guess I should add it to vending machines or something and give it an actual price. Whoops! Thanks arbitrage checker!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant