From 02c03e3751ff9c810f3635ca3eb93df18d55a64f Mon Sep 17 00:00:00 2001 From: Luna Date: Sun, 15 Jun 2025 03:30:58 +0200 Subject: [PATCH] todo: QUAL LEADERBAORDS AND SORTING --- src/lib/components/popups/EditMap.svelte | 76 +- src/lib/services/beatsaver.ts | 22 +- src/lib/stores.js | 2 + src/routes/+layout.svelte | 8 +- src/routes/tournaments/+page.svelte | 24 +- .../tournaments/[tournamentguid]/+page.svelte | 63 +- .../mappools/[poolGuid]/+page.svelte | 40 +- .../matches/[matchGuid]/+page.svelte | 10 +- .../[tournamentguid]/qualifiers/+page.svelte | 1100 ++++++++++++++ .../[tournamentguid]/qualifiers/+page.ts | 6 + .../qualifiers/[qualifierGuid]/+page.svelte | 1331 +++++++++++++++++ .../qualifiers/[qualifierGuid]/+page.ts | 7 + 12 files changed, 2581 insertions(+), 108 deletions(-) create mode 100644 src/routes/tournaments/[tournamentguid]/qualifiers/+page.svelte create mode 100644 src/routes/tournaments/[tournamentguid]/qualifiers/+page.ts create mode 100644 src/routes/tournaments/[tournamentguid]/qualifiers/[qualifierGuid]/+page.svelte create mode 100644 src/routes/tournaments/[tournamentguid]/qualifiers/[qualifierGuid]/+page.ts diff --git a/src/lib/components/popups/EditMap.svelte b/src/lib/components/popups/EditMap.svelte index 2d2833c..bd65ba6 100644 --- a/src/lib/components/popups/EditMap.svelte +++ b/src/lib/components/popups/EditMap.svelte @@ -5,6 +5,7 @@ import { TAServerPort, TAServerUrl, authTokenStore, client } from "$lib/stores"; import { v4 as uuidv4 } from "uuid"; import { onMount } from "svelte"; + import type { BeatSaverMap } from "$lib/services/beatsaver"; export let tournamentGuid: string; export let poolGuid: string; @@ -53,33 +54,6 @@ // Playing-specific settings let disableCustomNotesOnStream = false; - - type BeatSaverMap = { - id: string; - metadata: { - songName: string; - songAuthorName: string; - levelAuthorName: string; - songSubName: string; - duration: number; - bpm: number; - }; - versions: Array<{ - hash: string; - state: string; - createdAt: string; - downloadURL: string; - diffs: Array<{ - characteristic: string; - difficulty: string; - maxScore: number; - nps: number; - }>; - coverURL: string; - previewURL: string; - }>; - uploaded: string; - }; // Beat Saber Modifiers with incompatibility groups interface Modifier { @@ -433,7 +407,7 @@ selectedCharacteristic = availableCharacteristics[0] || ""; selectedDifficulty = availableDifficulties[selectedCharacteristic]?.[0] || ""; - mapBeatmapId = `custom_level_${latestVersion.hash}`; + mapBeatmapId = `custom_level_${latestVersion.hash.toLocaleUpperCase()}`; return data; } catch (err) { @@ -510,20 +484,25 @@ disableScoresaberSubmission: mode === 'playing' ? disableScoresaberSubmission : false, showScoreboard: mode === 'playing' ? showScoreboard : false, useSync: false, - target: 0 + target: 0, + playerSettings: { + options: 0, + playerHeight: 0, + sfxVolume: 0, + saberTrailIntensity: 0, + noteJumpStartBeatOffset: 0, + noteJumpFixedDuration: 0, + noteJumpDurationTypeSettings: 0, + arcVisibilityType: 0, + } } }; if (map) { - // I will need to fix this, for now this is a TA server issue dispatch('mapUpdated', { map: mapData }); } else { - await client.addTournamentPoolMaps(tournamentGuid, poolGuid, [mapData]); dispatch('mapAdded', { map: mapData }); } - - // if (response.type !== Response_ResponseType.Success) { - // throw new Error("Failed to save map"); - // } + isLoading = false; return dispatch('close'); } catch (err) { @@ -747,19 +726,7 @@

Tournament Assistant Settings

Configure game behavior options

