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.
| Side | both |
| Requires | none |
| Optional | menu (picker, invitation), form (picker search), prompts (stop key hint), downed (closes the picker), target (walking pace rows) |
| Contract | animations v1 — client |
| Config | config/animations.lua (shared) |
| Platform | open77_animations (solo, players.animations.control), open77_player_interactions (pairs, players.interactions.control) — both declared in the manifest |
| Data | none |
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:
| Family | Id (/e <id>) | Platform words |
|---|---|---|
| Gestures | gestures | gestures |
| On the move | onthemove | onthemove |
| Social | social | social |
| Emotions | emotions | emotions |
| Postures | postures | postures |
| Sitting | sitting | seated |
| Relaxation | relaxation | relaxation |
| Dance and music | music | dance, music |
| Consumables | consumables | consumables |
| Work | work | work |
| Interactions | interactions | interactions |
| More | other | any 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 pick | Typed | Coordinator kind |
|---|---|---|
| Carry them / Be carried | /e with carry, /e with carried | carry — fixed paired pose; the carrier walks, the other follows |
| Escort them / Be escorted | /e with escort, /e with escorted | escort — side by side, walking |
| Hand something over | /e with give | give |
| Look after them / Be looked after | /e with heal, /e with healed | heal — you kneel and examine, they sit wounded |
| A shortcut | /e with talk, /e with film | custom — 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 carryis the move,/e with carry carryis thecarryprofile 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 acceptand/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_LIMITwindow as a play. - One invitation or pair per player, either side (
duo_busy). - The stop key,
/opx.anim.stopor 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 + 2m, at most 20) or a failed presentation; both players are told why.
What the platform cannot do
- A
custompair is stationary: either body walking off ends it, a walking layer included. Onlycarryandescortmove, 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.
| Command | Access | Arguments | Effect |
|---|---|---|---|
/opx.anim | everyone | [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. |
/e | everyone | same as /opx.anim | Short form. |
/opx.anim.stop | everyone | — | Stops your animation (and your pair), unless started as not cancelable. |
/opx.anim.list | everyone | — | 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.
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 = falsewithholds both sides of that move. Onlycarry,escort,give,healare kinds; another key is a boot warning.- A
PAIRSrow:IDunique, lower case, not a move's id; its label is the locale keyanimations.duo.name.<ID>(without one the row reads its two profiles). A row naming a profile the build lacks or one inDISABLEDis not offered. - A bad value is a boot warning and falls back to the default shown.
- Pairs need
open77_player_interactionsrunning; 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.
| Code | Meaning | Locale key |
|---|---|---|
unknown_animation | Not in the catalogue, disabled, or not offered; for a pair, no such move, shortcut or profile. | animations.error.unknownAnimation, animations.duo.unknown |
unknown_category | Not one of the twelve families. | — |
invalid_variant | No such variant, or not offered. | animations.error.invalidVariant |
invalid_options | Bad options field, or non-cancelable with no end. | animations.error.invalidOptions |
animation_locked | A non-cancelable animation is running. | animations.error.locked |
rate_limited | Over RATE_LIMIT. Only the first refusal per window is answered. | animations.error.rateLimited |
player_not_ready | The player has not finished loading. | animations.error.notReady |
player_not_alive, player_in_vehicle, animation_owned | Refused by the platform service. | animations.error.notAlive, animations.error.inVehicle, animations.error.owned |
service_unavailable | No animation service, or players.animations.control not granted. | animations.error.unavailable |
play_raised, play_refused, stop_raised | The service or coordinator call failed. | animations.error.refused |
not_sent, request_timeout | The request did not leave, or got no answer in 15 s. | animations.error.notSent, animations.error.timeout |
no_player_nearby | Nobody free and ready within SHARED.RANGE, in your bucket. | animations.duo.nobody |
duo_busy, player_reserved, animation_busy | One of you already has an invitation or pair. | animations.duo.busy |
too_far, wrong_bucket, position_unavailable | Apart when the invitation was accepted. | animations.duo.tooFar |
duo_unavailable, interactions_unavailable, paired_animation_fixed | Pairs are off, or the coordinator is missing or refused. | animations.duo.unavailable |
menu_not_running, player_down, picker_not_open | Picker only. | animations.error.menuNotRunning |
invalid_player, presentation_unavailable | State only. | — |
internal_error | A contract function raised. | — |