Skip to content

Commit 42f76e7

Browse files
committed
feature: trophies
1 parent 6b57201 commit 42f76e7

26 files changed

Lines changed: 16560 additions & 7465 deletions

generated/schema.graphql

Lines changed: 2233 additions & 0 deletions
Large diffs are not rendered by default.

generated/schema.ts

Lines changed: 2293 additions & 6 deletions
Large diffs are not rendered by default.

generated/types.ts

Lines changed: 10938 additions & 7438 deletions
Large diffs are not rendered by default.

hasura/functions/leaderboard/get_leaderboard.sql

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,11 @@ BEGIN
2020
ELSIF _category = 'highest_hs_pct' THEN
2121
RETURN QUERY SELECT * FROM _leaderboard_hs_pct(_window_days, _match_type, _exclude_tournaments);
2222

23+
ELSIF _category = 'trophies' THEN
24+
RETURN QUERY SELECT * FROM _leaderboard_trophies(_window_days);
25+
2326
ELSE
24-
RAISE EXCEPTION 'Invalid category: %. Must be one of: elo, best_kdr, best_win_rate, highest_hs_pct', _category;
27+
RAISE EXCEPTION 'Invalid category: %. Must be one of: elo, best_kdr, best_win_rate, highest_hs_pct, trophies', _category;
2528
END IF;
2629
END;
2730
$$;
@@ -332,3 +335,44 @@ BEGIN
332335
ORDER BY value DESC;
333336
END;
334337
$$;
338+
339+
-- ============================================================
340+
-- Trophies leaderboard
341+
-- value = gold count, secondary = silver count, tertiary = bronze count
342+
-- matches_played = total trophies. Olympic medal-table ordering.
343+
-- ============================================================
344+
CREATE OR REPLACE FUNCTION public._leaderboard_trophies(
345+
_window_days INT
346+
)
347+
RETURNS SETOF public.leaderboard_entries
348+
LANGUAGE plpgsql STABLE
349+
AS $$
350+
BEGIN
351+
RETURN QUERY
352+
WITH counts AS (
353+
SELECT
354+
tt.player_steam_id,
355+
SUM(CASE WHEN tt.placement = 0 THEN 1 ELSE 0 END)::int as mvp,
356+
SUM(CASE WHEN tt.placement = 1 THEN 1 ELSE 0 END)::int as gold,
357+
SUM(CASE WHEN tt.placement = 2 THEN 1 ELSE 0 END)::int as silver,
358+
SUM(CASE WHEN tt.placement = 3 THEN 1 ELSE 0 END)::int as bronze,
359+
COUNT(*)::int as total
360+
FROM tournament_trophies tt
361+
WHERE (_window_days = 0 OR tt.tournament_start >= NOW() - make_interval(days => _window_days))
362+
GROUP BY tt.player_steam_id
363+
)
364+
SELECT
365+
c.player_steam_id::text as player_steam_id,
366+
p.name as player_name,
367+
p.avatar_url as player_avatar_url,
368+
p.country as player_country,
369+
c.gold::float as value,
370+
c.silver::float as secondary_value,
371+
c.bronze::float as tertiary_value,
372+
c.mvp as matches_played
373+
FROM counts c
374+
JOIN players p ON p.steam_id = c.player_steam_id
375+
WHERE c.total > 0
376+
ORDER BY c.mvp DESC, c.gold DESC, c.silver DESC, c.bronze DESC;
377+
END;
378+
$$;
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
CREATE OR REPLACE FUNCTION public.calculate_tournament_trophies(_tournament_id uuid)
2+
RETURNS void
3+
LANGUAGE plpgsql
4+
AS $$
5+
DECLARE
6+
_tournament_name text;
7+
_tournament_start timestamptz;
8+
_tournament_type text;
9+
_winning_team_id uuid;
10+
_mvp_steam_id bigint;
11+
BEGIN
12+
DELETE FROM public.tournament_trophies WHERE tournament_id = _tournament_id;
13+
14+
SELECT t.name, t.start INTO _tournament_name, _tournament_start
15+
FROM public.tournaments t WHERE t.id = _tournament_id;
16+
17+
SELECT type INTO _tournament_type
18+
FROM public.tournament_stages
19+
WHERE tournament_id = _tournament_id
20+
ORDER BY "order" ASC
21+
LIMIT 1;
22+
23+
-- 1st / 2nd / 3rd placements. Tie for 3rd => no bronze.
24+
-- Copy any organizer-set visual config (custom_name/silhouette/image_url)
25+
-- onto each trophy row so profile views render without joining configs.
26+
INSERT INTO public.tournament_trophies
27+
(tournament_id, tournament_team_id, player_steam_id, placement,
28+
tournament_name, tournament_start, tournament_type,
29+
custom_name, silhouette, image_url)
30+
WITH ranked AS (
31+
SELECT
32+
tournament_team_id,
33+
RANK() OVER (
34+
ORDER BY wins DESC,
35+
head_to_head_match_wins DESC,
36+
head_to_head_rounds_won DESC,
37+
CASE WHEN maps_lost > 0
38+
THEN maps_won::float / maps_lost::float
39+
ELSE maps_won::float END DESC,
40+
CASE WHEN rounds_lost > 0
41+
THEN rounds_won::float / rounds_lost::float
42+
ELSE rounds_won::float END DESC,
43+
team_kdr DESC
44+
) AS placement
45+
FROM public.v_team_tournament_results
46+
WHERE tournament_id = _tournament_id
47+
),
48+
tie_counts AS (
49+
SELECT placement, COUNT(*) AS team_count
50+
FROM ranked
51+
GROUP BY placement
52+
)
53+
SELECT
54+
_tournament_id,
55+
r.tournament_team_id,
56+
roster.player_steam_id,
57+
r.placement,
58+
_tournament_name,
59+
_tournament_start,
60+
_tournament_type,
61+
cfg.custom_name,
62+
cfg.silhouette,
63+
cfg.image_url
64+
FROM ranked r
65+
JOIN tie_counts tc USING (placement)
66+
JOIN public.tournament_team_roster roster
67+
ON roster.tournament_team_id = r.tournament_team_id
68+
AND roster.tournament_id = _tournament_id
69+
LEFT JOIN public.tournament_trophy_configs cfg
70+
ON cfg.tournament_id = _tournament_id
71+
AND cfg.placement = r.placement
72+
WHERE r.placement <= 3
73+
AND NOT (r.placement = 3 AND tc.team_count > 1);
74+
75+
-- MVP: highest avg ELO impact on the WINNING team across all tournament matches.
76+
SELECT tournament_team_id INTO _winning_team_id
77+
FROM public.tournament_trophies
78+
WHERE tournament_id = _tournament_id AND placement = 1
79+
LIMIT 1;
80+
81+
IF _winning_team_id IS NOT NULL THEN
82+
WITH t_matches AS (
83+
SELECT DISTINCT tb.match_id
84+
FROM public.tournament_brackets tb
85+
JOIN public.tournament_stages ts ON ts.id = tb.tournament_stage_id
86+
WHERE ts.tournament_id = _tournament_id
87+
AND tb.match_id IS NOT NULL
88+
),
89+
elo_impact AS (
90+
SELECT
91+
pe.steam_id,
92+
AVG(pe.change)::float AS avg_change,
93+
SUM(pe.change)::float AS total_change,
94+
COUNT(*)::int AS matches
95+
FROM public.player_elo pe
96+
WHERE pe.match_id IN (SELECT match_id FROM t_matches)
97+
GROUP BY pe.steam_id
98+
)
99+
SELECT ei.steam_id INTO _mvp_steam_id
100+
FROM elo_impact ei
101+
JOIN public.tournament_team_roster roster
102+
ON roster.player_steam_id = ei.steam_id
103+
AND roster.tournament_id = _tournament_id
104+
AND roster.tournament_team_id = _winning_team_id
105+
WHERE ei.matches > 0
106+
ORDER BY ei.avg_change DESC,
107+
ei.total_change DESC,
108+
ei.steam_id ASC
109+
LIMIT 1;
110+
111+
IF _mvp_steam_id IS NOT NULL THEN
112+
INSERT INTO public.tournament_trophies
113+
(tournament_id, tournament_team_id, player_steam_id, placement,
114+
tournament_name, tournament_start, tournament_type,
115+
custom_name, silhouette, image_url)
116+
SELECT
117+
_tournament_id,
118+
_winning_team_id,
119+
_mvp_steam_id,
120+
0,
121+
_tournament_name,
122+
_tournament_start,
123+
_tournament_type,
124+
cfg.custom_name,
125+
cfg.silhouette,
126+
cfg.image_url
127+
FROM (SELECT 1) dummy
128+
LEFT JOIN public.tournament_trophy_configs cfg
129+
ON cfg.tournament_id = _tournament_id
130+
AND cfg.placement = 0;
131+
END IF;
132+
END IF;
133+
END;
134+
$$;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
CREATE OR REPLACE FUNCTION public.recalculate_tournament_trophies(_tournament_id uuid)
2+
RETURNS SETOF public.tournament_trophies
3+
LANGUAGE plpgsql
4+
AS $$
5+
BEGIN
6+
PERFORM public.calculate_tournament_trophies(_tournament_id);
7+
RETURN QUERY SELECT * FROM public.tournament_trophies WHERE tournament_id = _tournament_id;
8+
END;
9+
$$;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
- "!include public_get_leaderboard.yaml"
2+
- "!include public_recalculate_tournament_trophies.yaml"
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
function:
2+
name: recalculate_tournament_trophies
3+
schema: public
4+
configuration:
5+
custom_root_fields: {}
6+
exposed_as: mutation
7+
permissions:
8+
- role: administrator