-
-
- -
- +
diff --git a/src/routes/tournaments/+page.svelte b/src/routes/tournaments/+page.svelte index e19c1e3..00c4b81 100644 --- a/src/routes/tournaments/+page.svelte +++ b/src/routes/tournaments/+page.svelte @@ -3,7 +3,7 @@ import { onMount, onDestroy } from 'svelte'; import Notification from '$lib/components/notifications/Popup.svelte'; import { discordAuthUrl } from '$lib/config.json'; - import { discordDataStore, discordTokenStore, authTokenStore, TAServerPort, TAServerUrl, client } from '$lib/stores'; + import { discordDataStore, discordTokenStore, authTokenStore, TAServerPort, TAServerUrl, client, TAServerPlayerPort } from '$lib/stores'; import { TAClient, Response_ResponseType, Tournament } from 'moons-ta-client'; import { bufferToImageUrl } from '$lib/services/taImages'; import { v4 as uuidv4 } from "uuid"; @@ -125,13 +125,6 @@ } }); - onDestroy(() => { - // if (client.isConnected == true) { - // // Properly disconnect and clean up any listeners - // client.disconnect(); - // } - }); - async function fetchTournamentAuthorisedUsers(tournamentGuid: string) { // Get the authorised users for the tournament let authorisedUsers = []; @@ -213,14 +206,7 @@ try { creating = true; - // Connect to the TA server - const connectResult = await client.connect('server.tournamentassistant.net', '8676'); - - if (connectResult.details.oneofKind === "connect" && connectResult.type === Response_ResponseType.Fail) { - authError = connectResult.details.connect.message; - creating = false; - return; - } + if(!client.isConnected) return; // Create the tournament object let tournament: Tournament = { @@ -241,6 +227,12 @@ pools: [], allowUnauthorizedView: true }, + server: { + name: `${$TAServerUrl}:${$TAServerPort}`, + address: $TAServerUrl, + port: $TAServerPlayerPort, + websocketPort: $TAServerPort + } }; // Convert image to Uint8Array if an image was provided diff --git a/src/routes/tournaments/[tournamentguid]/+page.svelte b/src/routes/tournaments/[tournamentguid]/+page.svelte index 31a702d..cf55526 100644 --- a/src/routes/tournaments/[tournamentguid]/+page.svelte +++ b/src/routes/tournaments/[tournamentguid]/+page.svelte @@ -3,7 +3,7 @@ import { onMount, onDestroy } from "svelte"; import { bkAPIUrl } from '$lib/config.json'; //@ts-ignore - import { Match, Tournament, TAClient, Response_ResponseType } from 'moons-ta-client'; + import { Match, Tournament, TAClient, Response_ResponseType, User, User_PlayStates } from 'moons-ta-client'; import { writable } from "svelte/store"; import SideMenu from "$lib/components/menus/SideMenuTournaments.svelte"; import { goto } from "$app/navigation"; @@ -15,25 +15,10 @@ let isLoading = false; let error: string | null = null; let matches: Match[] = []; - let availablePlayers: any[] = []; - let selectedPlayers: any[] = []; - let isCreatingMatch: boolean = false; + let availablePlayers: User[] = []; // Store for selected player guids const selectedPlayerGuids = writable([]); - - // Player status enum - enum PlayerStatus { - Downloading = "downloading", - SelectingSong = "selecting", - Idle = "idle" - } - - enum PlayerPlayState { - In_Menu = 0, - Waiting_For_Coordinator = 1, - In_Game = 2 - } // Remove 'Bearer ' from the token if($authTokenStore) { @@ -49,13 +34,13 @@ } // Return status color based on player status - function getStatusColor(status: PlayerStatus): string { + function getStatusColor(status: User_PlayStates): string { switch (status) { - case PlayerStatus.Downloading: + case User_PlayStates.InGame: return "var(--danger-color)"; - case PlayerStatus.SelectingSong: + case User_PlayStates.InMenu: return "#FCD34D"; // Yellow - case PlayerStatus.Idle: + case User_PlayStates.WaitingForCoordinator: return "#10B981"; // Green default: return "var(--text-secondary)"; @@ -110,6 +95,7 @@ tournament = client.stateManager.getTournament(tournamentGuid); if(!tournament?.users.some(user => user.guid == client.stateManager.getSelfGuid())) { const joinResult = await client.joinTournament(tournamentGuid); + console.log('jointournament', joinResult); if (joinResult.details.oneofKind !== 'join' || joinResult.type === Response_ResponseType.Fail) { throw new Error('Could not join tournament'); } @@ -159,19 +145,46 @@ associatedUsers: $selectedPlayerGuids }); - console.log(response) + console.log("Match created! Response: ", response); const newMatch = (response as any).details.createMatch.match; goto(`/tournaments/${tournamentGuid}/matches/${newMatch.guid}`); } + async function handleUserConnected(params: [User, Tournament]) { + if(params[1].guid == tournament?.guid) { + availablePlayers.push(params[0]); + } + } + + async function handleUserDisconnected(params: [User, Tournament]) { + if(params[1].guid == tournament?.guid) { + if(availablePlayers.some(x => x.guid == params[0].guid)) { + availablePlayers = availablePlayers.filter(x => x.guid !== params[0].guid); + } else { + let newMatchObject = matches.find(x => x.associatedUsers.includes(params[0].guid))!; + newMatchObject.associatedUsers = newMatchObject.associatedUsers.filter(x => x !== params[0].guid); + matches = [ + ...matches.filter(x => x.guid !== newMatchObject.guid), + newMatchObject + ] + } + } + } + + function getDiscordNameBecauseShit(user: User) { + return user.discordInfo?.username || ""; + } + onMount(async() => { if ($authTokenStore) { await fetchTournamentData(); client.stateManager.on('matchCreated', handleMatchCreated); client.stateManager.on('matchDeleted', handleMatchDeleted); client.stateManager.on('matchUpdated', handleMatchUpdated); + client.stateManager.on('userConnected', handleUserConnected); + client.stateManager.on('userDisconnected', handleUserDisconnected); } else { window.location.href = "/discordAuth" } @@ -181,6 +194,8 @@ client.stateManager.removeListener('matchUpdated', handleMatchUpdated); client.stateManager.removeListener('matchDeleted', handleMatchDeleted); client.stateManager.removeListener('matchCreated', handleMatchDeleted); + client.stateManager.removeListener('userConnected', handleUserConnected); + client.stateManager.removeListener('userDisconnected', handleUserDisconnected); client.removeListener('createdMatch', handleMatchUpdated) // client.disconnect(); }); @@ -287,8 +302,8 @@ alt="Player" class="player-avatar">
-

{player.name} ({player.discordInfo.username})

-

{PlayerPlayState[player.playState]}

+

{player.name} ({getDiscordNameBecauseShit(player)})

+

{User_PlayStates[player.playState]}

{/each} diff --git a/src/routes/tournaments/[tournamentguid]/mappools/[poolGuid]/+page.svelte b/src/routes/tournaments/[tournamentguid]/mappools/[poolGuid]/+page.svelte index 9c53b60..bbdd2ae 100644 --- a/src/routes/tournaments/[tournamentguid]/mappools/[poolGuid]/+page.svelte +++ b/src/routes/tournaments/[tournamentguid]/mappools/[poolGuid]/+page.svelte @@ -149,16 +149,22 @@ } async function handleMapUpdated(event: CustomEvent) { - const res = await client.updateTournamentPoolMap(tournamentGuid, poolGuid, event.detail.map); - console.log(res) + await client.updateTournamentPoolMap(tournamentGuid, poolGuid, event.detail.map); fetchMapPoolData(); showSuccessNotification = true; + setTimeout(() => { + showSuccessNotification = false; + }, 2500); successMessage = "Map successfully updated!"; } - function handleMapAdded(event: CustomEvent) { + async function handleMapAdded(event: CustomEvent) { + await client.addTournamentPoolMaps(tournamentGuid, poolGuid, [event.detail.map]); fetchMapPoolData(); showSuccessNotification = true; + setTimeout(() => { + showSuccessNotification = false; + }, 2500); successMessage = "Map successfully added!"; } @@ -260,25 +266,25 @@ } const response: BeatSaverMap = await data.json(); - + return response; } function getActiveModifiersCompact(gameplayModifiers: number): string[] { const activeModifiers: string[] = []; - // Get all numeric enum values and sort them + // Get all numeric enum values (powers of 2) and sort them const values = Object.values(GameplayModifiers_GameOptions) .filter((value): value is number => typeof value === 'number' && - value !== GameplayModifiers_GameOptions.None + value !== GameplayModifiers_GameOptions.None && + value > 0 ) .sort((a, b) => a - b); - - console.log(values) for (const value of values) { - if (gameplayModifiers & value) { + // Use bitwise AND to check if this specific flag is set + if ((gameplayModifiers & value) === value) { const name = modifierNameMap[value]; if (name && name.trim() !== "") { activeModifiers.push(name); @@ -290,11 +296,11 @@ } function getModifierArray(map: any): string[] { - if (map.taData.gameplayParameters?.gameplayModifiers.options == 0) { + if (map.taData.gameplayParameters?.gameplayModifiers?.options == 0) { return []; } - return getActiveModifiersCompact(map.taData.gameplayParameters.gameplayModifiers); + return getActiveModifiersCompact(map.taData.gameplayParameters?.gameplayModifiers?.options || 0); } onMount(async() => { @@ -414,6 +420,18 @@ {:else} No modifiers active {/each} + {#if map.taData.gameplayParameters?.disableFail} + Disable Fail + {/if} + {#if map.taData.gameplayParameters?.disableCustomNotesOnStream} + Disable Custom Notes On Stream + {/if} + {#if map.taData.gameplayParameters?.disablePause} + Disable Pause + {/if} + {#if map.taData.gameplayParameters?.disableScoresaberSubmission} + Disable ScoreSaber Submission + {/if}
diff --git a/src/routes/tournaments/[tournamentguid]/matches/[matchGuid]/+page.svelte b/src/routes/tournaments/[tournamentguid]/matches/[matchGuid]/+page.svelte index e7bcf7b..3b54938 100644 --- a/src/routes/tournaments/[tournamentguid]/matches/[matchGuid]/+page.svelte +++ b/src/routes/tournaments/[tournamentguid]/matches/[matchGuid]/+page.svelte @@ -258,7 +258,6 @@ } } - // API functions - implement these based on your TA client async function kickPlayerFromMatch(playerGuid: string) { const response = await client.removeUserFromMatch(tournamentGuid, matchGuid, playerGuid); console.log('Kicking player:', playerGuid); @@ -280,6 +279,13 @@ console.log('Sending players back to menu'); } + async function handleUserDisconnected(params: [User, Tournament]) { + if(matchPlayers.some(user => user.guid == params[0].guid)) { + console.log("User disconnected from match. User: ", params[0]) + matchPlayers = matchPlayers.filter(x => x.guid !== params[0].guid); + } + } + onMount(async() => { if ($authTokenStore) { // Fetch the match data that we need to display @@ -287,6 +293,7 @@ // Using stateManger listen to global changes to our match client.stateManager.on('matchDeleted', handleMatchDeleted); client.stateManager.on('matchUpdated', handleMatchUpdated); + client.stateManager.on('userDisconnected', handleUserDisconnected) } else { window.location.href = "/discordAuth" } @@ -296,6 +303,7 @@ // Remove the listeners that were added on mount client.stateManager.removeListener('matchUpdated', handleMatchUpdated); client.stateManager.removeListener('matchDeleted', handleMatchDeleted); + client.stateManager.removeListener('userDisconnected', handleUserDisconnected); }); diff --git a/src/routes/tournaments/[tournamentguid]/qualifiers/+page.svelte b/src/routes/tournaments/[tournamentguid]/qualifiers/+page.svelte new file mode 100644 index 0000000..008eac4 --- /dev/null +++ b/src/routes/tournaments/[tournamentguid]/qualifiers/+page.svelte @@ -0,0 +1,1100 @@ + + +
+
+ + +
+
+

