/tg/ Station 13 - Modules - Types

modular_aphelion/modules/worn_emissives/readme.md

Worn emissive grouping

Module ID: WORN_EMISSIVES

Description:

Moves floating emissive/blocker branches of ordinary carbon equipment into a sibling emissive group after slot offsets, height filters and missing-limb masks have been applied. The offsets remain inside that group, so resting uses normal inherited mob transforms. Existing visible appearance trees and internal mask ordering are preserved; the finished groups are stored in overlays_standing for removal and multiz rebuilding.

The same final hook prepares direct bodypart glow/blocker masks after centering and height adjustments. Standard masks move into an iconless, unshifted emissive parent, with their original offsets, filters, alpha and inherited direction inside. This supports different accessory canvas sizes without changing artwork. The raw shared limb-cache appearances are copied, never modified.

Only the mob's top-level overlays count toward MAX_ATOM_OVERLAYS, and every limb, marking and accessory colour slot brings a sprite plus a mask. So masks sharing a plane and layer share one parent, and plain floating sprites sharing a layer (limbs, markings, hair, matrixed colour slots, inner-ear and MOD texture holders) share one iconless holder; the hair slot uses the same pass. Worn masks share a group per layer within a slot, which mostly matters for the two held items. An entry that cannot join keeps its place, and later entries at its layer start afresh, so draw order is unchanged. overlays2text() names the first sprite inside iconless wrappers, so an overflow printout shows what they hold.

This covers ordinary worn slots and held overlays, listed in GLOB.worn_overlay_layers (which the custom sprite editors' dressed previews also read), not the separate psionic held-item vis_contents/render-source path. Absolute-layer effects and non-emissive planes are not moved. It does not create blockers for items that intentionally do not supply one.

Underwear, bras, undershirts and socks also use this pass through BODY_LAYER. The human path passes the wearer to make_appearance(), which honors em_block, creating a blocker from the final garment icon after female/digitigrade shaping or GAGS generation. The final pass incorporates height filters and assigns the wearer's emissive plane. Garment layers and transparent cutouts are preserved; taur parts drawn in front of the garment remain in front of its blocker too. Callers without a wearer, including mannequins, retain their existing behavior.

The bodypart path handles standard sprite mask leaves only. Inset husk blood, other blend modes, nonstandard appearance flags, nested trees, maptext and render-source/target effects keep their existing behavior. Icon datums generated by headwear masking and height-filtered masks are supported.

Call frequency and caching:

The finished per-slot appearance is cached in overlays_standing, not rebuilt every frame. Ordinary human resting changes the mob transform and reuses these appearances. Plane-offset changes rebuild the cached appearances through update_z_overlays(), without calling this helper.

The single hook is in apply_overlay(), when a slot has a cached appearance. Most equipment updates remove the old slot, build a new worn appearance, apply wearer-specific offsets/filters, and then call apply_overlay(). That new appearance needs preparation even if the item is unchanged. Other slot types exit at the layer guard; leaves and already-separated emissive roots skip recursion.

Bodypart preparation scans the final slot list once and returns a new list. A changed limb can cause the complete bodypart slot to be assembled again, so masks from unchanged limbs are prepared too. Prepared roots cannot join again and single sprites are not wrapped, so repeat application adds no wrappers. Each newly prepared mask requires one copied mutable appearance; each plane-and-layer parent and each holder of two or more sprites requires one more.

The cache stores the result, but there is no separate "already prepared" marker or memoization table. Explicitly preparing the same cached tree again returns it unchanged, although nested visible branches are still traversed. An identity-only memo would usually miss the freshly built equipment appearances; an item-only key would miss changing worn overlays, offsets, filters and plane offsets. Avoiding redundant equipment rebuilds at their callers would save both construction and preparation work, but needs call-frequency evidence rather than another cache in this module.

TG Proc/File Changes:

  • code/modules/mob/living/carbon/carbon_update_icons.dm: one call in apply_overlay() before add_overlay().
  • code/modules/mob/living/carbon/human/human_update_icons.dm: update_body_parts() measures nested sprites too (they read back as appearances, which have no procs), so accessories inside holders still size the body for HUDs, immersion and similar effects; the four active garment builders in get_underwear_overlays() pass the wearer to make_appearance().
  • code/controllers/subsystem/overlays.dm: overlays2text() names the first sprite inside iconless wrappers.
  • code/datums/sprite_accessories/clothing.dm: make_appearance() adds a blocker when em_block is enabled and a wearer is supplied, after the existing icon generation/cache path.

Modular Overrides:

  • None.

Defines:

  • WORN_EMISSIVE_UNDERLAYS / WORN_EMISSIVE_OVERLAYS: the two traversal selectors, in draw order.
  • WORN_EMISSIVE_VISIBLE_RESULT: the shared out-parameter slot for the visible tree.

These are file-local helpers, undefined at the end of worn_emissives.dm.

Included files that are not contained in this module:

  • None.