OPX//77
opx_infinityanimations

animations

Last updated on

Emotes for players — every platform animation, alone or with a nearby player, from /e, the F3 picker and the X stop key, plus walking paces and a client contract.

Players play emotes with /e dance or the picker on F3 (built with the menu) and stop with X. Other modules use the contract to play an animation on the local player (a ripperdoc chair, drinking an item). The module plays nothing itself: solo emotes go to the platform's animation service, pairs to its two-player coordinator. It adds the picker, a per-player rate limit, a readiness check, the invitation and walking paces on the player's own eye menu. Nothing is written to the database.

Sideboth
Requiresnone
Optionalmenu (picker, invitation), form (picker search), prompts (stop key hint), downed (closes the picker), target (walking pace rows)
Contractanimations v1 — client
Configconfig/animations.lua (shared)
Platformopen77_animations (solo, players.animations.control), open77_player_interactions (pairs, players.interactions.control) — both declared in the manifest
Datanone

Catalogue

Every profile the platform lists is offered: 104 on op77.123, read at start with Open77.animations.list, so a profile a later build adds shows up at the next start. Fifteen rows are also written in shared/catalogue.lua and win on the same name. The full list is on Catalogue.

Not offered: names in DISABLED, profiles whose validation word says they failed (fail, broken, unsupported…), malformed rows. The boot log has one line with the counts:

[animations] platform catalogue: 104 profiles listed, 89 adopted (not adopted: written 15)

The picker draws 12 families, 16 emotes to a page. The platform's own family words map onto them:

FamilyId (/e <id>)Platform words
Gesturesgesturesgestures
On the moveonthemoveonthemove
Socialsocialsocial
Emotionsemotionsemotions
Posturesposturespostures
Sittingsittingseated
Relaxationrelaxationrelaxation
Dance and musicmusicdance, music
Consumablesconsumablesconsumables
Workworkwork
Interactionsinteractionsinteractions
Moreotherany word this module does not know

/e dance is the emote, not the family (hence music and sitting). A one-shot gesture (wave, shrug…) plays once for the clip length the platform measured; a layer profile (the _walk rows) keeps your own walking.

With a nearby player

Picker › With a nearby player, or /e with …. The server invites the nearest player within SHARED.RANGE, from positions it observes — no client names who is invited. Nothing plays until they accept.

You pickTypedCoordinator kind
Carry them / Be carried/e with carry, /e with carriedcarry — fixed paired pose; the carrier walks, the other follows
Escort them / Be escorted/e with escort, /e with escortedescort — side by side, walking
Hand something over/e with givegive
Look after them / Be looked after/e with heal, /e with healedheal — you kneel and examine, they sit wounded
A shortcut/e with talk, /e with filmcustom — a SHARED.PAIRS row
Any two animations/e with <yours> [theirs]custom — one profile per body
/e with carry            invite the nearest player: you carry them
/e with dance            both dance
/e with preach armscrossed   you preach, they cross their arms
/e accept                the invited player says yes
/e decline               ... or no
  • A paired move's id is read before a profile of the same word: /e with carry is the move, /e with carry carry is the carry profile on both.
  • In the picker, Any two animations is two picks — yours, then theirs ("The same" on top) — so every ordered pair (104 × 104) is reachable without a 10 000-row list.
  • The invited player gets a two-row menu (Accept, Decline) or a chat line naming /e accept and /e decline. No name either way: "The person next to you invites you: {emote}.", and the asker reads sent, accepted or declined without the other's name.
ask ─► nearest player in RANGE invited ─► accept ─► distance measured again ─► pair starts
                                        ├► decline ─► asker told "declined" (no name)
                                        └► INVITE_MS passes ─► withdrawn, asker told "expired"
  • An invitation counts against the same RATE_LIMIT window as a play.
  • One invitation or pair per player, either side (duo_busy).
  • The stop key, /opx.anim.stop or disconnecting ends the pair for both; the asker leaving withdraws the invitation.
  • The coordinator also ends a pair on death, a vehicle, a bucket change, separation (RANGE + 2 m, at most 20) or a failed presentation; both players are told why.

What the platform cannot do

  • A custom pair is stationary: either body walking off ends it, a walking layer included. Only carry and escort move, and those two take no profile override (paired_animation_fixed).
  • A pair plays a profile, not a clip: no variant choice.
  • Two players per interaction, one interaction each.
  • Solo emotes are refused by the service for a dead player or one in a vehicle (player_not_alive, player_in_vehicle).

Commands

All four act on the caller only and are open by default.

CommandAccessArgumentsEffect
/opx.animeveryone[name [variant] | family | stop | list | with <move|name> [name] | accept | decline]None: opens the picker. A family: picker on it. A name: plays it. with: invites the nearest player (above). accept, decline: answers your invitation.
/eeveryonesame as /opx.animShort form.
/opx.anim.stopeveryone—Stops your animation (and your pair), unless started as not cancelable.
/opx.anim.listeveryone—Every offered animation by family, with variant counts. From the console: printed to the log.

Names come from COMMANDS; RESTRICTED = true gates command.<name>.

Configuration

config/animations.lua sets OPX.Config.MODULES.animations. Shared script.

