/tg/ Station 13 - Modules - Types

modular_aphelion/modules/dirt/module.md

Dirt overlays

Module ID: DIRT

Description:

Mess that lands on a person shows on their sprite where it was aimed, until it's washed off. It's the first material of a dirt overlay system: the generator, the masks, the component and the drawing are general, and only mess uses them so far.

  • Splats. /datum/component/dirtiness holds a person's or an item's splats (/datum/dirt_splat): one of eight variants of a zone's mess, at level 1 to 4. Levels 1 to 3 are streaks and separate runs with skin between them; level 4 covers the zone in a glaze. A shot adds levels to the zone's newest splat, by its amount (dirt_mess_amount_levels(): 1, 2, or straight to 4), and what's left past level 4 starts another splat, with a variant that zone doesn't have yet. A person carries DIRT_MESS_MAX_SPLATS at most, counting their skin, underclothes, parts and worn items, and past it the oldest goes first. A part's share of a shot and a taur body's pools are splats of their own, so one shot can add several. dirt_mess_land() puts a shot on a person.
  • Aiming. A shot on someone lists a region for each zone that shows on them (dirt_mess_regions()) in place of "On them", which stays only when none does: a zone needs a drawn limb it lands on, a monkey gets none (a kobold does), a taur gets none below the waist, and a portal offers only what it shows: through a wall, the groin and the butt. The pick lands with the shot's amount, and drops no floor decal.
  • Masks are made at run time, never shipped. A surface's splats become one four-direction RGBA icon, registered in the resource cache and drawn as it is (dirt_baked_icon()): each pixel takes its one tone role's colour (film, body, core, fleck or glint) at that role's alpha. Haze, the palest edge, has no role of its own: it's film at half coverage, so it takes the film's colour at half alpha. The colours are a parameter (dirt_mess_role_tints() for the mess), so another material bakes the same way.
  • Where masks come from. Dirt lies on the body, not on the screen. A body's template maps every pixel near it to a limb, an angle round it and a height along it, per direction, built from the limb sprites the body actually draws, so other species, digitigrade legs and fitness sizes get their own. Each zone's mess is a few streaks, globs, drips, drops and glints placed on that map by a seeded random stream, rasterised to roles, then all the splats on a surface merge into one mask, a role to each pixel. Every draw is md5-based and every rounding written out, so the same splats on the same sprites give the same pixels on any server.
  • SSdirt builds templates and masks in the background, a piece at a time. It keeps the newest masks (a body keeps drawing its own after they're dropped), the templates and sprites read most lately (a waiting job holds its own template), the plain human template for the round, and each splat's rasters as one text, its four directions packed and joined, so masks made again with one more splat rasterise only that one. A job reads the sprites of its parts or taur body one direction a piece first. A body whose new masks aren't made yet draws the ones it had, if any, and is drawn again once they are. A job that runtimes or reads a missing sprite is cached as failed, and isn't queued again while that's kept. SSdirt.finish_all() makes everything queued at once, for tests. With DIRT_MASKS_DEFERRED commented out there's no queue: masks are made as they're asked for.
  • Where a shot lands (dirt_mess_surface()). On the outermost of the head, mask, suit and uniform whose body_parts_covered takes in the zone (the face when the mouth is covered), else on the underclothes, else on the skin. The underclothes take it when the pieces reaching into the zone cover at least half of it together. Through a wall portal only the uniform counts, while no suit hides it: the relay draws no suit. Nor does a skirt or dress the relay leaves out (wall_relay_skips_uniform()).
    • The piece that shows the most of the zone takes it: its pixels there, less those the pieces above it draw over.
    • Ties go to the kind that belongs there (underwear, then socks, on the groin, butt and thighs; an undershirt or bra on the chest; an undershirt on the stomach and back), then to the topmost. So a shirt hem over the waistband leaves a groin shot to the underwear, and a corset over it, or a swimsuit over a bra, takes it.
    • Underclothes are judged by their own sprite against the zone's pixels on the plain human template, which SSdirt builds in the background from init and keeps. So a landing never builds a template, and every body is judged on the human shape: on a digitigrade or Muscular body the judgement can be a pixel or two off.
    • Until that template is built, the topmost piece covering the zone by the usual rules takes it: underwear covers the groin and butt, an undershirt or bra the chest, and an undershirt the stomach and back too, and a piece covers what it hides outright.
  • Skin. Each limb that carries mess (the head, the chest and the legs) gets a /datum/bodypart_overlay/dirt_skin: one container image holding the limb's baked image, clipped to the pixels that limb shows. It draws at DIRT_SKIN_LAYER (22.25): under the underclothes (19.1 and 19.101) and the genitals, over body hair (22.5), markings and the limbs. An MD5 of what the limb's baked image draws goes in its render key, so a landing elsewhere on the body leaves that limb's images as they were.
  • Dirty limbs' images. Every key mess adds to a limb's render key (the skin's, the underclothes', a part's share and a taur body's pools) starts with DIRT_PRIVATE_KEY, and a limb drawn under such a key keeps its images on its own mob (limb_icons_for()): one entry per zone, dropped when the zone is drawn clean or loses its limb. tg's limb_icon_cache, shared by every body, serves keys that come back: markings, tails, ears, wings, genitals' states, textures, body hair, augment finishes, blood on limbs and heights. A dirty limb's key almost never comes back, so it only ever added entries.
  • Fading out under clothes. Skin mess fades over the last DIRT_FADE_RINGS pixels before whatever is worn over it, instead of stopping at its edge. The masks are made with the fade, from the sprites drawn in the underclothes and garment slots (dirt_cover_sprites()), so those sprites are part of the masks' key. A change to those slots has SSdirt look again once the slots settle, and makes new masks only if what's worn over the skin changed.
  • Clothes. A worn garment keeps its splats when it comes off. While a person wears it, it draws them through its worn wrapper (COMSIG_ITEM_GET_SEPARATE_WORN_OVERLAYS): one baked image, BLEND_INSET_OVERLAY, so it shows only on the garment's own pixels, fitted or not. The masks are the cloth surface of the wearer's body, its rim a role lighter, so a garment moved to another body draws from that body's. They float, moved back onto the body's tile by what build_worn_icon() moves the wrapper by (dirt_worn_shift(): its centring on the garment's worn dimensions and its worn offsets). Offsets the body puts on the wrapper after that (a species' worn offsets) still move them. On a garment drawn on that tile they aren't offset, and the bulge redraws them over its cloth.
  • Underclothes aren't items. Mess landed on them is held by the person, kept with the piece it's on (its slot and accessory), through hiding it and changing to another and back. While that piece is worn and shows, it's drawn over it at DIRT_UNDERCLOTHES_LAYER through one /datum/bodypart_overlay/dirt_underclothes on the chest, kept to the pixels it shows. The dresser, the visibility verbs and preferences all redraw the underclothes slot, and the person's component then works out again what's drawn. A revealing loincloth the bulge cuts is judged and clipped as it's drawn, cut, and where a bulge look's cloth covers a piece, the piece's mess is drawn again over the cloth, kept to it, as the look draws a garment's own. Washing the person clears it.
  • Parts. Breasts and pecs take a share of a chest shot, the penis, testicles and vulva of a groin shot, the butt of a butt shot. A part is read as it draws now (dirt_part_drawn()), in the layers its kind is drawn from (dirt_part_layers(): the butt's front and back images, every other part's front image): their colour layers' states, and the layer each draws at, custom layering included. A hidden part, one a device or a bulge look draws in place of, and one with nothing in those layers (a taur's penis) take nothing. A plain human chest without breasts takes the plain pecs' shape as an undrawn guide (dirt_pecs_guide()), kept to where the chest shows.
    • In each direction a part draws over or under the surface a shot lands on as its topmost image there does, against the garment's layer, DIRT_UNDERCLOTHES_LAYER or DIRT_SKIN_LAYER. A butt's front image shows only facing north; its back image, the other ways, draws under every surface. Which directions each state shows in is read once and kept (dirt_sprite_facings()), so a draw works out the layers without reading the sprites.
    • A part drawn over that surface in some direction takes its own splat of the same variant, held by the person in part_mess_splats and drawn with the part (genital_overlays.dm): a container just over its front layer, at its layer and offsets, holding the share's baked image, kept to the sprite it draws, in the directions it shows over what it lies on. A new sprite (an erection, a jiggle, a size) has its masks made again, and the limb's render key carries their key, or "pending" while they're made. So the share draws wherever the part does, a portal relay and a bulge look included. Every front image draws over the skin, so on bare skin every exposed part takes its own share.
    • Where a part draws under the surface (pecs under a tank top, a genital set to draw below the underwear or a garment, a butt in profile, the guide) it shapes that surface's mess instead: its share is drawn in the surface's masks, on its own pixels, and the chest's skin mask takes those of its pixels past the body.
    • Either way the surface's mess drops a level in each direction the part shows, two at the genitals, except at the top level, which keeps its glaze (dirt_part_body_level()), and leaves out the rounded parts' pixels (dirt_merge_with_parts()). A part's own mess follows its form: lobes on rounded parts (dirt_lobe_rasterise()), a bead behind the tip of the penis (dirt_part_rasterise()), at levels 1 to 3; a top-level splat draws its parts at level 3. The vulva, too small for a lobe, also takes the ring of skin round it on bare skin.
    • Through a one-part portal a shot lands on the penis alone, its own entry in the list, offered and landed only while it draws its front image. A part removed takes its splats with it, a wash redraws the whole body, and a change to what the body draws of its parts makes the masks they shape again.
  • Taurs. A back shot on a taur lands on the rider's back as on anyone's, the rider drawn on its legless template, and puts broken pools with uneven drips along the taur body's back: a splat of the same variant in part_mess_splats under ORGAN_SLOT_EXTERNAL_TAUR (dirt_taur_takes_pools()), capped and washed with the rest. Big Legs kinds, legs under the rider with no taur back, take none: the shot lands on the rider alone.
    • The taur body is read from its own images (dirt_read_source()): each layer's colour layers' states, on the 64x32 taur canvas. Its pools (dirt_taur_pools()) lie on the pixels it shows with the rider drawn between its adjacent and front layers (dirt_taur_owners()), in a band under the back line the view shows (dirt_taur_back_region(): none facing south, none at the tail end or beside the rider's waist), and keep clear of the same shot on the rider's cloth and skin, so no line runs across both bodies (dirt_mess_splat_rasterise()). Level 1 is a drop, levels 2 and 3 two pools; a top-level splat draws level 3's pools beside the rider's glaze.
    • Each taur layer draws the pools on the pixels it shows: a container just over its images, at their layer and their centred offsets, in that layer's own holder (taur_overlay.dm). The masks' key goes in the taur body's render key, or "pending" while they're made.
    • A taur suit, a garment drawn over the whole taur canvas (dirt_taur_fitted()), takes the shot itself and the taur body takes none: the suit's back splats draw the same pools, inset over its own pixels on the canvas round the body's tile. Its wrapper is drawn 16 px left by worn_x_offset, so the 32x32 mess on it goes 16 px right, onto that tile.
    • A taur body removed takes its pools with it. A taur's penis drawn only behind the body takes nothing.
  • The overlay budget. A mob draws as an error sprite past MAX_ATOM_OVERLAYS. Whatever the number of splats or the roles they draw, skin mess adds one image per limb that carries it, four at most, each holding one baked image, and the underclothes one more. The body's limb images then share one holder per layer (prepare_bodypart_overlays()), so they take two of the mob's overlays. A part's container joins its front layer's holder, and a taur body's pools each layer's. A garment's images go in its worn wrapper and add none.
  • Washing clears it, through the clean signal, as it clears the floor's decals: showers, washing machines, soap. A person's wash reaches the worn items nothing covers.
  • Admin cleanup. Clear Dirt is available under Debug and in a human's VV action menu. Both require R_ADMIN and use the same logged admin verb. The Apply Dirt debugging helper is temporarily commented out.

Pixel parity. The masks match the reference generator's pixel for pixel. dirt_mess_corpus.json lists cases (random streams, noise, templates, mess on bodies with parts under and over them, on parts, taur bodies under a rider and the pools on them), and dirt_mess_manifest.txt holds the MD5 of each case's text as the reference generator writes it. The dirt_mess_manifest unit test builds the same text in game and fails on any case that differs, writing every case's text to data/dirt_mess_masks.txt to compare. A change that's meant to move pixels is made in the reference generator first, and the manifest is regenerated from it.

TG Proc/File Changes:

  • code/modules/mob/living/carbon/carbon_update_icons.dm: /mob/living/carbon/proc/update_body_parts() takes each limb's images from limb_icons_for(), which serves a key without DIRT_PRIVATE_KEY from limb_icon_cache as before; a body holding no private images reads the cache itself, and skips the call while the cache holds the key.

Modular Overrides:

  • code/component.dm: /datum/component/dirtiness.
  • code/skin.dm: /datum/bodypart_overlay/dirt_skin and /datum/bodypart_overlay/dirt_underclothes.
  • code/worn.dm: which garment or underclothes a shot lands on, what's worn over the skin, and /datum/dirt_drawing.
  • code/landing.dm: dirt_mess_land(), dirt_mess_place(), the splat cap, the aim zones and the regions a shot offers.
  • code/parts.dm: /datum/dirt_part_source, which parts a body draws and where, the pecs guide, and the parts' rasterisers.
  • code/genital_overlays.dm: on /datum/bodypart_overlay/mutant/genital, get_overlay() draws a part's share and icon_render_key() carries its masks' key; on /obj/item/organ/genital, on_mob_insert() and on_mob_remove().
  • code/taur.dm: /datum/dirt_taur_source, what a taur canvas shows, the pools' rasterisers, and when a back shot takes them.
  • code/taur_overlay.dm: on /datum/bodypart_overlay/mutant/taur_body, get_overlay() draws the pools, icon_render_key() carries their masks' key, and dirt_read_source() reads the taur body.
  • code/limb_cache.dm: on /mob/living/carbon, private_limb_icons, limb_icons_for() and drawn_limb_icons(); on /obj/item/bodypart, on_removal() drops the limb's private images.
  • code/admin.dm: the Clear Dirt admin verb and the commented-out Apply Dirt debugging helper.
  • code/subsystem.dm: SSdirt, and /mob/living/carbon/human/proc/redraw_dirt().
  • code/masks.dm: splats, mask requests and the mask jobs.
  • code/surface.dm: templates, from the limb sprites a body draws.
  • code/mess.dm, code/features.dm, code/rasterise.dm and code/random.dm: the generator.

Defines:

  • code/__DEFINES/~aphelion_defines/dirt.dm: roles, limbs, zones, thresholds, DIRT_SKIN_LAYER, DIRT_UNDERCLOTHES_LAYER, the underclothes, the fade, the variant, level, amount and splat limits, portal views, part kinds, the taur canvas, DIRT_PRIVATE_KEY, and the switches and flags SSdirt uses.

Included files that are not contained in this module:

  • modular_aphelion/modules/bulge/: dirt uses its bulge_register_icon(), bulge_blank_tile(), bulge_drawn_root(), bulge_drawn_leaf(), /datum/bulge_bounded_cache, a penis's bulge_key, bulge_wanted_cut() and bulge_cut_sprite(). Its look's anatomy memo (bulge_anatomy_record(), bulge_anatomy_unchanged()) records the testicles' dirt_share_key(), so the testicles it draws behind a cover draw their mess as it changes. The look's key carries DIRT_PRIVATE_KEY when the anatomy it draws carries mess, or the cover's mess goes over its cloth (bulge_over_signature()), so those chests keep their images on the wearer.
  • modular_nova/modules/modular_items/lewd_items/code/lewd_organs/: the genitals whose overlays and organs genital_overlays.dm overrides, and COMSIG_HUMAN_GENITAL_UPDATED.
  • modular_nova/modules/customization/modules/surgery/organs/taur_body.dm: the taur body whose overlay taur_overlay.dm overrides.
  • modular_nova/modules/modular_items/lewd_items/code/lewd_arousal/climax.dm: climax() offers the regions and lands the pick.
  • modular_nova/modules/modular_items/lewd_items/code/lewd_machinery/lewd_portals.dm: a wall relay draws the chest alone, with no legs or shoes, and keeps what it draws over the body (the uniform, the underclothes and the mess containers) to the body, through a render target of its own (body_render_target()). Images with no sprite of their own, the mess containers among them, are always copied (append_appearance_copies()) and never taken for an organ's (compare_organ_icon()). It leaves out a skirt or dress (wall_relay_skips_uniform(), by its bulge family), so the body shows under it. A wall-stuck body draws no bulge (portal_hides_bulge()).
  • code/modules/unit_tests/~nova/dirt_mess_drawing.dm, dirt_mess_manifest.dm, dirt_mess_corpus.json and dirt_mess_manifest.txt: the tests, the parity corpus and its manifest.
  • code/modules/unit_tests/_unit_tests.dm
  • tgstation.dme