hasura/metadata/databases/default/tables/public_players.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,13 @@ array_relationships:
200200
table:
201201
name: tournament_team_roster
202202
schema: public
203+
- name: tournament_trophies
204+
using:
205+
foreign_key_constraint_on:
206+
column: player_steam_id
207+
table:
208+
name: tournament_trophies
209+
schema: public
203210
- name: tournaments
204211
using:
205212
foreign_key_constraint_on:
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
table:
2+
name: tournament_trophies
3+
schema: public
4+
object_relationships:
5+
- name: tournament
6+
using:
7+
foreign_key_constraint_on: tournament_id
8+
- name: tournament_team
9+
using:
10+
foreign_key_constraint_on: tournament_team_id
11+
- name: player
12+
using:
13+
foreign_key_constraint_on: player_steam_id
14+
select_permissions:
15+
- role: guest
16+
permission:
17+
columns:
18+
- id
19+
- tournament_id
20+
- tournament_team_id
21+
- player_steam_id
22+
- placement
23+
- placement_tier
24+
- tournament_name
25+
- tournament_start
26+
- tournament_type
27+
- custom_name
28+
- silhouette
29+
- image_url
30+
- created_at
31+
filter: {}
32+
allow_aggregations: true
33+
comment: ""
34+
- role: user
35+
permission:
36+
columns:
37+
- id
38+
- tournament_id
39+
- tournament_team_id
40+
- player_steam_id
41+
- placement
42+
- placement_tier
43+
- tournament_name
44+
- tournament_start
45+
- tournament_type
46+
- custom_name
47+
- silhouette
48+
- image_url
49+
- created_at
50+
filter: {}
51+
allow_aggregations: true
52+
comment: ""
53+
- role: match_organizer
54+
permission:
55+
columns:
56+
- id
57+
- tournament_id
58+
- tournament_team_id
59+
- player_steam_id
60+
- placement
61+
- placement_tier
62+
- tournament_name
63+
- tournament_start
64+
- tournament_type
65+
- custom_name
66+
- silhouette
67+
- image_url
68+
- created_at
69+
filter: {}
70+
allow_aggregations: true
71+
comment: ""
72+
- role: tournament_organizer
73+
permission:
74+
columns:
75+
- id
76+
- tournament_id
77+
- tournament_team_id
78+
- player_steam_id
79+
- placement
80+
- placement_tier
81+
- tournament_name
82+
- tournament_start
83+
- tournament_type
84+
- custom_name
85+
- silhouette
86+
- image_url
87+
- created_at
88+
filter: {}
89+
allow_aggregations: true
90+
comment: ""

0 commit comments

Comments
 (0)