OPX//77
opx_infinityappearance

appearance

Last updated on

The character's face, the clothes it wears, the fitting room, and the looks other players are drawn from.

At join the module decides which body the world loads with, puts the stored face and clothes on, and sends the announcement that lets the player into the world. It opens the game's own mirror to edit a face, runs the fitting room where a player picks clothes slot by slot, and hands every player's look to the others so they are drawn at all. Players open their panel with /opx.appearance; the fitting room opens at a clothing store, a shop, from staff, or once after character creation. Other modules use the contract to dress, preview or bill clothing.

Sideboth
Requirescharacter (hard)
Optionalmenu, panel (they draw the panel and the fitting room)
Contractappearance v1 — server, client
Configconfig/appearance.lua (shared)
Dataopx77_character_clothing (owned); the appearance column of opx77_characters (row owned by character)

Fatal module

Declared fatal = true. Its client sends open77:session:gameplayReady, the only thing that clears the platform's join hold. With it stopped, no player gets into the world.

Joining the world

The client sends open77:session:gameplayReady as soon as the character's world entry starts, even when the platform has not reset the body yet: the platform resets and revives the body only after that announcement.

  • The face and clothes go on once the platform's reset has brought the body alive.
  • Before the reset, the entry waits up to 60 s (counted from the announcement), then enters on no face with a warning.
  • After the reset, a body that is still not alive after 5 s settles on no face. A failed reset (open77:playerReset:failed) ends the wait at once.
  • A face given up on a dead body goes on when the body is alive again (after a revive).
  • A character switch in the world (SWITCH = 'relog') and a restart of opx_infinity mid-session settle like a join: the new character is dressed and its look published.

Commands

CommandAccessArgumentsEffect
/opx.appearanceeveryone—Toggles the caller's appearance panel. Cooldown 2 s.

There is no fitting-room command (/opx.appearance.wardrobe is gone) and no panel key. Staff open the room for a player with /opx.admin.player.wardrobe.

What the panel offers

  • Looks: put the stored face back on (Wear it), open the mirror on the face (Edit face, ripperdoc mode) or the hair (Hair only, hairdresser mode).
  • Body: shows the body type. It cannot be changed: it belongs to the character.
  • Outfits: opens the fitting room. A room opened this way has no server grant, so its save is refused with clothing.noFittingRoom unless another door granted one in the last 10 minutes.

Garment names and pictures

The fitting room shows each piece with its real name and a picture on the player's body family. The data is generated; nothing here is a setting.

