Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5,491 changes: 5,479 additions & 12 deletions generated/schema.graphql

Large diffs are not rendered by default.

29,743 changes: 17,419 additions & 12,324 deletions generated/schema.ts

Large diffs are not rendered by default.

112,937 changes: 60,186 additions & 52,751 deletions generated/types.ts

Large diffs are not rendered by default.

48 changes: 48 additions & 0 deletions hasura/enums/game-modes.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
-- Starter modes, seeded on every boot so a fresh install has something to pick
-- besides "Competitive". Enum-style upsert: the name and description follow the
-- ship, but enabled / competitive_safe / cfg are left alone once an operator has
-- touched them, and anything they add of their own is untouched.
--
-- Runtime compatibility is NOT declared here. It is derived from the plugins
-- each mode selects, so a mode whose plugin has no build for this deployment
-- reports that by name rather than booting a server with nothing loaded.
insert into game_modes (slug, name, description, competitive_safe, enabled, cfg)
values
(
'retakes',
'Retakes',
'Bombsite retakes: the bomb is planted, T''s defend, CT''s retake. Fast rounds, no buy time.',
false,
true,
'mp_maxrounds 0' || chr(10) ||
'mp_freezetime 3' || chr(10) ||
'mp_round_restart_delay 3' || chr(10) ||
'mp_ignore_round_win_conditions 1' || chr(10) ||
'mp_respawn_on_death_ct 0' || chr(10) ||
'mp_respawn_on_death_t 0'
),
(
'deathmatch',
'Deathmatch',
'Free-for-all warmup with instant respawns and a weapon menu.',
false,
true,
'mp_maxrounds 0' || chr(10) ||
'mp_freezetime 0' || chr(10) ||
'mp_respawn_immunitytime 2' || chr(10) ||
'mp_ignore_round_win_conditions 1' || chr(10) ||
'mp_teammates_are_enemies 1'
)
on conflict (slug) do update set
name = excluded.name,
description = excluded.description;

-- Wire each starter mode to its plugin, but only once that plugin is in the
-- catalog: the registry syncs on its own schedule, so on a first boot these
-- modes exist with no plugins and pick them up on a later pass.
insert into game_mode_plugins (game_mode_id, plugin_slug, load_order)
select m.id, p.slug, 0
from game_modes m
join game_plugins p on p.slug = m.slug
where m.slug in ('retakes', 'deathmatch')
on conflict (game_mode_id, plugin_slug) do nothing;
4 changes: 4 additions & 0 deletions hasura/enums/game-plugin-channels.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
insert into e_game_plugin_channels ("value", "description") values
('Pinned', 'Stay on the installed version; a newer release only raises a notification'),
('Auto', 'Install new upstream releases automatically')
on conflict(value) do update set "description" = EXCLUDED."description"
7 changes: 7 additions & 0 deletions hasura/enums/game-plugin-install-statuses.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
insert into e_game_plugin_install_statuses ("value", "description") values
('Pending', 'Queued for install on the node'),
('Installing', 'Downloading and unpacking into the node plugin store'),
('Installed', 'Present in the node plugin store and ready to be selected by a mode'),
('Failed', 'Install did not complete; see the recorded error'),
('Removing', 'Being deleted from the node plugin store')
on conflict(value) do update set "description" = EXCLUDED."description"
5 changes: 5 additions & 0 deletions hasura/enums/game-plugin-kinds.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
insert into e_game_plugin_kinds ("value", "description") values
('game', 'A CS2 server plugin that loads into the game server'),
('panel', 'A web app that mounts as a page inside the panel'),
('bundle', 'A panel plugin and a game plugin installed and wired together')
on conflict(value) do update set "description" = EXCLUDED."description"
85 changes: 85 additions & 0 deletions hasura/functions/game-plugins/install_state.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
-- How far a requested plugin has actually got. Nodes converge on their own
-- schedule, so "installed" is never a single boolean: it is a count of the
-- nodes that have it against the nodes that should.
--
-- Every count below joins game_server_nodes and filters the same way as
-- game_plugin_target_node_count. Counting rows for nodes the target excludes is
-- what let a plugin present only on disabled nodes report Installed, and let a
-- Failed row on a decommissioned node pin the plugin at Failed forever --
-- disabling a node does not delete what it reported.
CREATE OR REPLACE FUNCTION public.game_plugin_installed_node_count(
plugin public.game_plugins
) RETURNS integer AS $$
SELECT count(*)::integer
FROM public.game_server_node_plugins n
INNER JOIN public.game_server_nodes g ON g.id = n.game_server_node_id
WHERE n.plugin_slug = plugin.slug
AND n.source = 'managed'
AND n.detected = true
AND g.enabled = true
AND g.status IN ('Online', 'NotAcceptingNewMatches');
$$ LANGUAGE sql STABLE;

