Packages
game_server_sdk
1.0.1073
1.0.1082
1.0.1079
1.0.1078
1.0.1077
1.0.1076
1.0.1075
1.0.1074
1.0.1073
1.0.1070
1.0.1068
1.0.1067
1.0.1063
1.0.1059
1.0.1058
1.0.1057
1.0.1056
1.0.1055
1.0.1050
1.0.1049
1.0.1048
1.0.1047
1.0.1046
1.0.1044
1.0.1043
1.0.1042
1.0.1041
1.0.1040
1.0.1039
1.0.1038
1.0.1034
1.0.1033
1.0.1029
1.0.1028
1.0.1026
1.0.1025
1.0.1024
1.0.1023
1.0.1022
1.0.1021
1.0.1020
1.0.1019
1.0.1018
1.0.1017
1.0.1016
1.0.1015
1.0.1014
1.0.1013
1.0.1012
1.0.1011
1.0.1009
1.0.1008
1.0.1007
1.0.1006
1.0.1005
1.0.1004
1.0.1003
1.0.1001
1.0.999
1.0.998
1.0.997
1.0.996
1.0.995
1.0.994
1.0.993
1.0.992
1.0.991
1.0.990
1.0.989
1.0.988
1.0.987
1.0.986
1.0.985
1.0.984
1.0.983
1.0.982
1.0.981
1.0.980
1.0.979
1.0.978
1.0.977
1.0.976
1.0.975
1.0.974
1.0.973
1.0.972
1.0.971
1.0.970
1.0.969
1.0.968
1.0.967
1.0.966
1.0.965
1.0.964
1.0.963
1.0.962
1.0.961
1.0.959
1.0.958
1.0.956
1.0.951
1.0.950
1.0.943
1.0.942
1.0.941
1.0.940
1.0.938
1.0.936
1.0.935
1.0.931
1.0.929
1.0.928
1.0.927
1.0.926
1.0.925
1.0.924
1.0.923
1.0.921
1.0.920
1.0.919
1.0.918
1.0.917
1.0.916
1.0.911
1.0.910
1.0.902
1.0.899
1.0.898
1.0.897
1.0.896
1.0.894
1.0.893
1.0.891
1.0.890
1.0.889
1.0.888
1.0.887
1.0.886
1.0.885
1.0.884
1.0.883
1.0.882
1.0.881
1.0.880
1.0.879
1.0.878
1.0.877
1.0.26
1.0.25
1.0.22
1.0.21
1.0.20
1.0.19
1.0.15
1.0.14
1.0.13
1.0.12
1.0.10
1.0.9
1.0.8
1.0.7
1.0.6
1.0.5
1.0.4
1.0.3
1.0.2
1.0.1
1.0.0
0.1.0
SDK for GameServer hooks development. Provides type specs, documentation, and IDE autocomplete for GameServer modules without requiring the full server.
Current section
Files
Jump to
Current section
Files
lib/game_server/ready_checks.ex
defmodule GameServer.ReadyChecks do
@moduledoc ~S"""
Ready checks: *these players must each answer before this proceeds*.
One primitive, two kinds — the only differences are what a "no" means and
whether an answer can be taken back:
| | `"accept"` | `"ready"` |
| --- | --- | --- |
| Answer | one-shot, irrevocable | a toggle |
| A "no" | fails the check for everyone | leaves it pending |
| Deadline | mandatory | optional |
| On timeout | fails | fails, naming who stalled |
`"ready"` is the lobby's ready-up and the party's standing ready board;
`"accept"` is matchmaking's match confirmation (see
`docs/specs/ready-check.md`).
## Two lanes
A player can be in at most one open check *per lane*: the match lane (lobby
ready or matchmaking accept — one match at a time) and the party lane. The
lanes are independent, so a party's standing board never blocks the party's
lobby from opening its own check.
## What core does *not* do
A failed check kicks nobody, deletes no lobby and moves no lobby state. Core
records who did not answer (`not_ready/1`); the host — or the game, in
`after_ready_check_failed` — decides what that is worth.
## Usage
{:ok, check} = ReadyChecks.open(lobby, member_ids, opened_by: host.id)
{:ok, check} = ReadyChecks.respond(user, true)
ReadyChecks.passed?(lobby)
## Concurrency
Answering is a single-row write, so no two players can lose each other's
flag. *Evaluating* the result is the part that races: two players answering
at once can each count the other as still pending, and nobody passes. So
`respond/3` holds a per-check advisory lock (`:ready_check`) around
write-then-evaluate. Hooks and broadcasts fire after the lock is released —
never inside the transaction.
**Note:** This is an SDK stub. Calling these functions will raise an error.
The actual implementation runs on the GameServer.
"""
@type answer() :: boolean()
@type scope() :: :match | :party
@type subject() :: GameServer.Lobbies.Lobby.t() | GameServer.Parties.Party.t() | :matchmaking
@doc ~S"""
Adds a member to the lobby's open check, if there is one.
"""
@spec add_member(Ecto.UUID.t(), Ecto.UUID.t()) :: :ok
def add_member(_lobby_id, _user_id) do
case Application.get_env(:game_server_sdk, :stub_mode, :raise) do
:placeholder ->
:ok
_ ->
raise "GameServer.ReadyChecks.add_member/2 is a stub - only available at runtime on GameServer"
end
end
@doc ~S"""
Adds a member to the party's open check, if there is one.
"""
@spec add_party_member(Ecto.UUID.t(), Ecto.UUID.t()) :: :ok
def add_party_member(_party_id, _user_id) do
case Application.get_env(:game_server_sdk, :stub_mode, :raise) do
:placeholder ->
:ok
_ ->
raise "GameServer.ReadyChecks.add_party_member/2 is a stub - only available at runtime on GameServer"
end
end
@doc ~S"""
Answers on behalf of a member — for bots and AI-controlled players, which
cannot press anything.
Server-side only: this is in `internal_hooks()`, so a client cannot reach it
over RPC and mark someone else ready.
"""
@spec answer_for(GameServer.ReadyChecks.Check.t(), Ecto.UUID.t(), answer()) ::
{:ok, GameServer.ReadyChecks.Check.t()} | {:error, term()}
def answer_for(_check, _user_id, _ready?) do
case Application.get_env(:game_server_sdk, :stub_mode, :raise) do
:placeholder ->
{:ok, %GameServer.ReadyChecks.Check{id: "", kind: "ready", status: "pending", lobby_id: nil, deadline: nil, opened_by: nil, reason: nil, resolved_at: nil, metadata: %{}, participants: [], inserted_at: ~U[1970-01-01 00:00:00Z], updated_at: ~U[1970-01-01 00:00:00Z]}}
_ ->
raise "GameServer.ReadyChecks.answer_for/3 is a stub - only available at runtime on GameServer"
end
end
@doc ~S"""
Cancels a pending check — the host called it off, or the subject went away.
"""
@spec cancel(GameServer.ReadyChecks.Check.t(), String.t()) ::
{:ok, GameServer.ReadyChecks.Check.t()} | {:error, term()}
def cancel(_check, _reason) do
case Application.get_env(:game_server_sdk, :stub_mode, :raise) do
:placeholder ->
{:ok, %GameServer.ReadyChecks.Check{id: "", kind: "ready", status: "pending", lobby_id: nil, deadline: nil, opened_by: nil, reason: nil, resolved_at: nil, metadata: %{}, participants: [], inserted_at: ~U[1970-01-01 00:00:00Z], updated_at: ~U[1970-01-01 00:00:00Z]}}
_ ->
raise "GameServer.ReadyChecks.cancel/2 is a stub - only available at runtime on GameServer"
end
end
@doc ~S"""
Cancels the lobby's pending check, if it has one.
"""
@spec cancel_for_lobby(Ecto.UUID.t()) :: :ok
def cancel_for_lobby(_lobby_id) do
case Application.get_env(:game_server_sdk, :stub_mode, :raise) do
:placeholder ->
:ok
_ ->
raise "GameServer.ReadyChecks.cancel_for_lobby/1 is a stub - only available at runtime on GameServer"
end
end
@doc ~S"""
Cancels the party's pending check, if it has one.
"""
@spec cancel_for_party(Ecto.UUID.t()) :: :ok
def cancel_for_party(_party_id) do
case Application.get_env(:game_server_sdk, :stub_mode, :raise) do
:placeholder ->
:ok
_ ->
raise "GameServer.ReadyChecks.cancel_for_party/1 is a stub - only available at runtime on GameServer"
end
end
@doc ~S"""
Counts checks matching the same filters as `list_checks/1`.
"""
@spec count_checks(keyword()) :: non_neg_integer()
def count_checks(_opts) do
case Application.get_env(:game_server_sdk, :stub_mode, :raise) do
:placeholder ->
0
_ ->
raise "GameServer.ReadyChecks.count_checks/1 is a stub - only available at runtime on GameServer"
end
end
@doc ~S"""
Fails one check on its deadline. A no-op if it already resolved.
"""
@spec expire(GameServer.ReadyChecks.Check.t()) :: :ok | :noop
def expire(_check) do
case Application.get_env(:game_server_sdk, :stub_mode, :raise) do
:placeholder ->
:ok
_ ->
raise "GameServer.ReadyChecks.expire/1 is a stub - only available at runtime on GameServer"
end
end
@doc ~S"""
Fails every pending check whose deadline has passed.
Each still-unanswered participant becomes `timed_out`. Returns how many
checks were expired. Idempotent, so the durable expiry job and the
matchmaking sweep's backstop can both run it.
"""
@spec expire_due(DateTime.t()) :: non_neg_integer()
def expire_due(_now) do
case Application.get_env(:game_server_sdk, :stub_mode, :raise) do
:placeholder ->
0
_ ->
raise "GameServer.ReadyChecks.expire_due/1 is a stub - only available at runtime on GameServer"
end
end
@doc ~S"""
The caller's open check, with participants preloaded, or nil.
`scope` narrows to one lane: `:match` (lobby or matchmaking) or `:party`.
`:any` returns the newest across both lanes — the admin's view, not the
API's.
"""
@spec for_user(GameServer.Accounts.User.t() | Ecto.UUID.t(), scope() | :any) ::
GameServer.ReadyChecks.Check.t() | nil
def for_user(_user, _scope) do
case Application.get_env(:game_server_sdk, :stub_mode, :raise) do
:placeholder ->
if :erlang.phash2(make_ref(), 2) == 0, do: nil, else: %GameServer.ReadyChecks.Check{id: "", kind: "ready", status: "pending", lobby_id: nil, deadline: nil, opened_by: nil, reason: nil, resolved_at: nil, metadata: %{}, participants: [], inserted_at: ~U[1970-01-01 00:00:00Z], updated_at: ~U[1970-01-01 00:00:00Z]}
_ ->
raise "GameServer.ReadyChecks.for_user/2 is a stub - only available at runtime on GameServer"
end
end
@doc ~S"""
Fetches a check by id (with participants), or nil.
"""
@spec get_check(Ecto.UUID.t()) :: GameServer.ReadyChecks.Check.t() | nil
def get_check(_id) do
case Application.get_env(:game_server_sdk, :stub_mode, :raise) do
:placeholder ->
if :erlang.phash2(make_ref(), 2) == 0, do: nil, else: %GameServer.ReadyChecks.Check{id: "", kind: "ready", status: "pending", lobby_id: nil, deadline: nil, opened_by: nil, reason: nil, resolved_at: nil, metadata: %{}, participants: [], inserted_at: ~U[1970-01-01 00:00:00Z], updated_at: ~U[1970-01-01 00:00:00Z]}
_ ->
raise "GameServer.ReadyChecks.get_check/1 is a stub - only available at runtime on GameServer"
end
end
@doc ~S"""
Lists checks for the admin views, newest first.
Options: `:status`, `:kind`, `:lobby_id`, `:party_id`, `:page`,
`:page_size`.
"""
@spec list_checks(keyword()) :: [GameServer.ReadyChecks.Check.t()]
def list_checks(_opts) do
case Application.get_env(:game_server_sdk, :stub_mode, :raise) do
:placeholder ->
[]
_ ->
raise "GameServer.ReadyChecks.list_checks/1 is a stub - only available at runtime on GameServer"
end
end
@doc ~S"""
The participants who did not answer ready — the host's kick list, and what
`after_ready_check_failed` is handed.
"""
@spec not_ready(GameServer.ReadyChecks.Check.t()) :: [GameServer.ReadyChecks.Participant.t()]
def not_ready(_check) do
case Application.get_env(:game_server_sdk, :stub_mode, :raise) do
:placeholder ->
[]
_ ->
raise "GameServer.ReadyChecks.not_ready/1 is a stub - only available at runtime on GameServer"
end
end
@doc ~S"""
Opens a check over `user_ids` and notifies them.
`subject` is a `%Lobby{}` or `%Party{}` (kind `"ready"`) or `:matchmaking`
(kind `"accept"`). Options:
* `:kind` — override the kind implied by the subject
* `:timeout_ms` — answering window; `nil` leaves a `"ready"` check open
until it passes or is cancelled. Defaults to `ready_check_timeout_ms`.
* `:opened_by` — the user who asked for it; they are pre-marked ready,
since clicking the button is their answer
* `:ready` — user ids to pre-mark ready (bots, an auto-ready mode)
* `:tickets` — `%{user_id => ticket_id}` for matchmaking checks
* `:metadata` — game payload echoed to clients (match params, mode)
Fails with `{:error, :already_pending}` when the subject already has an open
check or any player is in one in the same lane, `{:error, :no_participants}`,
and `{:error, :too_many_participants}` past `max_ready_check_participants`.
"""
@spec open(subject(), [Ecto.UUID.t()], keyword()) ::
{:ok, GameServer.ReadyChecks.Check.t()} | {:error, term()}
def open(_subject, _user_ids, _opts) do
case Application.get_env(:game_server_sdk, :stub_mode, :raise) do
:placeholder ->
{:ok, %GameServer.ReadyChecks.Check{id: "", kind: "ready", status: "pending", lobby_id: nil, deadline: nil, opened_by: nil, reason: nil, resolved_at: nil, metadata: %{}, participants: [], inserted_at: ~U[1970-01-01 00:00:00Z], updated_at: ~U[1970-01-01 00:00:00Z]}}
_ ->
raise "GameServer.ReadyChecks.open/3 is a stub - only available at runtime on GameServer"
end
end
@doc ~S"""
True when the subject's most recent check passed.
What a game calls from `before_lobby_state_change` to gate its own start.
A reset opens a fresh pending check, which makes this false again — so a
rematch cannot ride the previous match's pass.
"""
@spec passed?(GameServer.Lobbies.Lobby.t() | GameServer.Parties.Party.t() | Ecto.UUID.t()) :: boolean()
def passed?(_lobby_id) do
case Application.get_env(:game_server_sdk, :stub_mode, :raise) do
:placeholder ->
false
_ ->
raise "GameServer.ReadyChecks.passed?/1 is a stub - only available at runtime on GameServer"
end
end
@doc ~S"""
The lobby's open check, with participants preloaded, or nil.
"""
@spec pending_for_lobby(Ecto.UUID.t()) :: GameServer.ReadyChecks.Check.t() | nil
def pending_for_lobby(_lobby_id) do
case Application.get_env(:game_server_sdk, :stub_mode, :raise) do
:placeholder ->
if :erlang.phash2(make_ref(), 2) == 0, do: nil, else: %GameServer.ReadyChecks.Check{id: "", kind: "ready", status: "pending", lobby_id: nil, deadline: nil, opened_by: nil, reason: nil, resolved_at: nil, metadata: %{}, participants: [], inserted_at: ~U[1970-01-01 00:00:00Z], updated_at: ~U[1970-01-01 00:00:00Z]}
_ ->
raise "GameServer.ReadyChecks.pending_for_lobby/1 is a stub - only available at runtime on GameServer"
end
end
@doc ~S"""
The party's open check, with participants preloaded, or nil.
"""
@spec pending_for_party(Ecto.UUID.t()) :: GameServer.ReadyChecks.Check.t() | nil
def pending_for_party(_party_id) do
case Application.get_env(:game_server_sdk, :stub_mode, :raise) do
:placeholder ->
if :erlang.phash2(make_ref(), 2) == 0, do: nil, else: %GameServer.ReadyChecks.Check{id: "", kind: "ready", status: "pending", lobby_id: nil, deadline: nil, opened_by: nil, reason: nil, resolved_at: nil, metadata: %{}, participants: [], inserted_at: ~U[1970-01-01 00:00:00Z], updated_at: ~U[1970-01-01 00:00:00Z]}
_ ->
raise "GameServer.ReadyChecks.pending_for_party/1 is a stub - only available at runtime on GameServer"
end
end
@doc ~S"""
Drops a member from the lobby's open check and re-evaluates it.
Called when someone leaves or is kicked: kicking the one player who never
answered is a legitimate way to pass a check.
"""
@spec remove_member(Ecto.UUID.t(), Ecto.UUID.t()) :: :ok
def remove_member(_lobby_id, _user_id) do
case Application.get_env(:game_server_sdk, :stub_mode, :raise) do
:placeholder ->
:ok
_ ->
raise "GameServer.ReadyChecks.remove_member/2 is a stub - only available at runtime on GameServer"
end
end
@doc ~S"""
Drops a member from the party's open check and re-evaluates it.
"""
@spec remove_party_member(Ecto.UUID.t(), Ecto.UUID.t()) :: :ok
def remove_party_member(_party_id, _user_id) do
case Application.get_env(:game_server_sdk, :stub_mode, :raise) do
:placeholder ->
:ok
_ ->
raise "GameServer.ReadyChecks.remove_party_member/2 is a stub - only available at runtime on GameServer"
end
end
@doc ~S"""
Resets the subject's board: quietly cancels its pending check (no failed
event, no hook — the fresh `ready_check_started` replaces it on clients) and
opens a new one over `user_ids`.
The one verb behind every "answers are stale now" moment: a match ended
(rematch needs a fresh board), the game mode changed, a member joined a
party whose board had already resolved, or the host wants everyone to
re-confirm on a deadline ("force ready"). Same options as `open/3`.
"""
@spec reset(subject(), [Ecto.UUID.t()], keyword()) ::
{:ok, GameServer.ReadyChecks.Check.t()} | {:error, term()}
def reset(_subject, _user_ids, _opts) do
case Application.get_env(:game_server_sdk, :stub_mode, :raise) do
:placeholder ->
{:ok, %GameServer.ReadyChecks.Check{id: "", kind: "ready", status: "pending", lobby_id: nil, deadline: nil, opened_by: nil, reason: nil, resolved_at: nil, metadata: %{}, participants: [], inserted_at: ~U[1970-01-01 00:00:00Z], updated_at: ~U[1970-01-01 00:00:00Z]}}
_ ->
raise "GameServer.ReadyChecks.reset/3 is a stub - only available at runtime on GameServer"
end
end
@doc ~S"""
Records the caller's answer to their open check in `scope` and re-evaluates
it.
`scope` is `:match` (the lobby ready-up or matchmaking accept — the default)
or `:party` (the party's standing board): a player can hold one open check
in each lane, so the answer needs to say which one it is for.
`true` is "ready"/"accept"; `false` is "not ready"/"decline". In an `accept`
check a decline fails the whole check; in a `ready` check it just leaves the
check pending and can be taken back.
Returns the check as it stands after the answer. Fails with
`{:error, :no_open_check}` and, for an `accept` check the caller already
answered, `{:error, :not_revocable}`.
"""
@spec respond(GameServer.Accounts.User.t() | Ecto.UUID.t(), answer(), scope()) ::
{:ok, GameServer.ReadyChecks.Check.t()} | {:error, term()}
def respond(_user, _ready?, _scope) do
case Application.get_env(:game_server_sdk, :stub_mode, :raise) do
:placeholder ->
{:ok, %GameServer.ReadyChecks.Check{id: "", kind: "ready", status: "pending", lobby_id: nil, deadline: nil, opened_by: nil, reason: nil, resolved_at: nil, metadata: %{}, participants: [], inserted_at: ~U[1970-01-01 00:00:00Z], updated_at: ~U[1970-01-01 00:00:00Z]}}
_ ->
raise "GameServer.ReadyChecks.respond/3 is a stub - only available at runtime on GameServer"
end
end
@doc ~S"""
Counts by status over the last `hours` — the accept-rate and dodge-rate
numbers on the admin page.
"""
@spec stats(pos_integer()) :: %{required(String.t()) => non_neg_integer()}
def stats(_hours) do
case Application.get_env(:game_server_sdk, :stub_mode, :raise) do
:placeholder ->
0
_ ->
raise "GameServer.ReadyChecks.stats/1 is a stub - only available at runtime on GameServer"
end
end
end