Skip to content

Commit 6d8e7ab

Browse files
authored
feature: trophies system update to handle giving to teams / players better (#289)
1 parent 959f716 commit 6d8e7ab

15 files changed

Lines changed: 1366 additions & 556 deletions

File tree

components/EloChangeBadge.vue

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ interface EloChange {
1717
expected_score?: number | string | null;
1818
actual_score?: number | string | null;
1919
k_factor?: number | string | null;
20+
impact?: number | string | null;
2021
performance_multiplier?: number | string | null;
2122
series_multiplier?: number | string | null;
2223
map_wins?: number | string | null;
@@ -55,6 +56,8 @@ const seriesMult = computed(() => toNum(props.eloChange?.series_multiplier));
5556
const hasSeriesBonus = computed(() => seriesMult.value > 1);
5657
5758
const perfMult = computed(() => toNum(props.eloChange?.performance_multiplier));
59+
const impact = computed(() => toNum(props.eloChange?.impact) || perfMult.value);
60+
const impactDelta = computed(() => Math.round((impact.value - 1) * 100));
5861
const expectedScore = computed(() => toNum(props.eloChange?.expected_score));
5962
const actualScore = computed(() => toNum(props.eloChange?.actual_score));
6063
const kFactor = computed(() => toNum(props.eloChange?.k_factor) || 500);
@@ -91,6 +94,11 @@ function formatSigned(n: number): string {
9194
return `${sign}${n.toLocaleString()}`;
9295
}
9396
97+
function formatSignedPercent(n: number): string {
98+
const sign = n > 0 ? "+" : "";
99+
return `${sign}${n}%`;
100+
}
101+
94102
// shared building blocks
95103
const badgeBase =
96104
"inline-flex items-center gap-[3px] font-['Oxanium',sans-serif] font-bold tracking-[0.04em] tabular-nums rounded-[3px] border leading-none whitespace-nowrap cursor-help transition-all duration-150 hover:-translate-y-px";
@@ -383,6 +391,40 @@ const chipClipSm =
383391
</span>
384392
</span>
385393
</div>
394+
<div class="flex flex-col gap-0.5 px-2 py-1.5 bg-card/80">
395+
<span :class="labelClass">SCORE</span>
396+
<span
397+
:class="[monoNum, 'font-semibold text-[12px] text-foreground']"
398+
>
399+
{{ actualScore.toFixed(2) }}
400+
<span
401+
class="text-[10px] text-muted-foreground font-normal ml-0.5"
402+
>
403+
· EXP {{ expectedScore.toFixed(2) }}
404+
</span>
405+
</span>
406+
</div>
407+
<div class="flex flex-col gap-0.5 px-2 py-1.5 bg-card/80">
408+
<span :class="labelClass">IMPACT</span>
409+
<span
410+
:class="[
411+
monoNum,
412+
'font-semibold text-[12px]',
413+
impact > 1
414+
? 'text-[hsl(142_71%_60%)]'
415+
: impact < 1
416+
? 'text-[hsl(0_84%_66%)]'
417+
: 'text-foreground',
418+
]"
419+
>
420+
{{ impact.toFixed(2) }}×
421+
<span
422+
class="text-[10px] text-muted-foreground font-normal ml-0.5"
423+
>
424+
{{ formatSignedPercent(impactDelta) }}
425+
</span>
426+
</span>
427+
</div>
386428
<div class="flex flex-col gap-0.5 px-2 py-1.5 bg-card/80">
387429
<span :class="labelClass">PERF ×</span>
388430
<span

components/PlayerDisplay.vue

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ import TrophyCountBadge from "~/components/trophy/TrophyCountBadge.vue";
220220
<script lang="ts">
221221
import { e_player_roles_enum } from "~/generated/zeus";
222222
import { typedGql } from "~/generated/zeus/typedDocumentNode";
223+
import { resolveAvatarUrl } from "~/utilities/avatarUrl";
223224
224225
export default {
225226
props: {
@@ -306,9 +307,15 @@ export default {
306307
me() {
307308
return useAuthStore().me;
308309
},
310+
apiDomain() {
311+
return useRuntimeConfig().public.apiDomain;
312+
},
309313
playerAvatarSrc() {
310314
if (!this.player) return null;
311-
return this.player.custom_avatar_url || this.player.avatar_url || null;
315+
return resolveAvatarUrl(
316+
this.player.custom_avatar_url || this.player.avatar_url,
317+
this.apiDomain,
318+
);
312319
},
313320
isMe() {
314321
if (!this.player) {

components/tournament/TournamentResults.vue

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,19 @@ import {
1717
HoverCardTrigger,
1818
} from "~/components/ui/hover-card";
1919
import { ChevronRight } from "lucide-vue-next";
20+
import { resolveAvatarUrl } from "~/utilities/avatarUrl";
21+
22+
const apiDomain = useRuntimeConfig().public.apiDomain;
23+
24+
function playerAvatarSrc(player: {
25+
custom_avatar_url?: string | null;
26+
avatar_url?: string | null;
27+
}) {
28+
return resolveAvatarUrl(
29+
player.custom_avatar_url || player.avatar_url,
30+
apiDomain,
31+
);
32+
}
2033
</script>
2134

2235
<template>
@@ -143,8 +156,8 @@ import { ChevronRight } from "lucide-vue-next";
143156
:title="p.name"
144157
>
145158
<img
146-
v-if="p.custom_avatar_url || p.avatar_url"
147-
:src="p.custom_avatar_url || p.avatar_url"
159+
v-if="playerAvatarSrc(p)"
160+
:src="playerAvatarSrc(p)"
148161
:alt="p.name"
149162
class="h-full w-full object-cover"
150163
/>
@@ -280,7 +293,7 @@ import { ChevronRight } from "lucide-vue-next";
280293
:placement="0"
281294
:tournament-name="tournament.name"
282295
:tournament-start="tournament.start"
283-
:tournament-type="mvp.tournament_type"
296+
:tournament-type="finalStageType"
284297
:custom-name="trophyConfigFor(0)?.custom_name"
285298
:silhouette-override="trophyConfigFor(0)?.silhouette"
286299
:image-url="trophyConfigFor(0)?.image_url"
@@ -774,6 +787,10 @@ export default {
774787
isLive() {
775788
return (this.tournament as any)?.status === e_tournament_status_enum.Live;
776789
},
790+
finalStageType() {
791+
const stages = (this.tournament as any)?.stages || [];
792+
return stages.length ? stages[stages.length - 1]?.type || null : null;
793+
},
777794
podium() {
778795
const trophies = (this.tournament as any)?.trophies || [];
779796
if (trophies.length === 0) return [];
@@ -807,7 +824,7 @@ export default {
807824
t.tournament_team,
808825
t.tournament_team_id,
809826
),
810-
tournamentType: t.tournament_type,
827+
tournamentType: this.finalStageType,
811828
players,
812829
});
813830
}

components/tournament/TournamentTrophiesConfig.vue

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import TrophyBadge from "~/components/trophy/TrophyBadge.vue";
33
import AvatarUpload from "~/components/AvatarUpload.vue";
44
import { Input } from "~/components/ui/input";
55
import { Button } from "~/components/ui/button";
6+
import { Switch } from "~/components/ui/switch";
67
import { typedGql } from "~/generated/zeus/typedDocumentNode";
78
import { $ } from "~/generated/zeus";
89
@@ -19,7 +20,7 @@ const FRAME_CLASSES =
1920
"relative overflow-hidden rounded-lg border border-border px-6 py-6 [background:linear-gradient(180deg,hsl(var(--card)_/_0.7)_0%,hsl(var(--card)_/_0.35)_100%)] [backdrop-filter:blur(6px)] before:pointer-events-none before:absolute before:left-2 before:top-2 before:h-[14px] before:w-[14px] before:border-l-2 before:border-t-2 before:border-[hsl(var(--tac-amber))] before:content-[''] after:pointer-events-none after:absolute after:bottom-2 after:right-2 after:h-[14px] after:w-[14px] after:border-b-2 after:border-r-2 after:border-[hsl(var(--tac-amber))] after:content-['']";
2021
2122
export default {
22-
components: { TrophyBadge, AvatarUpload, Input, Button },
23+
components: { TrophyBadge, AvatarUpload, Input, Button, Switch },
2324
props: {
2425
tournament: {
2526
type: Object,
@@ -41,6 +42,7 @@ export default {
4142
number,
4243
boolean
4344
>,
45+
savingEnabled: false,
4446
};
4547
},
4648
computed: {
@@ -177,6 +179,27 @@ export default {
177179
this.drafts[placement] = { custom_name: "", silhouette: null };
178180
this.save(placement);
179181
},
182+
async toggleEnabled(next: boolean) {
183+
this.savingEnabled = true;
184+
try {
185+
await this.$apollo.mutate({
186+
mutation: typedGql("mutation")({
187+
update_tournaments_by_pk: [
188+
{
189+
pk_columns: { id: $("id", "uuid!") },
190+
_set: { trophies_enabled: $("trophies_enabled", "Boolean!") },
191+
},
192+
{ id: true, trophies_enabled: true },
193+
],
194+
}),
195+
variables: { id: this.tournament.id, trophies_enabled: next },
196+
});
197+
} catch (err) {
198+
console.error("Failed to toggle trophies_enabled", err);
199+
} finally {
200+
this.savingEnabled = false;
201+
}
202+
},
180203
},
181204
};
182205
</script>
@@ -207,7 +230,31 @@ export default {
207230
ORGANIZER ACCESS REQUIRED
208231
</div>
209232

210-
<div v-else class="grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4">
233+
<template v-else>
234+
<div
235+
class="mb-4 flex items-center justify-between rounded-sm border border-border/60 bg-background/40 px-4 py-3"
236+
>
237+
<div class="flex flex-col gap-1">
238+
<span
239+
class="font-mono text-[0.7rem] font-semibold uppercase tracking-[0.2em]"
240+
>
241+
AWARD TROPHIES
242+
</span>
243+
<span
244+
class="font-mono text-[0.6rem] uppercase tracking-[0.2em] text-muted-foreground"
245+
>
246+
Disable to skip / clear auto-awarded trophies for this tournament.
247+
Manual awards stay.
248+
</span>
249+
</div>
250+
<Switch
251+
:model-value="tournament.trophies_enabled !== false"
252+
:disabled="savingEnabled"
253+
@update:model-value="toggleEnabled"
254+
/>
255+
</div>
256+
257+
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4">
211258
<div
212259
v-for="p in placements"
213260
:key="p"
@@ -352,5 +399,6 @@ export default {
352399
</div>
353400
</div>
354401
</div>
402+
</template>
355403
</section>
356404
</template>

0 commit comments

Comments
 (0)