fix map loading stuff, add bswc score calc stuff
This commit is contained in:
parent
5ae7c790be
commit
9bb0af358c
7 changed files with 633 additions and 11 deletions
|
|
@ -263,7 +263,7 @@
|
||||||
gameplayParameters: {
|
gameplayParameters: {
|
||||||
beatmap: {
|
beatmap: {
|
||||||
name: map.songName,
|
name: map.songName,
|
||||||
levelId: `custom_level_${map.hash}`,
|
levelId: `custom_level_${map.hash.toUpperCase()}`,
|
||||||
difficulty: getDifficultyNumber(map.difficulty),
|
difficulty: getDifficultyNumber(map.difficulty),
|
||||||
characteristic: {
|
characteristic: {
|
||||||
serializedName: 'Standard',
|
serializedName: 'Standard',
|
||||||
|
|
|
||||||
|
|
@ -287,7 +287,7 @@
|
||||||
gameplayParameters: {
|
gameplayParameters: {
|
||||||
beatmap: {
|
beatmap: {
|
||||||
name: mapName,
|
name: mapName,
|
||||||
levelId: mapBeatmapId,
|
levelId: mapBeatmapId.toUpperCase().replace('CUSTOM_LEVEL_', 'custom_level_'),
|
||||||
difficulty: getDifficultyNumber(selectedDifficulty),
|
difficulty: getDifficultyNumber(selectedDifficulty),
|
||||||
characteristic: {
|
characteristic: {
|
||||||
serializedName: selectedCharacteristic,
|
serializedName: selectedCharacteristic,
|
||||||
|
|
|
||||||
|
|
@ -213,10 +213,10 @@ export async function fetchMapByHashOrKey(identifier: string): Promise<BeatSaver
|
||||||
*/
|
*/
|
||||||
export function extractHashFromLevelId(levelId: string): string {
|
export function extractHashFromLevelId(levelId: string): string {
|
||||||
const prefix = 'custom_level_';
|
const prefix = 'custom_level_';
|
||||||
if (!levelId.startsWith(prefix)) {
|
if (!levelId.toLowerCase().startsWith(prefix)) {
|
||||||
throw new Error(`Invalid level ID format. Expected format: ${prefix}[hash]`);
|
throw new Error(`Invalid level ID format. Expected format: ${prefix}[hash]`);
|
||||||
}
|
}
|
||||||
return levelId.substring(prefix.length);
|
return levelId.toLowerCase().substring(prefix.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
622
src/routes/bswc/2025/scoreParser/+page.svelte
Normal file
622
src/routes/bswc/2025/scoreParser/+page.svelte
Normal file
|
|
@ -0,0 +1,622 @@
|
||||||
|
<script lang="ts">
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="header">
|
||||||
|
<div class="header-controls">
|
||||||
|
<label class="file-upload-label">
|
||||||
|
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
|
||||||
|
</svg>
|
||||||
|
Load Map Pool
|
||||||
|
<input
|
||||||
|
bind:this={fileInput}
|
||||||
|
type="file"
|
||||||
|
accept=".bplist,.json"
|
||||||
|
on:change={handleFileUpload}
|
||||||
|
class="file-upload-input"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
{#if loading}
|
||||||
|
<div class="loading-text">Loading maps...</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if mapPool}
|
||||||
|
<div class="map-pool-info">
|
||||||
|
<img
|
||||||
|
src={mapPool.image}
|
||||||
|
alt={mapPool.playlistTitle}
|
||||||
|
class="map-pool-image"
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<h1 class="map-pool-title">{mapPool.playlistTitle}</h1>
|
||||||
|
<p class="map-pool-author">by {mapPool.playlistAuthor}</p>
|
||||||
|
<p class="map-pool-description">{mapPool.playlistDescription}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Maps Grid -->
|
||||||
|
<div class="maps-grid">
|
||||||
|
{#each maps as map, index}
|
||||||
|
<div
|
||||||
|
class="map-card {selectedMapIndex === index ? 'selected' : ''}"
|
||||||
|
on:click={() => selectMap(index)}
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
on:keydown={(e) => e.key === 'Enter' && selectMap(index)}
|
||||||
|
>
|
||||||
|
<div class="map-content">
|
||||||
|
<h3 class="map-title">
|
||||||
|
{map.metadata.songName}
|
||||||
|
</h3>
|
||||||
|
<p class="map-author">by {map.metadata.songAuthorName}</p>
|
||||||
|
<p class="map-mapper">mapped by {map.metadata.levelAuthorName}</p>
|
||||||
|
|
||||||
|
<div class="map-details">
|
||||||
|
<div class="map-difficulty-row">
|
||||||
|
<span class="map-difficulty difficulty-{map.selectedDifficulty.toLowerCase()}">
|
||||||
|
{map.selectedDifficulty.charAt(0).toUpperCase() + map.selectedDifficulty.slice(1)}
|
||||||
|
</span>
|
||||||
|
<span class="map-nps">{map.nps.toFixed(1)} NPS</span>
|
||||||
|
</div>
|
||||||
|
<div class="map-score">
|
||||||
|
Max Score: {map.maxScore.toLocaleString()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Results Section -->
|
||||||
|
<div class="results-section">
|
||||||
|
<div class="results-column">
|
||||||
|
<h2>Enter Results</h2>
|
||||||
|
<textarea
|
||||||
|
bind:value={resultsInput}
|
||||||
|
placeholder="United Kingdom Score: 1,238,773 Poland Score: 1,233,813 Winner: United Kingdom"
|
||||||
|
class="results-textarea"
|
||||||
|
></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="results-column">
|
||||||
|
<div class="results-header">
|
||||||
|
<h2>Calculated Results</h2>
|
||||||
|
{#if calculatedResults}
|
||||||
|
<button
|
||||||
|
on:click={copyResults}
|
||||||
|
class="copy-button"
|
||||||
|
>
|
||||||
|
{#if copied}
|
||||||
|
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
|
||||||
|
</svg>
|
||||||
|
Copied!
|
||||||
|
{:else}
|
||||||
|
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
|
||||||
|
</svg>
|
||||||
|
Copy
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<textarea
|
||||||
|
bind:value={calculatedResults}
|
||||||
|
readonly
|
||||||
|
class="results-textarea"
|
||||||
|
></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if selectedMapIndex !== null && maps[selectedMapIndex]}
|
||||||
|
<div class="selected-map-info">
|
||||||
|
<h3>Currently Selected:</h3>
|
||||||
|
<p class="selected-map-name">{maps[selectedMapIndex].metadata.songName}</p>
|
||||||
|
<p class="selected-map-score">Max Score: {maps[selectedMapIndex].maxScore.toLocaleString()}</p>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
min-height: 100vh;
|
||||||
|
color: white;
|
||||||
|
padding: 24px;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 1280px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header */
|
||||||
|
.header {
|
||||||
|
margin-bottom: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-controls {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-upload-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
background-color: #2563eb;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-upload-label:hover {
|
||||||
|
background-color: #1d4ed8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-upload-label svg {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-upload-input {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-text {
|
||||||
|
color: #60a5fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-pool-info {
|
||||||
|
background-color: #1f2937;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 24px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-pool-image {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
border-radius: 8px;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-pool-title {
|
||||||
|
font-size: 30px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: white;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-pool-author {
|
||||||
|
color: #d1d5db;
|
||||||
|
margin: 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-pool-description {
|
||||||
|
color: #9ca3af;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Maps Grid */
|
||||||
|
.maps-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||||
|
gap: 24px;
|
||||||
|
margin-bottom: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-card {
|
||||||
|
background-color: #1f2937;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 24px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
opacity: 0.8;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-card:hover {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-card.selected {
|
||||||
|
opacity: 1;
|
||||||
|
border: 2px solid #3b82f6;
|
||||||
|
box-shadow: 0 0 20px rgba(59, 130, 246, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-title {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: white;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
line-height: 1.4;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-author {
|
||||||
|
color: #d1d5db;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-mapper {
|
||||||
|
color: #9ca3af;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-details {
|
||||||
|
margin-top: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-difficulty-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-difficulty {
|
||||||
|
font-weight: 500;
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.difficulty-easy { color: #10b981; }
|
||||||
|
.difficulty-normal { color: #3b82f6; }
|
||||||
|
.difficulty-hard { color: #f59e0b; }
|
||||||
|
.difficulty-expert { color: #ef4444; }
|
||||||
|
.difficulty-expertplus { color: #8b5cf6; }
|
||||||
|
|
||||||
|
.map-nps {
|
||||||
|
color: #9ca3af;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-score {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Results Section */
|
||||||
|
.results-section {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.results-column h2 {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.results-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-button {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
background-color: #059669;
|
||||||
|
padding: 4px 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-button:hover {
|
||||||
|
background-color: #047857;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-button svg {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.results-textarea {
|
||||||
|
width: 100%;
|
||||||
|
height: 192px;
|
||||||
|
background-color: #1f2937;
|
||||||
|
border: 1px solid #374151;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 16px;
|
||||||
|
color: white;
|
||||||
|
font-family: inherit;
|
||||||
|
resize: none;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.results-textarea:focus {
|
||||||
|
border-color: #3b82f6;
|
||||||
|
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.results-textarea::placeholder {
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
|
||||||
|
.results-textarea[readonly] {
|
||||||
|
background-color: #1f2937;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Selected Map Info */
|
||||||
|
.selected-map-info {
|
||||||
|
margin-top: 24px;
|
||||||
|
padding: 16px;
|
||||||
|
background-color: #1f2937;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-map-info h3 {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-map-name {
|
||||||
|
color: #60a5fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-map-score {
|
||||||
|
color: #9ca3af;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1024px) {
|
||||||
|
.results-section {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-pool-info {
|
||||||
|
flex-direction: column;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
body {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.maps-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -130,7 +130,7 @@
|
||||||
let authorisedUsers = [];
|
let authorisedUsers = [];
|
||||||
try {
|
try {
|
||||||
const response = await client.getAuthorizedUsers(tournamentGuid);
|
const response = await client.getAuthorizedUsers(tournamentGuid);
|
||||||
console.log(response)
|
console.log("authUsers Found", response);
|
||||||
authorisedUsers = (response as any).details.getAuthorizedUsers.authorizedUsers;
|
authorisedUsers = (response as any).details.getAuthorizedUsers.authorizedUsers;
|
||||||
return authorisedUsers;
|
return authorisedUsers;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
||||||
|
|
@ -245,7 +245,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getMapBeatsaverData(levelId: string): Promise<BeatSaverMap> {
|
async function getMapBeatsaverData(levelId: string): Promise<BeatSaverMap> {
|
||||||
const newLevelId = levelId.replace("custom_level_", "");
|
const newLevelId = levelId.toLowerCase().replace("custom_level_", "");
|
||||||
|
|
||||||
const data = await fetch(`https://api.beatsaver.com/maps/hash/${newLevelId}`);
|
const data = await fetch(`https://api.beatsaver.com/maps/hash/${newLevelId}`);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -107,9 +107,9 @@
|
||||||
matchPlayers
|
matchPlayers
|
||||||
.filter(x => x.playState == User_PlayStates.InGame)
|
.filter(x => x.playState == User_PlayStates.InGame)
|
||||||
.forEach(player => {
|
.forEach(player => {
|
||||||
const playerKey = `${player.guid}-${currentSong.beatmap.levelId.toLowerCase()}`;
|
const playerKey: string = `${player.guid}-${currentSong.beatmap.levelId.toLowerCase()}`;
|
||||||
if (!activeSongPlayers.has(playerKey)) {
|
if (!activeSongPlayers.has(playerKey as any)) {
|
||||||
activeSongPlayers.add(playerKey);
|
activeSongPlayers.add(playerKey as any);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -424,7 +424,7 @@
|
||||||
const poolCustomMaps: CustomMap[] = pool.maps
|
const poolCustomMaps: CustomMap[] = pool.maps
|
||||||
.map(map => {
|
.map(map => {
|
||||||
const levelId = map.gameplayParameters!.beatmap!.levelId;
|
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;
|
if (!beatsaverData) return null;
|
||||||
|
|
||||||
|
|
@ -775,7 +775,7 @@
|
||||||
loadingSongForPlayers = true;
|
loadingSongForPlayers = true;
|
||||||
const matchResponse = await client.setMatchMap(tournamentGuid, matchGuid, map.taData);
|
const matchResponse = await client.setMatchMap(tournamentGuid, matchGuid, map.taData);
|
||||||
if(matchResponse.type == Response_ResponseType.Fail) return;
|
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;
|
failedToLoadSongForPlayers = false;
|
||||||
|
|
||||||
const failedLoads = loadResponse.filter(x => x.response.type == Response_ResponseType.Fail);
|
const failedLoads = loadResponse.filter(x => x.response.type == Response_ResponseType.Fail);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue