Scenes

Several entities, players with a body among them, animated together by the server on one clock: the base a mod builds a paired animation, a cutscene or a scene system on.

The idea

Strut owns what every such system needs and none of what a scene means. A scene has roles (names you pick: host, partner), each filled by an entity placed at a spot in the scene's space. A step says which clip each role plays. Every clip of a step starts on the same game tick, so the clips stay in step for everyone however they are watching (the clock is the server's, see playing animations), and the next step replaces the one before on all roles at once. Which step follows, what an event does and how a scene is won are yours.

Starting one

Scene scene = StrutScenes.create(new Identifier("mymod", "embrace"), world, origin, yaw)
        .participant("host", creature, ScenePlacement.ORIGIN)
        .participant("partner", player, ScenePlacement.offset(0, 0, 0.8).facing(180))
        .onStepEnd(next -> next.step().clip("host", "embrace_end").clip("partner", "embrace_end").start())
        .onEnd((ended, why) -> ...)
        .start();

scene.step().clip("host", "embrace_in").clip("partner", "embrace_in").start();   // both start on the same tick
scene.setRate(1.5f);                                                              // faster for both, without a jump

start() places everyone and plays nothing; scene.step()...start() plays. A role the step does not name stops what it was playing, so a step is also how a branch drops a participant's clip. clip(role, name) plays once and holds the last frame; clip(role, name, LoopMode.LOOP) loops. clipThen(role, intro, then, LoopMode.LOOP) plays an intro and, the moment it ends, the clip that follows it on the same clock with no gap (an intro and its loop, for one role); the step is not finished while the intro runs.

Where everyone stands

The scene has an origin and a yaw. A ScenePlacement is a spot in the scene's own space: z ahead, x to the left, y up, plus a turn from the scene's facing. Author the scene once with the host at the origin and the partner a step ahead and it works wherever it is placed; scene.move(origin, yaw) moves them all.

Which controller plays

Every role plays on a controller of its entity: scene by default, which every player body has (over locomotion, swings and item use) and which a StrutEntity makes on demand. participant(role, entity, placement, controllerName, required) picks another. The clip names are found the way they are for any server-played animation: through the body's or entity's declared animation files, then by name if only one file has it.

Steps, branches and the end

Joining and leaving

scene.join(role, entity, placement) adds a participant to a scene that is running (a partner who gets on), placed and announced as at the start, with no clip until it is given one: by the next step, or by scene.playNow(role, clip, loop), which starts a clip for that role alone and leaves the others as they were. scene.leave(role) takes one out: its clip stops, what the scene changed on it is put back, and the scene goes on, even if the role was required. So a participant changing does not mean a new scene, and nothing restarts.

Losing a participant

A participant is required unless added with optional(...). If a required one logs out, dies, is removed or changes dimension, the scene ends with SceneEnd.PARTICIPANT_LOST; an optional one just leaves its role. An entity is in one scene at a time: starting a second throws an exception that names the entity, the role and the scene it is in.

Events and the client

Clips of a scene fire their markers and typed events as any server-played clip does (events). In a handler, StrutScenes.sceneOf(context.entity()) on the server finds the scene and scene.roleOf(entity) the role. On every client that sees an entity in a scene, StrutScenes.of(entity) is its SceneMember: the scene's id, the role, the kind of scene and the origin, for a camera, a HUD or a rule. Taking part in a scene also makes a player a participant of what the other members' clips do (ClientEventContext#isLocalPlayerParticipant).

Players and outfits

A scene with players needs nothing special: give them a body (players with a body) and the scene controller shows what the server plays. The same calls work outside a scene: StrutPlayers.play(player, "outfit", LoopMode.PLAY_ONCE), setRate, seek, playing, hasEnded. To hide the armour, use StrutArmour.hideAll(entity) (setHidden for one slot, showAll to dress again), and change the model a character is drawn with through the body's variant or its model (players). A mod's strip then is: play the clip, hide the armour on its event, swap the variant.

What to know