PieceWhat it is
modules/appearance/data/garments-1.lua … garments-5.luaGenerated: ["Items.<record>"] = { NAME, FEMALE, MALE } — the item's name and the picture for each body family (either may be missing). Do not edit by hand.
modules/appearance/client/garments.luaLoaded before the parts; reads them. Exposes M.Garments.Describe (client, inside the module).
web/images/clothing/*.webpThe pictures (160 px WebP), shipped to every client, built from ui/public/images/clothing/ by npm run build. Named <first 10 hex digits of sha256(slug)>.webp (e.g. 0ff29bbf8d.webp), so a path is 35 characters.
web/images/clothing/ATTRIBUTION.mdSources: pictures © CD PROJEKT RED (in-game renders from the Cyberpunk Wiki, not covered by this repository's licence); names from the Cyberpunk Wiki, CC BY-SA.
tools/generate-garments.mjsRebuilds pictures, attribution and the garments-*.lua parts from a staged collection: npm i --no-save sharp, then node tools/generate-garments.mjs <collection-dir>. Keeps only records the server's items table knows (with a wardrobe slot). Names each picture by a hash of its slug; stops on a name collision or a path that is too long. If the number of parts changes, update the garments-*.lua lines in open77.lua.
local name, picture = M.Garments.Describe(record, family)
  • record: string — an Items.* name
  • family: 'female' | 'male'
  • Returns the name and picture (each may be nil): the family's own picture, else the other body's, else nil. A malformed row is reported once with OPX.Note and treated as missing. Not on the contract.

Shipped paths must be 47 characters or less

A client installs a server's resources deep under the game folder. A Steam install in Program Files leaves 59 characters of Windows MAX_PATH for a path inside the resource, and one file past that makes the whole resource fail on that client. Every shipped path is held to 47 characters (59 less a 12-character margin); tests/run.lua checks every file under web/. That is why clothing pictures have hashed names. Keep any new file under web/ short.

  • Each box in the room is drawn through the panel tiles spec with labels (the garment name, or one made from the record) and images (the picture, or '' for a monogram).
  • Category names are the wardrobe.slot.* locale keys: Head, Face, Top (InnerChest), Jacket (OuterChest), Pants (Legs), Shoes (Feet), Outfit; in French Tête, Visage, Haut, Veste, Pantalon, Chaussures, Tenue.

Configuration

config/appearance.lua sets OPX.Config.MODULES.appearance. Shared script. Nothing else in opx_infinity sends the join announcement, so enabled = false only when another resource does.

KeyDefaultEffect
enabledtrueSwitches the module on or off.
GAME_BUILDS{ ['2.31'] = true }Builds a stored face may be read back on. A face from another build is refused, not applied.
MAX_JSON_BYTES49152Largest encoded face stored; under 1024 falls back to 49152. The clothing limit is fixed at 16384.
PRESENT_BODIEStrueHand every player's look to everyone else. false only when another resource does it, otherwise nobody sees anybody.
COMMIT_MS20000How long the client waits for the server to answer a face or clothing save.
SAVE_COOLDOWN_MS2000Client-side wait between two saves. The server's cooldown is fixed at 2 s.
CLOTHING{ PERSIST = true, SAVE_DEBOUNCE_MS = 2000 }PERSIST: put stored clothing on and save changes (false leaves clothing to something else). SAVE_DEBOUNCE_MS: how long a change must hold before it is saved.
RESTORE_RETRIES3Re-tries of a join-time face restore the mirror aborted.
FAMILY_RETRIES2World reloads onto the character's body family before the player keeps the body they are on.
BODY_RELOAD_SETTLE_MS10000After a body reload, how long a face waits for the respawn to finish.
BODY_RELOAD_TIMEOUT_MS30000After this, a reload is ended if the puppet is attached, alive and in the world. Not a positive number: recovery off.
CREATION_WAIT_MS15000How long a character with no face waits for something to answer needsCreation before entering on the default face.
BOOTSTRAP{ CHOICE_WAIT_MS = 15000, CHOICE_CEILING_MS = 45000, DEFAULT_FAMILY = 'female' }Wait for a character choice while nothing is on screen; the whole wait, screen or not (0 = off); the body loaded when no character is known in time.
WARDROBEsee belowThe fitting room.
config/appearance.lua — WARDROBE
WARDROBE = {
  OFFER_POLICY = 'first',
  CREATION_WAIT_MS = 60000,
  CAMERA_OFFSET = { X = 0.0, Y = 2.6, Z = 1.1 },
  CAMERA_FOV = 95,
},
  • OFFER_POLICY: which world entries get the room. 'first': only a character just built in the creator. 'always': every entry. 'never': none. Unknown: warning, reads 'first'.
  • CREATION_WAIT_MS: how long the join retries opening the owed room before moving on.
  • CAMERA_OFFSET: camera position in metres in the puppet's space (X across, Y in front, Z up).
  • CAMERA_FOV: used only when the camera cannot be moved to CAMERA_OFFSET; nil leaves the player's own.

Join order for a new character: name → fitting room → spawn menu. A player dressing is inside the spawn module's HOLD_MAX_SECONDS; keep that longer than a player needs to dress.

Refusal codes

Sent on opx:net:appearance:refused. Face refusals are also shown to the player through the locale catalogue; clothing refusals are only published and logged.

CodeMeaning
error.badRequestThe payload was not a table.
error.tooFastInside the 2 s cooldown. The client retries a clothing save.
error.notLoggedInNo character loaded on the connection.
appearance.stale / clothing.staleThe save was captured for another character.
appearance.invalid / clothing.invalidThe face or record failed validation.
appearance.tooLarge / clothing.tooLargeOver the size limit.
clothing.noFittingRoomNo grant covers this save: no open grant in the last 10 minutes, no matching look grant, no priced room.
clothing.unpaidA priced room's charge was refused (or raised). The save is not written and the stored look goes back on.
error.unavailableThe stored clothing could not be read at login; saves are off this session.

Validation details on appearance.invalid: unsupported_schema, unsupported_game_build, invalid_catalog_digest, invalid_gender, invalid_option_count, option_out_of_range, duplicate_option, sparse_options. On clothing.invalid: unknown_field, invalid_slot, invalid_item, invalid_active, invalid_outfit, invalid_outfit_slot.

On this page