CREATE OR REPLACE FUNCTION public.game_plugin_target_node_count(
plugin public.game_plugins
) RETURNS integer AS $$
SELECT count(*)::integer
FROM public.game_server_nodes
WHERE enabled = true
AND status IN ('Online', 'NotAcceptingNewMatches');
$$ LANGUAGE sql STABLE;

CREATE OR REPLACE FUNCTION public.game_plugin_install_state(
plugin public.game_plugins
) RETURNS text AS $$
DECLARE
_requested boolean;
_installed integer;
_target integer;
_failed integer;
_manual boolean;
BEGIN
SELECT EXISTS (
SELECT 1 FROM public.game_plugin_installs
WHERE plugin_slug = plugin.slug AND enabled = true
) INTO _requested;

SELECT count(*) FILTER (WHERE n.source = 'managed' AND n.detected = true),
count(*) FILTER (WHERE n.status = 'Failed'),
bool_or(n.source = 'manual')
INTO _installed, _failed, _manual
FROM public.game_server_node_plugins n
INNER JOIN public.game_server_nodes g ON g.id = n.game_server_node_id
WHERE n.plugin_slug = plugin.slug
AND g.enabled = true
AND g.status IN ('Online', 'NotAcceptingNewMatches');

IF NOT _requested THEN
-- Present without being asked for: dropped in by hand. Reported rather
-- than hidden, because it loads on every server regardless of mode.
IF _installed > 0 OR COALESCE(_manual, false) THEN
RETURN 'Manual';
END IF;

RETURN 'NotInstalled';
END IF;

SELECT count(*) INTO _target
FROM public.game_server_nodes
WHERE enabled = true AND status IN ('Online', 'NotAcceptingNewMatches');

IF _failed > 0 THEN
RETURN 'Failed';
END IF;

IF _target = 0 OR _installed = 0 THEN
RETURN 'Pending';
END IF;

IF _installed >= _target THEN
RETURN 'Installed';
END IF;

RETURN 'Partial';
END;
$$ LANGUAGE plpgsql STABLE;
59 changes: 59 additions & 0 deletions hasura/functions/game-plugins/mode_runtimes.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
-- These returned text[] before. CREATE OR REPLACE cannot change a function's
-- return type, so any database that already has the old signature needs it
-- dropped first -- this file is re-applied on every boot.
DROP FUNCTION IF EXISTS public.game_mode_supported_runtimes(public.game_modes);
DROP FUNCTION IF EXISTS public.game_mode_runtime_conflicts(public.game_modes);

-- Which frameworks a mode can actually run on: the intersection of what every
-- plugin in it publishes for. An empty result means the selection is impossible
-- -- two plugins that exist for different frameworks and can never load together.
-- jsonb, not text[]: Hasura only accepts a base type as a computed field's
-- return, and an array type is not one. It arrives in GraphQL as a JSON array
-- either way.
CREATE OR REPLACE FUNCTION public.game_mode_supported_runtimes(mode public.game_modes)
RETURNS jsonb
LANGUAGE sql
STABLE
AS $$
SELECT to_jsonb(CASE
-- A mode with no plugins is just cvars and a map pool, so it runs anywhere.
WHEN NOT EXISTS (
SELECT 1 FROM public.game_mode_plugins WHERE game_mode_id = mode.id
)
THEN ARRAY(SELECT value FROM public.e_plugin_runtimes ORDER BY value)
ELSE ARRAY(
SELECT r.value
FROM public.e_plugin_runtimes r
WHERE NOT EXISTS (
SELECT 1
FROM public.game_mode_plugins mp
WHERE mp.game_mode_id = mode.id
AND NOT EXISTS (
SELECT 1
FROM public.game_plugin_versions v
WHERE v.plugin_slug = mp.plugin_slug
AND v.runtime = r.value
)
)
ORDER BY r.value
)
END);
$$;

