Talos docs

PlayersHandle

Since 0.7

The local players on the couch (meta.players) and the entity each drives.

Plain text

Seating and unseating raise player-joined ({ player, device, pad? }), player-left, player-disconnected and player-reconnected ({ player }) on the event bus. A behaviour on a player's entity reads its own ctx.input exactly as a single player's does.

Signature

export interface PlayersHandle {
  /** Every seated player, in number order. */
  list(): LocalPlayer[]
  /** How many players are seated. */
  readonly count: number
  /** The scene's player limit, 1 to 4; 1 for a scene without a players block. */
  readonly max: number
  /** Whether free devices may join now. Open in a lobby, closed during play. */
  readonly joining: boolean
  /** Open or close joining: a lobby opens it, a race closes it. Seated players stay seated. */
  setJoining(open: boolean): void
  /** Seat a device from a script (`join: "script"` scenes). The new number, or null when no seat was free. */
  join(device: "keyboard" | "keyboard-left" | "keyboard-right"): number | null
  join(device: "pad", pad: number): number | null
  /** Seat a player with no device on this machine: a peer's, or one a test or tool drives by input frames. */
  join(device: "remote"): number | null
  /** Unseat a player; their devices are free to join again. */
  leave(number: number): boolean
  /** Drive `entity` with player `number`'s devices; null goes back to the scene's `spawn { for: "player", player }`. */
  bind(number: number, entity: EntityHandle | string | null): boolean
  /** The player whose devices drive `entity`, or 0. */
  of(entity: EntityHandle | string): number
  /** Player `number` (2 and up) reads its own input context; null follows player 1's again. */
  setContext(number: number, name: string | null): boolean
  /** The input context player `number` reads now, or null. */
  context(number: number): string | null
}

Import: import type { PlayersHandle } from "@argus/api"

Members

MemberSignatureDescription
listlist(): LocalPlayer[]Every seated player, in number order.
countreadonly count: numberHow many players are seated.
maxreadonly max: numberThe scene's player limit, 1 to 4; 1 for a scene without a players block.
joiningreadonly joining: booleanWhether free devices may join now. Open in a lobby, closed during play.
setJoiningsetJoining(open: boolean): voidOpen or close joining: a lobby opens it, a race closes it. Seated players stay seated.
joinjoin(device: "keyboard" | "keyboard-left" | "keyboard-right"): number | nullSeat a device from a script (join: "script" scenes). The new number, or null when no seat was free.
leaveleave(number: number): booleanUnseat a player; their devices are free to join again.
bindbind(number: number, entity: EntityHandle | string | null): booleanDrive entity with player number's devices; null goes back to the scene's spawn { for: "player", player }.
ofof(entity: EntityHandle | string): numberThe player whose devices drive entity, or 0.
setContextsetContext(number: number, name: string | null): booleanPlayer number (2 and up) reads its own input context; null follows player 1's again.
contextcontext(number: number): string | nullThe input context player number reads now, or null.

Example

The tested fixture fixtures/recipes/collectible-and-goal/scenes/behaviors/collectible.ts.

import type { Behavior } from "@argus/api"

/**
 * A coin the player collects by walking into it.
 *
 * It says so once, as `collected`, and leaves the world. Counting is somebody else's job —
 * `goal-exit.ts` — so a coin never needs to know how many there are.
 *
 * The contact is `onTrigger`, which needs three things in the scene: a collider with
 * `isTrigger: true` (the volume), a `trigger` component (what fires), and a body on the player —
 * the character controller's capsule is one.
 */
interface State {
  collected: boolean
}

interface Props {
  /** Tag the toucher must carry. Default `player`. */
  playerTag?: string
}

export default {
  init(ctx) {
    if (typeof ctx.state.collected !== "boolean") ctx.state.collected = false
  },

  onTrigger(ctx, trigger) {
    if (ctx.state.collected || !trigger.entering) return
    if (!trigger.other.hasTag(ctx.props.playerTag ?? "player")) return
    ctx.state.collected = true
    // The `goal` component on this coin has the same id, so the playtester and the HUD agree.
    ctx.events.emit("collected", { goal: ctx.entity.id })
    // Last: after `despawn` every member of this entity's handles throws.
    ctx.entity.despawn()
  },
} satisfies Behavior<State, Props>