Qualifiers Management

+
+ {#if !error && !isLoading} + + + {/if} +
+
+ +
+

Qualifiers

+ +
+ + {#if isLoading} +
+
+

Loading qualifiers data...

+
+ {:else if error} +
+ error_outline +

{error}

+ +
+ {:else if qualifiers.length === 0} +
+ quiz +

No qualifiers have been created yet

+ +
+ {:else} +
+ {#each qualifiers as qualifier, qualIndex} +
+
+ {qualifier.name} +
+

{qualifier.name}

+

{qualifier.qualifierMaps.length} maps

+
+
+ + +
+
+ +
+ {#each qualifier.maps as map, mapIndex} + +
{hoveredMapIndex = mapIndex; hoveredQualIndex = qualIndex}} + on:mouseleave={() => {hoveredMapIndex = -1; hoveredQualIndex = -1}} + > +
+ {map.metadata.songName} +
+
+
{map.name}
+

{map.metadata.songName}

+

{map.metadata.songAuthorName}

+

Mapped by {map.metadata.levelAuthorName}

+
+
+ {/each} +
+
+ {/each} +
+ {/if} +
+
+
+ +{#if showQualifierPopup} + +{/if} + +{#if showDeleteWarning} + +{/if} + +{#if showInfoPopup} + +{/if} + + \ No newline at end of file diff --git a/src/routes/tournaments/[tournamentguid]/qualifiers/+page.ts b/src/routes/tournaments/[tournamentguid]/qualifiers/+page.ts new file mode 100644 index 0000000..658dc02 --- /dev/null +++ b/src/routes/tournaments/[tournamentguid]/qualifiers/+page.ts @@ -0,0 +1,6 @@ +export function load({ params }: any){ + return { + tournamentGuid: params.tournamentguid, + params: params + } +} \ No newline at end of file diff --git a/src/routes/tournaments/[tournamentguid]/qualifiers/[qualifierGuid]/+page.svelte b/src/routes/tournaments/[tournamentguid]/qualifiers/[qualifierGuid]/+page.svelte new file mode 100644 index 0000000..ce57674 --- /dev/null +++ b/src/routes/tournaments/[tournamentguid]/qualifiers/[qualifierGuid]/+page.svelte @@ -0,0 +1,1331 @@ + + +
+
+ + +
+
+
+ +

{qualifierName || 'Qualifier'} Settings

+
+
+ {#if !error && !isLoading} + + + {/if} +
+
+ + {#if isLoading} +
+
+

Loading qualifier data...

+
+ {:else if error} +
+ error_outline +

{error}

+ +
+ {:else} + +
+
+

Qualifier Settings

+ +
+ +
+
+ + +
+ +
+ + +
+
+ Qualifier +
+ + +
+
+ +
+
+
+ + +
+ +
+
+ +
+
+
+ + +
+ +
+
+ +
+
+
+ + +
+ +
+
+ +
+
+
+ + +
+ +
+
+ + {#if enableDiscordBotLeaderboard || enableDiscordScoreFeed} +
+ + +
+ {/if} + +
+ + + +
+
+
+ + +
+
+

Maps

+
+ +
+
+ + {#if maps.length === 0} +
+ music_note +

No maps have been added to this qualifier yet

+ +
+ {:else} +
+ {#each maps as map} +
+
+
+ {map.taData.gameplayParameters?.beatmap?.name} cover +
+
+

{map.taData.gameplayParameters?.beatmap?.name}

+

By {map.beatsaverData.metadata.levelAuthorName || "Unknown Artist"}

+
+ {#if map.taData.gameplayParameters?.beatmap?.difficulty} + + speed + {MapDifficulty[map.taData.gameplayParameters?.beatmap?.difficulty]} + + {/if} + {#if map.beatsaverData.metadata.duration} + + access_time + {Math.floor(map.beatsaverData.metadata.duration / 60)}:{(map.beatsaverData.metadata.duration % 60).toString().padStart(2, '0')} + + {/if} +
+
+
+ + +
+
+
+
Active Modifiers
+
+ {#each getModifierArray(map) as modifier} + {modifier} + {:else} + No modifiers active + {/each} + {#if map.taData.gameplayParameters?.disableFail} + Disable Fail + {/if} + {#if map.taData.gameplayParameters?.disableCustomNotesOnStream} + Disable Custom Notes On Stream + {/if} + {#if map.taData.gameplayParameters?.disablePause} + Disable Pause + {/if} + {#if map.taData.gameplayParameters?.disableScoresaberSubmission} + Disable ScoreSaber Submission + {/if} +
+
+
+ {/each} +
+ {/if} +
+ {/if} +
+
+
+ + +{#if showEditMapModal} + +{/if} + + +{#if showInfoPopup} + +{/if} + + +{#if showDeleteConfirmation} + +{/if} + + + + + \ No newline at end of file diff --git a/src/routes/tournaments/[tournamentguid]/qualifiers/[qualifierGuid]/+page.ts b/src/routes/tournaments/[tournamentguid]/qualifiers/[qualifierGuid]/+page.ts new file mode 100644 index 0000000..50c68ac --- /dev/null +++ b/src/routes/tournaments/[tournamentguid]/qualifiers/[qualifierGuid]/+page.ts @@ -0,0 +1,7 @@ +export function load({ params }: any){ + return { + tournamentGuid: params.tournamentguid, + qualifierGuid: params.qualifierGuid, + params: params + } +} \ No newline at end of file