-- Named so the panel can say which plugin is the odd one out rather than only
-- that the combination does not work.
CREATE OR REPLACE FUNCTION public.game_mode_runtime_conflicts(mode public.game_modes)
RETURNS jsonb
LANGUAGE sql
STABLE
AS $$
SELECT to_jsonb(COALESCE(array_agg(mp.plugin_slug ORDER BY mp.plugin_slug), ARRAY[]::text[]))
FROM public.game_mode_plugins mp
WHERE mp.game_mode_id = mode.id
AND NOT EXISTS (
SELECT 1
FROM public.game_plugin_versions v
WHERE v.plugin_slug = mp.plugin_slug
AND v.runtime = public.active_plugin_runtime()
);
$$;
9 changes: 9 additions & 0 deletions hasura/functions/leaderboard/get_leaderboard.sql
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,7 @@ BEGIN
JOIN match_options mo ON mo.id = m.match_options_id
WHERE m.status = 'Finished'
AND m.source = '5stack'
AND m.counts_toward_ranking = true
AND mlp.steam_id IS NOT NULL
AND m.winning_lineup_id IS NOT NULL
AND ((_from IS NULL OR m.ended_at >= _from) AND (_to IS NULL OR m.ended_at < _to))
Expand Down Expand Up @@ -424,6 +425,7 @@ BEGIN
JOIN match_options mo ON mo.id = m.match_options_id
WHERE m.status = 'Finished'
AND m.source = '5stack'
AND m.counts_toward_ranking = true
AND mlp.steam_id IS NOT NULL
AND m.winning_lineup_id IS NOT NULL
AND ((_from IS NULL OR m.ended_at >= _from) AND (_to IS NULL OR m.ended_at < _to))
Expand Down Expand Up @@ -534,6 +536,7 @@ BEGIN
JOIN match_options mo ON mo.id = m.match_options_id
WHERE m.status = 'Finished'
AND m.source = '5stack'
AND m.counts_toward_ranking = true
AND mlp.steam_id IS NOT NULL
AND m.winning_lineup_id IS NOT NULL
AND ((_from IS NULL OR m.ended_at >= _from) AND (_to IS NULL OR m.ended_at < _to))
Expand Down Expand Up @@ -619,6 +622,7 @@ BEGIN
WHERE pk.attacker_steam_id IS NOT NULL
AND pk.attacker_steam_id != pk.attacked_steam_id
AND m.source = '5stack'
AND m.counts_toward_ranking = true
AND ((_from IS NULL OR pk.time >= _from) AND (_to IS NULL OR pk.time < _to))
AND (_match_type IS NULL OR mo.type = _match_type)
AND (NOT _exclude_tournaments OR NOT EXISTS (SELECT 1 FROM tournament_brackets tb WHERE tb.match_id = pk.match_id))
Expand All @@ -634,6 +638,7 @@ BEGIN
JOIN match_options mo2 ON mo2.id = m2.match_options_id
WHERE 1=1
AND m2.source = '5stack'
AND m2.counts_toward_ranking = true
AND ((_from IS NULL OR dk.time >= _from) AND (_to IS NULL OR dk.time < _to))
AND (_match_type IS NULL OR mo2.type = _match_type)
AND (NOT _exclude_tournaments OR NOT EXISTS (SELECT 1 FROM tournament_brackets tb WHERE tb.match_id = dk.match_id))
Expand Down Expand Up @@ -703,6 +708,7 @@ BEGIN
JOIN match_options mo ON mo.id = m.match_options_id
WHERE m.status = 'Finished'
AND m.source = '5stack'
AND m.counts_toward_ranking = true
AND mlp.steam_id IS NOT NULL
AND m.winning_lineup_id IS NOT NULL
AND ((_from IS NULL OR m.ended_at >= _from) AND (_to IS NULL OR m.ended_at < _to))
Expand Down Expand Up @@ -776,6 +782,7 @@ BEGIN
WHERE pk.attacker_steam_id IS NOT NULL
AND pk.attacker_steam_id != pk.attacked_steam_id
AND m.source = '5stack'
AND m.counts_toward_ranking = true
AND ((_from IS NULL OR pk.time >= _from) AND (_to IS NULL OR pk.time < _to))
AND (_match_type IS NULL OR mo.type = _match_type)
AND (NOT _exclude_tournaments OR NOT EXISTS (SELECT 1 FROM tournament_brackets tb WHERE tb.match_id = pk.match_id))
Expand Down Expand Up @@ -1002,6 +1009,7 @@ BEGIN
AND r.match_map_id = h.match_map_id
AND r.steam_id = h.steam_id
WHERE m.source = '5stack'
AND m.counts_toward_ranking = true
AND ((_from IS NULL OR m.created_at >= _from) AND (_to IS NULL OR m.created_at < _to))
AND (_match_type IS NULL OR mo.type = _match_type)
AND (NOT _exclude_tournaments OR NOT EXISTS (SELECT 1 FROM tournament_brackets tb WHERE tb.match_id = h.match_id))
Expand Down Expand Up @@ -1084,6 +1092,7 @@ BEGIN
AND r.match_map_id = s.match_map_id
AND r.steam_id = s.steam_id
WHERE m.source = '5stack'
AND m.counts_toward_ranking = true
AND ((_from IS NULL OR m.created_at >= _from) AND (_to IS NULL OR m.created_at < _to))
AND (_match_type IS NULL OR mo.type = _match_type)
AND (NOT _exclude_tournaments OR NOT EXISTS (SELECT 1 FROM tournament_brackets tb WHERE tb.match_id = s.match_id))
Expand Down
8 changes: 8 additions & 0 deletions hasura/functions/match/match_player_elo.sql
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,14 @@ BEGIN
RETURN 0;
END IF;