KeyDefaultEffect
enabledtruefalse switches the module off.
PRESENTER'auto'Who poses the bodies on this client. auto leaves it to open77_animations while that runs; always / never force it.
NOTIFYtrueRefusals as toasts; false writes chat lines.
TOAST_MS4000Toast duration, 750..120000.
PROMPTStrueStop key in the prompt strip while an animation plays.
DISABLED{}Names never offered, alone or in a pair: a written row, a platform profile ('camera_point'), or a paired move's id ('escort', 'carried'). A name matching nothing is a boot-log warning.
PLATFORMtrueOffer every platform profile beside the written rows. false: the 15 written rows only.
SHAREDsee belowEmotes with a nearby player. Not a table, or ENABLED = false: none offered.
LOOP_BY_DEFAULTtrueLoop when a request does not say.
ONE_SHOT_MS10000Length of a non-looping play with no duration (a one-shot platform gesture uses its measured clip). Tunable ANIM_ONE_SHOT_MS.
MAX_DURATION_MS600000Longest duration a request may name. Tunable ANIM_MAX_DURATION_MS.
RATE_LIMIT{ WINDOW_MS = 10000, REQUESTS = 6 }Plays and invitations per player per window; stops get twice as many. Checked before the request is validated. Tunables ANIM_RATE_WINDOW_MS, ANIM_RATE_REQUESTS.
PICKER{ CLOSE_ON_SELECT = true, SHOW_VARIANT_WORDS = true }Close the picker after a choice; show the engine clip words beside each variant.
KEYS{ PICKER = 'F3', STOP = 'X' }Default keys (opx.animations.picker, opx.animations.stop), rebindable in the pause menu. false registers none. The same key for both drops the stop key.
WALK_PACESstroll 0.8, walk 1.2, brisk 1.8{ ID, SPEED } paces in m/s (0.5..2.5) a player cycles from their own eye menu. Past the last: normal movement.
COMMANDSANIM = opx.anim, EMOTE = e, STOP = opx.anim.stop, LIST = opx.anim.list, all RESTRICTED = false{ KEY = { NAME, RESTRICTED } }. NAME = false registers none.
config/animations.lua — SHARED
SHARED = {
	ENABLED = true,
	RANGE = 3.0,         -- metres between the two bodies, 0.25..10
	INVITE_MS = 15000,   -- how long an invitation waits, 1000..60000
	KINDS = {            -- the platform's paired moves; DURATION_MS 500..600000
		carry  = { ENABLED = true, DURATION_MS = 60000 },
		escort = { ENABLED = true, DURATION_MS = 60000 },
		give   = { ENABLED = true, DURATION_MS = 5000 },
		heal   = { ENABLED = true, DURATION_MS = 12000 },
	},
	ANY = true,          -- any two profiles, one per body
	DURATION_MS = 30000, -- length of such a pair; two one-shot gestures use the longer clip
	PAIRS = {            -- shortcuts listed above "any two"
		{ ID = 'talk', ACTOR = 'preach', TARGET = 'armscrossed', DURATION_MS = 20000 },
		{ ID = 'film', ACTOR = 'record', TARGET = 'dance', DURATION_MS = 30000 },
	},
},
  • KINDS.<kind>.ENABLED = false withholds both sides of that move. Only carry, escort, give, heal are kinds; another key is a boot warning.
  • A PAIRS row: ID unique, lower case, not a move's id; its label is the locale key animations.duo.name.<ID> (without one the row reads its two profiles). A row naming a profile the build lacks or one in DISABLED is not offered.
  • A bad value is a boot warning and falls back to the default shown.
  • Pairs need open77_player_interactions running; without it the boot log says so and the picker has no With a nearby player row.

Refusal codes

A refusal caused by a contract call is not shown to the player (the caller decides); refusals from keys, commands and the picker are shown.

CodeMeaningLocale key
unknown_animationNot in the catalogue, disabled, or not offered; for a pair, no such move, shortcut or profile.animations.error.unknownAnimation, animations.duo.unknown
unknown_categoryNot one of the twelve families.—
invalid_variantNo such variant, or not offered.animations.error.invalidVariant
invalid_optionsBad options field, or non-cancelable with no end.animations.error.invalidOptions
animation_lockedA non-cancelable animation is running.animations.error.locked
rate_limitedOver RATE_LIMIT. Only the first refusal per window is answered.animations.error.rateLimited
player_not_readyThe player has not finished loading.animations.error.notReady
player_not_alive, player_in_vehicle, animation_ownedRefused by the platform service.animations.error.notAlive, animations.error.inVehicle, animations.error.owned
service_unavailableNo animation service, or players.animations.control not granted.animations.error.unavailable
play_raised, play_refused, stop_raisedThe service or coordinator call failed.animations.error.refused
not_sent, request_timeoutThe request did not leave, or got no answer in 15 s.animations.error.notSent, animations.error.timeout
no_player_nearbyNobody free and ready within SHARED.RANGE, in your bucket.animations.duo.nobody
duo_busy, player_reserved, animation_busyOne of you already has an invitation or pair.animations.duo.busy
too_far, wrong_bucket, position_unavailableApart when the invitation was accepted.animations.duo.tooFar
duo_unavailable, interactions_unavailable, paired_animation_fixedPairs are off, or the coordinator is missing or refused.animations.duo.unavailable
menu_not_running, player_down, picker_not_openPicker only.animations.error.menuNotRunning
invalid_player, presentation_unavailableState only.—
internal_errorA contract function raised.—

On this page