From 9bb0af358ce046612f1fb3b5cddfd9f0bc4f9a33 Mon Sep 17 00:00:00 2001 From: Luna Date: Sat, 5 Jul 2025 18:54:16 +0200 Subject: [PATCH] fix map loading stuff, add bswc score calc stuff --- .../components/popups/AddMapsFromFile.svelte | 2 +- src/lib/components/popups/EditMap.svelte | 2 +- src/lib/services/beatsaver.ts | 4 +- src/routes/bswc/2025/scoreParser/+page.svelte | 622 ++++++++++++++++++ src/routes/tournaments/+page.svelte | 2 +- .../mappools/[poolGuid]/+page.svelte | 2 +- .../matches/[matchGuid]/+page.svelte | 10 +- 7 files changed, 633 insertions(+), 11 deletions(-) create mode 100644 src/routes/bswc/2025/scoreParser/+page.svelte diff --git a/src/lib/components/popups/AddMapsFromFile.svelte b/src/lib/components/popups/AddMapsFromFile.svelte index b0e11d2..c11f954 100644 --- a/src/lib/components/popups/AddMapsFromFile.svelte +++ b/src/lib/components/popups/AddMapsFromFile.svelte @@ -263,7 +263,7 @@ gameplayParameters: { beatmap: { name: map.songName, - levelId: `custom_level_${map.hash}`, + levelId: `custom_level_${map.hash.toUpperCase()}`, difficulty: getDifficultyNumber(map.difficulty), characteristic: { serializedName: 'Standard', diff --git a/src/lib/components/popups/EditMap.svelte b/src/lib/components/popups/EditMap.svelte index 38da52a..10efa07 100644 --- a/src/lib/components/popups/EditMap.svelte +++ b/src/lib/components/popups/EditMap.svelte @@ -287,7 +287,7 @@ gameplayParameters: { beatmap: { name: mapName, - levelId: mapBeatmapId, + levelId: mapBeatmapId.toUpperCase().replace('CUSTOM_LEVEL_', 'custom_level_'), difficulty: getDifficultyNumber(selectedDifficulty), characteristic: { serializedName: selectedCharacteristic, diff --git a/src/lib/services/beatsaver.ts b/src/lib/services/beatsaver.ts index ba03a91..320177e 100644 --- a/src/lib/services/beatsaver.ts +++ b/src/lib/services/beatsaver.ts @@ -213,10 +213,10 @@ export async function fetchMapByHashOrKey(identifier: string): Promise + import { onMount } from 'svelte'; + + interface Difficulty { + characteristic: string; + name: string; + } + + interface Song { + hash: string; + difficulties: Difficulty[]; + } + + interface MapPool { + playlistTitle: string; + playlistAuthor: string; + playlistDescription: string; + syncURL: string; + image: string; + songs: Song[]; + } + + interface BeatSaverMap { + id: string; + name: string; + description: string; + uploader: { + name: string; + }; + metadata: { + songName: string; + songAuthorName: string; + levelAuthorName: string; + }; + versions: Array<{ + hash: string; + diffs: Array<{ + difficulty: string; + characteristic: string; + nps: number; + notes: number; + }>; + }>; + } + + interface MapData extends BeatSaverMap { + selectedDifficulty: string; + selectedCharacteristic: string; + maxScore: number; + nps: number; + } + + let mapPool: MapPool | null = null; + let maps: MapData[] = []; + let selectedMapIndex: number | null = null; + let loading = false; + let resultsInput = ''; + let calculatedResults = ''; + let copied = false; + let fileInput: HTMLInputElement; + + onMount(() => { + const defaultPool: MapPool = { + playlistTitle: "Week 2", + playlistAuthor: "Cube Community", + playlistDescription: "The Bo7 Week 2 Map Pool for the Beat Saber World Cup 2025", + syncURL: "https://api.cube.community/rest/pooling/playlist?poolId=cm94fsme0000ty9u9h3d3par0", + image: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==", + songs: [ + {"hash":"bce7949bd6b9432782b543c77014fddc756a6894","difficulties":[{"characteristic":"Standard","name":"expert"}]}, + {"hash":"e2e71212e6468506b35ab08986918f317157cddc","difficulties":[{"characteristic":"Standard","name":"easy"}]}, + {"hash":"40f74ba3c2a4475acabb8e2a1d5eb9219f36e043","difficulties":[{"characteristic":"Standard","name":"expertPlus"}]}, + {"hash":"55710205349cc4b25e09644d797d119ef964bd1d","difficulties":[{"characteristic":"Standard","name":"expertPlus"}]}, + {"hash":"62d66da2dd4ee601b24ee608847fb3657a78e761","difficulties":[{"characteristic":"Standard","name":"expertPlus"}]}, + {"hash":"9109925c3719d232e3eeae8868c0bf1efd660d51","difficulties":[{"characteristic":"Standard","name":"expertPlus"}]}, + {"hash":"93d5adc1756f488ad85484279831f327d94efe64","difficulties":[{"characteristic":"Standard","name":"expertPlus"}]}, + {"hash":"95d9d9e21c381f2e41073ca3257208174e90b322","difficulties":[{"characteristic":"Standard","name":"expertPlus"}]}, + {"hash":"b907db0d67ab965b4b702d242463a44e01c5ecd3","difficulties":[{"characteristic":"Standard","name":"expertPlus"}]}, + {"hash":"d67821c494b38ff51860766d853ba8f5d82ca104","difficulties":[{"characteristic":"Standard","name":"expertPlus"}]}, + {"hash":"a1c6d47059e479ae493d118b4c7e5a1ef1340e99","difficulties":[{"characteristic":"Standard","name":"expertPlus"}]} + ] + }; + + mapPool = defaultPool; + fetchMapsData(defaultPool); + }); + + async function fetchMapsData(pool: MapPool) { + loading = true; + const mapData: MapData[] = []; + + for (const song of pool.songs) { + try { + const response = await fetch(`https://api.beatsaver.com/maps/hash/${song.hash}`); + if (response.ok) { + const data: BeatSaverMap = await response.json(); + + // Find the selected difficulty + const selectedDiff = song.difficulties[0]; + const version = data.versions.find(v => v.hash.toLowerCase() === song.hash.toLowerCase()); + + if (version) { + const diff = version.diffs.find(d => + d.difficulty.toLowerCase() === selectedDiff.name.toLowerCase() && + d.characteristic === selectedDiff.characteristic + ); + + if (diff) { + const maxScore = diff.notes * 920; // Max score per note in Beat Saber + + mapData.push({ + ...data, + selectedDifficulty: selectedDiff.name, + selectedCharacteristic: selectedDiff.characteristic, + maxScore, + nps: diff.nps + }); + } + } + } + } catch (error) { + console.error(`Error fetching map ${song.hash}:`, error); + } + } + + maps = mapData; + loading = false; + } + + async function handleFileUpload(event: Event) { + const target = event.target as HTMLInputElement; + const file = target.files?.[0]; + if (!file) return; + + const text = await file.text(); + try { + const pool: MapPool = JSON.parse(text); + mapPool = pool; + fetchMapsData(pool); + } catch (error) { + console.error('Error parsing map pool file:', error); + } + } + + function calculateResults() { + if (selectedMapIndex === null || selectedMapIndex < 0 || !maps[selectedMapIndex]) return; + + const selectedMap = maps[selectedMapIndex]; + const maxScore = selectedMap.maxScore; + + // Parse results input + const lines = resultsInput.trim().split('\n'); + const scores: Array<{ country: string; score: number; accuracy: number }> = []; + let winner = ''; + + lines.forEach(line => { + const scoreMatch = line.match(/(.+?)\s+Score:\s*([\d,]+)/); + const winnerMatch = line.match(/Winner:\s*(.+)/); + + if (scoreMatch) { + const country = scoreMatch[1].trim(); + const score = parseInt(scoreMatch[2].replace(/,/g, '')); + const accuracy = (score / maxScore) * 100 / 3; // Divide by 3 for average accuracy + scores.push({ country, score, accuracy }); + } else if (winnerMatch) { + winner = winnerMatch[1].trim(); + } + }); + + // Format results + let result = ''; + scores.forEach(({ country, score, accuracy }) => { + result += `${country} Score: ${score.toLocaleString()} (${accuracy.toFixed(3)}%)\n`; + }); + + if (winner) { + result += `Winner: ${winner}`; + } + + calculatedResults = result; + } + + async function copyResults() { + if (calculatedResults) { + await navigator.clipboard.writeText(calculatedResults); + copied = true; + setTimeout(() => copied = false, 2000); + } + } + + function getDifficultyColor(difficulty: string): string { + switch (difficulty.toLowerCase()) { + case 'easy': return 'text-green-400'; + case 'normal': return 'text-blue-400'; + case 'hard': return 'text-orange-400'; + case 'expert': return 'text-red-400'; + case 'expertplus': return 'text-purple-400'; + default: return 'text-gray-400'; + } + } + + function selectMap(index: number) { + selectedMapIndex = index; + calculateResults(); + } + + // Reactive statement to recalculate when inputs change + $: if (resultsInput && selectedMapIndex !== null) { + calculateResults(); + } + + + +
+ +
+
+ + {#if loading} +
Loading maps...
+ {/if} +
+ + {#if mapPool} +
+ {mapPool.playlistTitle} +
+

{mapPool.playlistTitle}

+

by {mapPool.playlistAuthor}

+

{mapPool.playlistDescription}

+
+
+ {/if} +
+ + +
+ {#each maps as map, index} +
selectMap(index)} + role="button" + tabindex="0" + on:keydown={(e) => e.key === 'Enter' && selectMap(index)} + > +
+

+ {map.metadata.songName} +

+

by {map.metadata.songAuthorName}

+

mapped by {map.metadata.levelAuthorName}

+ +
+
+ + {map.selectedDifficulty.charAt(0).toUpperCase() + map.selectedDifficulty.slice(1)} + + {map.nps.toFixed(1)} NPS +
+
+ Max Score: {map.maxScore.toLocaleString()} +
+
+
+
+ {/each} +
+ + +
+
+

Enter Results

+ +
+ +
+
+

Calculated Results

+ {#if calculatedResults} + + {/if} +
+ +
+
+ + {#if selectedMapIndex !== null && maps[selectedMapIndex]} +
+

Currently Selected:

+

{maps[selectedMapIndex].metadata.songName}

+

Max Score: {maps[selectedMapIndex].maxScore.toLocaleString()}

+
+ {/if} +
+ + + \ No newline at end of file diff --git a/src/routes/tournaments/+page.svelte b/src/routes/tournaments/+page.svelte index 00c4b81..0da6fbf 100644 --- a/src/routes/tournaments/+page.svelte +++ b/src/routes/tournaments/+page.svelte @@ -130,7 +130,7 @@ let authorisedUsers = []; try { const response = await client.getAuthorizedUsers(tournamentGuid); - console.log(response) + console.log("authUsers Found", response); authorisedUsers = (response as any).details.getAuthorizedUsers.authorizedUsers; return authorisedUsers; } catch (error) { diff --git a/src/routes/tournaments/[tournamentguid]/mappools/[poolGuid]/+page.svelte b/src/routes/tournaments/[tournamentguid]/mappools/[poolGuid]/+page.svelte index e839549..f71c820 100644 --- a/src/routes/tournaments/[tournamentguid]/mappools/[poolGuid]/+page.svelte +++ b/src/routes/tournaments/[tournamentguid]/mappools/[poolGuid]/+page.svelte @@ -245,7 +245,7 @@ } async function getMapBeatsaverData(levelId: string): Promise { - const newLevelId = levelId.replace("custom_level_", ""); + const newLevelId = levelId.toLowerCase().replace("custom_level_", ""); const data = await fetch(`https://api.beatsaver.com/maps/hash/${newLevelId}`); diff --git a/src/routes/tournaments/[tournamentguid]/matches/[matchGuid]/+page.svelte b/src/routes/tournaments/[tournamentguid]/matches/[matchGuid]/+page.svelte index 148b84c..fcf3648 100644 --- a/src/routes/tournaments/[tournamentguid]/matches/[matchGuid]/+page.svelte +++ b/src/routes/tournaments/[tournamentguid]/matches/[matchGuid]/+page.svelte @@ -107,9 +107,9 @@ matchPlayers .filter(x => x.playState == User_PlayStates.InGame) .forEach(player => { - const playerKey = `${player.guid}-${currentSong.beatmap.levelId.toLowerCase()}`; - if (!activeSongPlayers.has(playerKey)) { - activeSongPlayers.add(playerKey); + const playerKey: string = `${player.guid}-${currentSong.beatmap.levelId.toLowerCase()}`; + if (!activeSongPlayers.has(playerKey as any)) { + activeSongPlayers.add(playerKey as any); } }); } @@ -424,7 +424,7 @@ const poolCustomMaps: CustomMap[] = pool.maps .map(map => { const levelId = map.gameplayParameters!.beatmap!.levelId; - const beatsaverData = mapDataLookup[(levelId.replace('custom_level_', '').toLowerCase())]; + const beatsaverData = mapDataLookup[(levelId.toLowerCase().replace('custom_level_', ''))]; if (!beatsaverData) return null; @@ -775,7 +775,7 @@ loadingSongForPlayers = true; const matchResponse = await client.setMatchMap(tournamentGuid, matchGuid, map.taData); if(matchResponse.type == Response_ResponseType.Fail) return; - const loadResponse = await client.loadSong(map.taData.gameplayParameters!.beatmap!.levelId, playingPlayerIds, 3 * 60 * 1000); + const loadResponse = await client.loadSong(map.taData.gameplayParameters!.beatmap!.levelId.toUpperCase().replace('CUSTOM_LEVEL_', 'custom_level_'), playingPlayerIds, 3 * 60 * 1000); failedToLoadSongForPlayers = false; const failedLoads = loadResponse.filter(x => x.response.type == Response_ResponseType.Fail);