-- Played under a game mode that is not marked safe for competitive play.
-- The match is real -- stats, demos and rounds are all recorded -- it just
-- does not move anybody's rating. Decided when the match was created, so
-- editing the mode later cannot rewrite history.
IF match_record.counts_toward_ranking = false THEN
RETURN 0;
END IF;

-- Skip matches without a winning_lineup_id
IF match_record.winning_lineup_id IS NULL THEN
RAISE NOTICE 'Skipping match % as it has no winning_lineup_id', _match_id;
Expand Down
86 changes: 86 additions & 0 deletions hasura/metadata/actions.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -1253,6 +1253,13 @@ type TelemetryFleetTotals {
scrimRequests: Int!
events: Int!
eventTeams: Int!
pluginsReported: Int!
pluginsRequested: Int!
pluginsManual: Int!
gameModes: Int!
gameModesEnabled: Int!
gameModesUnranked: Int!
pluginsBySlug: jsonb
}

type TelemetryFeatureAdoption {
Expand Down Expand Up @@ -1823,3 +1830,82 @@ type TournamentAward {
silhouette: Int
image_url: String
}

type SyncPluginRegistryOutput {
plugins: Int!
versions: Int!
}

type ReconcileNodePluginsOutput {
detected: Int!
}

type PreviewGameModeOutput {
enabledPlugins: String!
cfg: String
extraGameParams: String
}

type Mutation {
syncPluginRegistry: SyncPluginRegistryOutput
}

type Mutation {
installGamePlugin(
slug: String!
version: String
): SuccessOutput
}

type Mutation {
uninstallGamePlugin(
slug: String!
force: Boolean
): SuccessOutput
}

type Mutation {
reconcileNodePlugins(
nodeId: String!
): ReconcileNodePluginsOutput
}

type AddCustomGamePluginOutput {
slug: String!
name: String!
version: String!
runtime: String!
}

type Mutation {
addCustomGamePlugin(
url: String!
runtime: String!
slug: String
name: String
description: String
version: String
layout: String
installPath: String
): AddCustomGamePluginOutput
}

type Mutation {
previewGameMode(
gameModeId: uuid!
): PreviewGameModeOutput
}

type PluginReadmeOutput {
repo: String
content: String
format: String
url: String
}

type Mutation {
getPluginReadme(
slug: String!
runtime: String
): PluginReadmeOutput
}
Loading
Loading