The Faces module
The faces module builds character portraits for comms screens, hails, the crew roster and the avatar editor.
A face is a stack of layers. Each layer names one cell of a race's sprite atlas, and the cells composite on top of each other at the same box - a body, then clothes, then eyes, a mouth, hair, a hat. Every cell is 512x512 and already drawn in position, so stacking is all there is to it.
The atlases are registered in data/graphics/allFaceFiles.txt, which maps a short alias
to an image under data/graphics. A mod can add its own sheets there.
The face string syntax
Layers are separated by ;. The first layer is the lowest.
<texture-tag> <color-tint> <col> <row> [<x-offset>] [<y-offset>];
<texture-tag>- the alias fromallFaceFiles.txt(ter,tor,ska,kra,zim,arv, or a mod's own)<color-tint>-#rrggbbor#rgb.#fffmeans no tint.<col>/<row>- which cell<x-offset>/<y-offset>- optional nudge of the source rectangle. The stock sheets do not need them; they are drawn already in place.
set_face(officer, "ter #fff 0 0;ter #fff 16 6;ter #fff 1 1;ter #fff 11 2;")
The sheets
Each stock race has its own grid, and each row of a sheet is one kind of layer.
| Alias | Race | Grid | Rows |
|---|---|---|---|
ter |
Terran | 24 x 7 | body, eyes, mouth, hair, facial hair, headwear, clothes |
tor |
Torgoth | 11 x 5 | body, nose, hat, eyes, clothes |
ska |
Skaraan | 7 x 5 | body, eyes, mouth, hair/headwear, clothes |
kra |
Kralien | 6 x 6 | body, rank pips, eyewear/hat, eyes, mouth, clothes |
zim |
Ximni | 8 x 5 | body, hair/horns, eyes, mask/mouth, clothes |
arv |
Arvonian | 8 x 4 | body, headdress, hair, clothes |
Some rows hold more than one feature side by side: the Terran headwear row is hats (columns 0-5), eyewear (6-9) and headsets (10-11), and Ximni's row 3 is masks then mouths. They are separate features, so a character can wear a cap and a headset.
Two races are not built like the others. Torgoth has no mouth row, and Arvonian's
eight bodies are complete busts with the face painted in - it has no eye or mouth layer
at all, so it has no expressions and no skin tint. Code that walks these tables has to
tolerate a missing layer rather than assume all six races match;
face_layer_count(race, layer) answers 0 for exactly this reason.
_tools/face_contact_sheet.py renders every cell of a sheet composited over its body and
labeled row.col. That is the only practical way to check a layer table against the art,
because a mouth cell on its own is a few hundred pixels of lip in an empty square.
Making a face
face_build("terran", body=0, eyes=1, mouth=11, hair=3, clothes=16, skin=9)
random_face("terran", gender="female", civilian=False)
random_terran_male() # and random_skaraan / _torgoth / _kralien / _ximni / _arvonian
face_build takes the layers by name; a layer left out is not drawn, which is how hats,
facial hair and accessories stay optional. An index larger than a race has wraps rather
than raising.
build_face(race, values, enables) is the editor-facing form: indices in
FACE_FEATURES[race] order. parse_face is its exact inverse, so an editor can open on a
face that already exists without changing it.
Expressions and speech
The redrawn sheets carry expression art. An expression is a named (eyes, mouth) pair, and swapping it leaves the body, hair, clothes and every tint alone.
angry = face_expression(get_face(officer), "angry")
face_expressions("terran") # neutral happy angry worried suspicious shocked asleep
A race missing a cell for an expression falls back to neutral; Arvonian has none and returns the face unchanged.
For speech, face_talk_frame(face, elapsed) and face_blink_frame(face, elapsed) are
pure functions of the time - no ticker, no registry, nothing to reset between
missions. Whatever already has a clock drives them:
--- talking
await delay_sim(0.15)
the_face.value = face_talk_frame(base_face, talk_elapsed)
talk_elapsed += 0.15
jump talking if talk_elapsed < line_seconds
the_face.value = base_face
Assigning is enough: Face.update() marks itself dirty, so the engine re-sends the widget
without a page rebuild. Only Terran blinks - the alien sheets draw eye colors rather
than eye expressions, so nobody else has a closed-eye cell.
Tints, and what they can and cannot do
The engine's only blend mode for a face layer is MULTIPLY, so a tint can only darken. That is the single most important thing to know about face color. A palette written as "the color I want to see" is therefore half unusable: measured against the current art, only 3 of 31 skin tones were absolutely reachable on Kralien and 8 on Skaraan.
So the shipped palettes keep each tone's hue and saturation and re-map its lightness into the range multiply can reach. Every slider position is distinct and the ramp is monotonic, but it is a ramp down from the skin as painted, not an absolute color picker. Absolute control needs a desaturated gray body cell in the art, which is an outstanding request to the artist.
face_tint("terran", "skin", 9) # a palette index -> a tint hex
face_tone_names("terran", "skin") # names, for a control
face_tone_index("terran", "skin", "emerald") # by name, rather than counting
face_tone_indices("terran", "skin", natural_only=True)
Non-human tones need different maths, and they have it. Multiplying a blue tint
through warm skin does not give blue - it just darkens toward the base and comes out
brown (ice-blue measured #67553f). So those tones divide the base skin out of the
tint first, which cancels its warmth and lets the hue survive. Reds land exactly, since
red is the base's strongest channel.
EXOTIC_SKIN lists them - emerald, jade, ice-blue, cobalt, rust, crimson,
ashen, amber - and they are excluded from the natural pool, so a random Terran never
rolls one. They read clearly on all five tinted races, Torgoth included.
Do not predict how dark one of these lands from the median skin value: the median says a
blue must come out muddy and the rendered face disagrees, because its highlights sit far
above the median and carry the hue. Render it (_tools/face_render.py).
The palettes live in sbs_utils/face_tints.py, which is generated by
_tools/face_tint_calibrate.py from the art itself - do not hand-edit it, rerun the tool.
face_tone_indices(..., natural_only=True) exists because the palette deliberately
carries green, blue and violet skins and fuchsia hair, for aliens and for missions that
ask. A random Terran must not roll one.
The skin tint covers the eye and mouth layers too, not just the body. Those cells are not clean cutouts - an eye cell is 17-31% skin and a mouth cell 19-40%: brow ridge, eyelid, the surround of the lips. Tint the body alone and that skin keeps the color it was painted, so a re-toned face wears a pale mask around the eyes and a pale muzzle, glaring on a dark or non-human tone. The Torgoth nose is skin as well. Equipment sharing those rows is excluded: a Ximni breathing mask and a Torgoth eye-plate are hardware, and must not change color when somebody changes complexion.
Hair and facial hair take the hair palette. Hats, clothes and accessories are never tinted. Arvonian has no skin palette at all - its eight bodies are painted busts.
Resting faces vs expressions
Some cells are states rather than identities: a closed eye, an eye-roll, a mouth caught
mid-word. They are what expressions and the talking animation are made of, and the editor
offers every one of them - but a randomizer must not hand one to somebody as their
standing portrait, or you get an officer who is permanently asleep or forever saying
"oh". FACE_LAYERS[race]["resting"] names the indices a random face may use; a layer
with no entry has no unsuitable cells.
This narrows the randomizer only. face_layer_count, face_cell, face_expression
and the editor all still reach the full set.
No layer offsets
A face string may carry an ox/oy nudge, and the pre-redraw builder used them heavily
(6 -2 for hair, 14 -2 for a hat, 12 4 for facial hair, 20 4 for an accessory).
The current builders emit none. Every cell on the new sheets is drawn already in
position, so a nudge would only move it off. The v1 parser still recognizes the old
offsets, because it has to read strings that were written with them.
Faces authored before the 2026-09 redraw
The six stock atlases were replaced in place - same aliases, same filenames, different grids and different meanings per cell. An old face string is not merely wrong-looking: it names cells that now hold something else entirely.
It is also indistinguishable from a current one. ter #fff 3 0 used to be a feminine
face and is now hair #3 - the same six tokens. Nothing can tell them apart at runtime, so
migration is never automatic. A caller has to know that what it is holding predates
the change.
face_migrate(old_face_string) # read against the old layout, rebuilt against the new
face_is_valid(face_string) # every layer names a cell that exists
face_migrate is deterministic and returns a valid face of the right race. It is not
idempotent - a migrated face is a perfectly good face string, so running it twice restyles
the person again.
Where the new art dropped a feature its index is folded into one that survives (a Torgoth's mouth into its nose; an Arvonian's eyes and mouth into the choice of bust), so two old faces that looked different still look different.
face_is_valid is a partial check and worth knowing how partial: it catches most
stale alien faces, whose old rows 5-7 no longer exist, and almost no stale Terran one,
because the new 24 x 7 grid still contains nearly every coordinate the old 15 x 8 could
name. A True answer means "not obviously stale", never "current".
Two tools do the repair:
_tools/face_migrate_sources.py <path> --writerewrites literal face strings in source files, printing every change, and keeps a ledger so a second pass cannot silently restyle everybody.- Faces saved in a player's own
client_string_set.txtcarry a format stamp (crew_self_pack/crew_self_unpack); an unstamped record is migrated once on read and re-saved.
Character faces
Characters holds a few ready-made face strings, useful as worked examples.
API: faces module
Characters
A set of predefined faces
arvonian(face_id, eye_id, mouth_id, crown_id, collar_id, hair_id=None)
Create an arvonian face.
The redrawn Arvonian sheet is eight COMPLETE busts - face, eyes, mouth and skin
pattern all painted in - so there is no eye or mouth layer to set. eye_id and
mouth_id are folded into the choice of bust, which is what now carries identity,
and face_id selects it directly when it is the only thing given.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
face_id
|
int
|
bust, 0-7 |
required |
eye_id
|
int | None
|
folded into the bust |
required |
mouth_id
|
int | None
|
folded into the bust |
required |
crown_id
|
int | None
|
hat / headdress, 0-5, or None |
required |
collar_id
|
int | None
|
clothes, 0-7, or None |
required |
hair_id
|
int | None
|
hair, 0-2, or None |
None
|
Returns:
| Type | Description |
|---|---|
str
|
A Face string |
build_face(race, values, enables=None)
Build a face string from per-feature indices in FACE_FEATURES order.
values[i] is the chosen index for feature i; enables[i] False switches an optional
feature off. Returns '' for a race with no recipe.
clear_face(ship_id)
Removes a face string for a specified ID.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ship_id
|
Agent | int
|
The id of the ship/object |
required |
face_blink_frame(face_string, elapsed, period=4.0, close_for=0.14)
The face as it looks elapsed seconds into an idle blink cycle.
Pure, for the same reason as face_talk_frame. period is how often a blink starts
and close_for how long the eyes stay shut - short, because a blink that lingers
reads as falling asleep rather than as being alive.
Only races with a closed-eye cell blink - which is Terran alone; everyone else is returned untouched.
face_build(race, skin=None, hairtone=None, **layers)
Assemble a face string from per-layer indices.
Layers are named by the race's own cells table in FACE_LAYERS - body, eyes, mouth,
hair, clothes and whatever else it has. (Spelled that way round on purpose: written
as a subscript chain it reads to mkdocs-autorefs as a markdown reference link and
fails the strict docs build.) A layer that is absent or None is simply not drawn, which
is how optional parts (a hat, a headset, facial hair) stay optional. body defaults to
0, because a face with no body is an invisible face.
An index the race does not have that many of WRAPS rather than raising: see face_cell.
face_build("terran", body=0, eyes=1, mouth=11, hair=3, clothes=16, skin=9)
face_cell(race, layer, index)
(col, row) for one choice, wrapping out-of-range indices.
Wrapping rather than raising is deliberate and load-bearing: every pre-redraw caller passes indices sized for the OLD sheets, and every count changed. A Skaraan hat index of 4 against a five-wide row still has to produce a Skaraan.
face_expression(face_string, expression)
Return face_string wearing a different expression.
Only the eye and mouth layers change - body, hair, clothes, accessories and every tint are left exactly as they were, so a character keeps their identity while their face moves. An unknown expression, or a race that has none, returns the input unchanged rather than a stranger.
angry = face_expression(get_face(officer), "angry")
face_expressions(race)
The expression names a race can actually wear. [] for Arvonian.
face_eyes(face_string, index)
Return face_string with only its eye cell replaced.
face_gender_index(gender)
A gender word as the terran builder's face index, or None for "any".
0 male, 1 female, 2 fluid - the numbering :func:terran documents. Unknown words
answer None rather than guessing, so a mod's own vocabulary degrades to a random face
instead of to the wrong one.
face_in_uniform(face_string)
True when the face is dressed as crew rather than as a civilian.
"Uniform" used to be a feature of its own, a (hat, shirt) pair picked from a table. The redrawn Terran sheet has 24 garments in one row - dress uniforms, flight jackets, a lab coat, a leather jacket, a suit and tie - so being in uniform is now a property of WHICH garment, not a separate switch. The crew system needs the question answered either way: a bridge officer out of uniform is not a variation, it is a stranger at the helm.
The five alien sheets draw only formal wear, so for them any clothing counts. A face wearing nothing at all is not in uniform.
face_is_valid(face_string)
True when every layer names a cell that exists on the sheet it points at.
A PARTIAL check, and worth knowing how partial. It catches most stale alien faces, whose old rows 5-7 no longer exist on sheets that are now five or six rows tall. It catches almost no stale Terran one, because the new 24x7 grid still contains nearly every coordinate the old 15x8 could name. So a True answer means "not obviously stale", never "current".
face_layer_cells(race, layer)
[(col, row)] for every choice of one layer, or [] if the race has no such layer.
face_layer_count(race, layer)
How many choices a layer offers.
0 means the race does not have it at all - Torgoth has no mouth, Arvonian has neither eyes nor mouth - which callers must treat as "skip", never as an error.
face_migrate(face_string)
Translate a face string authored BEFORE the 2026-09 sheet redraw.
The old string is read against the old cell layout, then rebuilt through the current builders - so the fold rules for features the new art dropped (a Torgoth's mouth, an Arvonian's eyes) live in exactly one place, the builders themselves.
Not every old face has an exact successor and none of them can: the art is different. What this guarantees is that a migrated face is a VALID face of the right race that keeps as much of the original choice as survives. Faces that differed before still differ afterwards.
Returns the input unchanged for a mod face or anything unreadable, so it is safe to run over a mixed pile of strings.
face_mod_reset()
Drop every mod registration. Called by reset_mission_state().
face_mod_size()
Reset-ledger probe: how much mod registration is currently held.
face_mouth(face_string, index)
Return face_string with only its mouth cell replaced - the primitive a talking
animation drives. Races with no mouth row return the input unchanged.
face_overlay(face_string, *overlays)
Stack overlay layers onto an existing face string.
This is the whole point of keeping implants as separate cells: any face can be assimilated at runtime, including one that was never drawn with implants. Layers composite lowest-first, so overlays go last.
face_overlay(random_face("terran"), "tng6 #fff 2 1", "tng6 #fff 5 3")
Empty/None overlays are skipped so a caller can pass an optional one straight in.
face_race_mapped(race)
Re-point a race name at the face race its portraits should come from. ART ONLY.
The third of the mod re-skin maps (RACE_ART and ART_KEYS are the other two, in
procedural/ship_data.py). Returns race unchanged unless a mission or profile set
RACE_FACES, so stock behavior is untouched.
WHY FACES NEED THEIR OWN MAP AND CANNOT REUSE RACE_ART: face races are SPECIES,
ship-data sides are FACTIONS, and they do not spell the same. A mod's Federation ships
are crewed by human, not by federation; a faction can field hulls and register no
portraits at all (Cosmos-TNG-Mod has Breen ships and no Breen faces). Feeding a faction
name to random_face() therefore matches nothing and falls back to terran - the exact
"NPC comms faces are still stock" symptom this exists to fix.
Applied INSIDE random_face() rather than at its call sites, because a mission has many
(LM alone has four for NPCs, one of them in a fleet spawner) and a missed one looks
identical to the bug. A name with no entry passes through untouched, so player faces
already named by species (human) are unaffected.
face_race_of(face_string)
The race a face string belongs to, or None for a mod atlas or an unreadable one.
face_random_registered(race, role=None)
A random registered face for a race, or None if the race was never registered.
A role with no faces falls back to the race pool rather than returning nothing - asking for a Breen science officer should still get a Breen.
face_register_race(race, faces, roles=None, in_random=True)
Declare ready-made face strings for a race so random_face(race) can use them.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
race
|
race name as scripts will ask for it, case-insensitive. |
required | |
faces
|
list of face strings - the pool used when no role is asked for. |
required | |
roles
|
optional {role: [face strings]} for role-filtered picks, e.g. random_face("klingon", "command"). |
None
|
|
in_random
|
whether a bare random_face()/random_face("random") may return this race. A mod that only wants its faces when asked for by name passes False. |
True
|
face_register_sheet(alias, cols=8, rows=8)
Declare a mod atlas alias and its cell grid.
alias is the short name in a face string ("tng1") and must match the name the
engine reads from data/graphics/allFaceFiles.txt - the library cannot write that
file, so registering here does NOT make the sheet loadable, it only teaches the
library and the browser compositor how to read cells out of it.
face_registered_sheets()
{alias: {"cols","rows"}} - what the mock compositor and AMD renderer need.
face_resolve(spec)
Resolve a declarative face spec to a face string. A KEYWORD (terran / male / female / fluid) -> a fresh random face of that kind; a literal face string -> itself unchanged; None/empty -> a random terran. Lets AMD/data author a face as a simple word instead of a raw face string. (Promoted from Open Universe's lifeform_face.)
face_sheet_grid(alias)
(cols, rows) for an alias, or None if nothing knows that sheet.
A mod registration wins over the stock table, so a mod may re-point a stock alias. The six stock grids are all different since the 2026-09 redraw - see FACE_SHEETS.
face_talk_frame(face_string, elapsed, rate=7.0)
The face as it looks elapsed seconds into speaking.
A PURE function of the time, deliberately: no ticker, no registry, no per-widget
state to reset between missions. Whatever already has a clock - a GUI sub-task loop,
an on change against a counter - calls this and assigns the result:
--- talking
await delay_sim(0.15)
the_face.value = face_talk_frame(base_face, talk_elapsed)
talk_elapsed += 0.15
jump talking if talk_elapsed < line_seconds
the_face.value = base_face
Assigning is enough because Face.update() marks itself visual-dirty, so the engine re-sends it without a page rebuild.
A race with no viseme mouths (Torgoth has no mouth row; Arvonian's busts are drawn whole) returns the input unchanged rather than flapping something that is not a mouth.
face_tint(race, kind, tone)
A tint hex (no leading '#') for a tone on a race, or 'fff' for no tint.
tone is an index into that race's palette, a raw hex string (passed straight
through, so a caller with its own color is never second-guessed), or None.
face_tone_index(race, kind, name, default=0)
The palette index of a tone BY NAME, so a mission can ask for "emerald"
instead of counting. Unknown names answer default rather than raising - a face
built from tone 0 is still a face, and a mission should not die over a color.
face_tone_indices(race, kind, natural_only=False)
Palette indices for a race, optionally only the ones that read as natural.
Returns [] when the race has no palette of that kind - Arvonian has no skin ramp, and only three races have hair - so a caller can pass the result straight to a random pick and get None rather than a wrong color.
face_tone_names(race, kind)
The tone names for a race's palette, for an editor's dropdown. [] if it has none - Arvonian has no skin palette, and only three races have hair.
face_tone_tints(race, kind)
The tint hexes for a race's palette, in the same order as face_tone_names.
Parsing needs these and an editor needs the names; handing a parser the NAMES matches nothing and silently reports tone 0, which reads as "this face has no tint" and quietly strips somebody's skin color on an edit.
face_visemes(race)
The mouth indices to cycle while somebody is speaking. [] means do not animate.
get_face(ship_id)
Returns a face string for a specified ID
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ship_id
|
Agent | int
|
The id of the ship/object |
required |
Returns:
| Name | Type | Description |
|---|---|---|
str |
A Face string |
get_face_from_data(race)
Deprecated in v1.1.0.
Use random_race instead.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
race
|
_type_
|
description |
required |
Returns:
| Name | Type | Description |
|---|---|---|
_type_ |
description |
kralien(face_id, eye_id, mouth_id, scalp_id, extra_id, clothes_id=None)
Create a kralien face.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
face_id
|
int
|
body - Kralien has only one, so this is ignored |
required |
eye_id
|
int
|
eyes, 0-4 |
required |
mouth_id
|
int
|
mouth, 0-5 |
required |
scalp_id
|
int | None
|
rank pips, 0-2, or None |
required |
extra_id
|
int | None
|
eyewear when even, hat when odd, or None |
required |
clothes_id
|
int | None
|
clothes, 0-5, or None |
None
|
Returns:
| Type | Description |
|---|---|
str
|
A Face string |
parse_face(face_string)
Recover (race, values, enables) from a face string - the inverse of build_face.
Returns None when the string is not a readable stock face: empty, malformed, or a MOD atlas, whose cells are whole drawn busts with no features to take apart. All three are "nothing to resume", which is what an editor needs to know.
The result rebuilds to the identical string through build_face, so opening an editor on a face cannot silently change how somebody looks.
probably(chance)
True with the given probability.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
chance
|
float
|
A float between 0 and 1. |
required |
random_arvonian()
Create a random arvonian face.
Returns:
| Type | Description |
|---|---|
str
|
A Face string |
random_face(race=None, role=None, gender=None, civilian=None)
Returns a random face for the specified race.
Mod races registered with face_register_race() are checked FIRST, so an add-on can supply "klingon" or "cardassian" without this function knowing they exist. That is what the TODO which used to sit here was asking for.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
race
|
str
|
The Race Terran, Torgoth etc, or a registered mod race. |
None
|
role
|
str
|
optional role filter for registered races ("command", "ops"...). Ignored by the six stock races, which have no role concept. |
None
|
gender
|
str
|
"male", "female", "fluid" - so a face can be asked to AGREE with a name. Only terran has a gender axis; the other stock races ignore it, as do mod races, whose faces are whole drawn portraits. |
None
|
civilian
|
bool
|
True forces no uniform, False forces one, None (the default) leaves it to chance - which is one civilian in five. A crew member wants False: a bridge officer out of uniform is not a variation, it is a stranger on the bridge. Terran only, for the same reason. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
str |
The Face String |
random_kralien()
Create a random kralien face.
Returns:
| Type | Description |
|---|---|
str
|
A Face string |
random_skaraan()
Create a random skaraan face.
Returns:
| Type | Description |
|---|---|
str
|
A Face string |
random_terran(face=None, civilian=None)
Create a random terran face.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
face
|
int | None
|
0=masculine, 1=feminine, 2/3=fluid, None=roll one |
None
|
civilian
|
bool | None
|
True forces no uniform, False forces one, None rolls |
None
|
Returns:
| Type | Description |
|---|---|
str
|
A Face string |
random_terran_female(civilian=None)
Create a random feminine terran face.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
civilian
|
bool
|
True forces a civilian, False a uniform, None rolls. |
None
|
Returns:
| Type | Description |
|---|---|
str
|
A Face string |
random_terran_fluid(civilian=None)
Create a random fluid terran face, mixing masculine and feminine features.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
civilian
|
bool
|
True forces a civilian, False a uniform, None rolls. |
None
|
Returns:
| Type | Description |
|---|---|
str
|
A Face string |
random_terran_male(civilian=None)
Create a random masculine terran face.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
civilian
|
bool
|
True forces a civilian, False a uniform, None rolls. |
None
|
Returns:
| Type | Description |
|---|---|
str
|
A Face string |
random_torgoth()
Create a random torgoth face.
Returns:
| Type | Description |
|---|---|
str
|
A Face string |
random_ximni()
Create a random ximni face.
Returns:
| Type | Description |
|---|---|
str
|
A Face string |
set_face(ship_id, face)
Sets a face string for a specified ID.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ship_id
|
Agent | int
|
The id of the ship/object |
required |
face
|
str
|
A Face string |
required |
skaraan(face_id, eye_id, mouth_id, horn_id, hat_id, clothes_id=None)
Create a skaraan face.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
face_id
|
int
|
body, 0-1 |
required |
eye_id
|
int
|
eyes, 0-4 |
required |
mouth_id
|
int
|
mouth, 0-4 |
required |
horn_id
|
int | None
|
hair, 0-1, or None |
required |
hat_id
|
int | None
|
headwear (wraps, veil, headpiece), 0-4, or None |
required |
clothes_id
|
int | None
|
clothes, 0-4, or None. New; the old sheet had none. |
None
|
Returns:
| Type | Description |
|---|---|
str
|
A Face string |
terran(face_id, eye_id, mouth_id, hair_id, longhair_id, facial_id, extra_id, uniform_id, skintone, hairtone)
Create a terran face.
Two arguments changed meaning with the redraw, both because the art merged rows.
longhair_id is used only when hair_id is None - long and short styles now share
one row of 22. extra_id selects eyewear, which is what the old "extra" cells were.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
face_id
|
int | None
|
0=masculine, 1=feminine, 2/3=fluid (body is face_id % 2) |
required |
eye_id
|
int | None
|
eyes, 0-13 |
required |
mouth_id
|
int
|
mouth, 0-13 |
required |
hair_id
|
int | None
|
hair, 0-21, or None |
required |
longhair_id
|
int | None
|
hair, used only when hair_id is None |
required |
facial_id
|
int | None
|
facial hair, 0-13, or None |
required |
extra_id
|
int | None
|
eyewear, 0-3, or None |
required |
uniform_id
|
int | None
|
uniform, 0-9, or None for a civilian |
required |
skintone
|
int | str | None
|
palette index, a hex string, or None |
required |
hairtone
|
int | str | None
|
palette index, a hex string, or None |
required |
Returns:
| Type | Description |
|---|---|
str
|
A Face string |
torgoth(face_id, eye_id, mouth_id, hair_id, extra_id, hat_id, clothes_id=None)
Create a torgoth face.
The redrawn Torgoth sheet has NO mouth row, so mouth_id is folded into the nose
when no nose was asked for - dropping it outright would collapse distinct old faces
onto one another.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
face_id
|
int
|
body, 0-1 |
required |
eye_id
|
int
|
eyes, 0-4 |
required |
mouth_id
|
int
|
folded into the nose; Torgoth has no mouth art |
required |
hair_id
|
int | None
|
nose / tendrils, 0-3, or None |
required |
extra_id
|
int | None
|
armored eye plate, or None |
required |
hat_id
|
int | None
|
hat, 0-3, or None |
required |
clothes_id
|
int | None
|
clothes, 0-10, or None |
None
|
Returns:
| Type | Description |
|---|---|
str
|
A Face string |
ximni(face_id, eye_id, mouth_id, horns_id, mask_id, collar_id)
Create a ximni face.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
face_id
|
int
|
body, 0-1 |
required |
eye_id
|
int
|
eyes, 0-3 |
required |
mouth_id
|
int
|
mouth, 0-3 |
required |
horns_id
|
int | None
|
horns, 0-3, or None |
required |
mask_id
|
int | None
|
breathing mask, 0-3, or None |
required |
collar_id
|
int | None
|
clothes, 0-2, or None |
required |
Returns:
| Type | Description |
|---|---|
str
|
A Face string |