diff --git a/src/lib/components/modules/PreviouslyPlayedSongs.svelte b/src/lib/components/modules/PreviouslyPlayedSongs.svelte index 546ff78..0b5ac6c 100644 --- a/src/lib/components/modules/PreviouslyPlayedSongs.svelte +++ b/src/lib/components/modules/PreviouslyPlayedSongs.svelte @@ -2,42 +2,12 @@ import { createEventDispatcher } from 'svelte'; import { type Map, GameplayModifiers_GameOptions, Push_SongFinished, RealtimeScore } from 'moons-ta-client'; import type { BeatSaverMap } from '$lib/services/beatsaver.js'; - - interface ScoreWithAccuracy extends Push_SongFinished { - accuracy: number; - } - - interface PreviousResults { - taData: Map; - beatsaverData: BeatSaverMap; - scores: ScoreWithAccuracy[]; - completionType: 'Completed' | 'Still Awaiting Scores' | 'Exited To Menu'; - } + import { type ScoreWithAccuracy, type PreviousResults, MapDifficulty } from '$lib/interfaces/match.interfaces'; export let maps: PreviousResults[] = []; - $: maps = maps.map(map => { - const newScores = map.scores.map(score => { - const accuracy = calculateAccuracy(score, map); - return { - ...score, - accuracy: parseFloat(accuracy) - } as ScoreWithAccuracy; - }); - map.scores = newScores; - return map; - }); - const dispatch = createEventDispatcher(); - enum MapDifficulty { - "Easy" = 0, - "Normal" = 1, - "Hard" = 2, - "Expert" = 3, - "ExpertPlus" = 4, - } - const modifierNameMap: Record = { [GameplayModifiers_GameOptions.None]: "", [GameplayModifiers_GameOptions.NoFail]: "No Fail", @@ -87,7 +57,7 @@ discordMessage += `**${positionText} ${displayName}**\n`; discordMessage += `Score: ${score.score.toLocaleString()}\n`; - discordMessage += `Accuracy: ${score.accuracy.toFixed(2)}%\n`; + discordMessage += `Accuracy: ${score.accuracy}%\n`; discordMessage += `Misses: ${score.misses} | Bad Cuts: ${score.badCuts}\n\n`; }); @@ -162,32 +132,6 @@ return 'help'; } } - - function getMaxScore(songInfo: BeatSaverMap, characteristic: string = "standard", difficulty: string): number { - const diff = maps.find(x => x.beatsaverData.versions[0].hash == songInfo.versions[0].hash)?.beatsaverData.versions[0].diffs.find( - (x) => ((characteristic.toLowerCase() !== 'expertplus' && characteristic.toLowerCase() !== 'expert+') ? x.characteristic.toLowerCase() === characteristic.toLowerCase() : (x.characteristic.toLowerCase() == 'expertplus' || x.characteristic.toLowerCase() == 'expert+')) && x.difficulty.toLowerCase() === difficulty.toLowerCase() - ); - - return diff?.maxScore ?? 0; - } - - // Method to convert difficulty number to string - function getDifficultyAsString(difficulty: number): string { - return MapDifficulty[difficulty] || "ExpertPlus"; - } - - // Main accuracy calculation logic - function calculateAccuracy(score: Push_SongFinished, mapWithSongInfo: PreviousResults): string { - console.log(maps) - const maxScore = getMaxScore( - mapWithSongInfo.beatsaverData, - score.beatmap?.characteristic?.serializedName ?? "Standard", - getDifficultyAsString(score.beatmap?.difficulty ?? 4) || "ExpertPlus" - ); - - const accuracy = ((score.score / maxScore) * 100).toFixed(2); - return accuracy; - }
diff --git a/src/lib/components/modules/SongQueue.svelte b/src/lib/components/modules/SongQueue.svelte index 84c1871..226e6fd 100644 --- a/src/lib/components/modules/SongQueue.svelte +++ b/src/lib/components/modules/SongQueue.svelte @@ -58,6 +58,10 @@ dispatch('removeMap', { map: map }); } + function handleAddMapsFromTAPool() { + dispatch('addMapsFromTAPool'); + } + function handleLoadMap(map: CustomMap) { mapToLoad = map; showConfirmDialog = true; @@ -121,6 +125,10 @@ add Add Map +
{#if maps.length === 0} @@ -337,8 +345,8 @@ display: flex; flex-direction: column; gap: 1rem; - max-height: 60vh; - overflow-y: auto; + /* max-height: 60vh; + overflow-y: auto; */ } .map-card { @@ -608,7 +616,7 @@ -webkit-font-smoothing: antialiased; } - /* Scrollbar Styling */ + /* Scrollbar Styling .maps-list::-webkit-scrollbar { width: 0.375rem; } @@ -625,7 +633,7 @@ .maps-list::-webkit-scrollbar-thumb:hover { background: var(--text-secondary); - } + } */ /* Responsive Design */ @media (max-width: 768px) { diff --git a/src/lib/components/popups/AddMapsFromFile.svelte b/src/lib/components/popups/AddMapsFromFile.svelte new file mode 100644 index 0000000..b0e11d2 --- /dev/null +++ b/src/lib/components/popups/AddMapsFromFile.svelte @@ -0,0 +1,1054 @@ + + +{#if isOpen} + + + +{/if} + + \ No newline at end of file diff --git a/src/lib/components/popups/AddMapsFromTAPool.svelte b/src/lib/components/popups/AddMapsFromTAPool.svelte new file mode 100644 index 0000000..fdbdf03 --- /dev/null +++ b/src/lib/components/popups/AddMapsFromTAPool.svelte @@ -0,0 +1,657 @@ + + +{#if isOpen} + + + +{/if} + + \ No newline at end of file diff --git a/src/lib/components/popups/EditMap.svelte b/src/lib/components/popups/EditMap.svelte index 582a085..38da52a 100644 --- a/src/lib/components/popups/EditMap.svelte +++ b/src/lib/components/popups/EditMap.svelte @@ -6,6 +6,7 @@ import { v4 as uuidv4 } from "uuid"; import { onMount } from "svelte"; import type { BeatSaverMap } from "$lib/services/beatsaver"; + import { type Modifier, allGameModifiers } from "$lib/services/gamePlayModifiers"; export let tournamentGuid: string; export let mode: 'qualifier' | 'playing' = 'playing'; @@ -38,177 +39,18 @@ // Qualifier-specific settings let limitAttemptsEnabled = false; let numberOfAttempts = 1; - let disableScoresaberSubmission = false; let showScoreboard = false; // Tournament Assistant settings for both modes let disableFail = false; let disablePause = false; + let disableScoresaberSubmission = false; // Playing-specific settings let disableCustomNotesOnStream = false; - // Beat Saber Modifiers with incompatibility groups - interface Modifier { - id: GameplayModifiers_GameOptions; - name: string; - description: string; - enabled: boolean; - group: string; - incompatibilityGroup?: string; // Modifiers in same group are mutually exclusive - allowedInMode?: ('qualifier' | 'playing')[]; - } - - enum MapDifficulty { - "Easy" = 0, - "Normal" = 1, - "Hard" = 2, - "Expert" = 3, - "Expert+" = 4 - } - // Game modifiers with proper grouping and mode restrictions - let gameModifiers: Modifier[] = [ - // Failure Prevention (mutually exclusive) - { - id: GameplayModifiers_GameOptions.NoFail, - name: "No Fail", - description: "You can't fail the level", - enabled: false, - group: "Failure Prevention", - incompatibilityGroup: "failure", - allowedInMode: ['qualifier', 'playing'] - }, - { - id: GameplayModifiers_GameOptions.InstaFail, - name: "One Life", - description: "You only have one life", - enabled: false, - group: "Failure Prevention", - incompatibilityGroup: "failure", - allowedInMode: ['qualifier', 'playing'] - }, - { - id: GameplayModifiers_GameOptions.BatteryEnergy, - name: "Four Lives", - description: "You have four lives", - enabled: false, - group: "Failure Prevention", - incompatibilityGroup: "failure", - allowedInMode: ['qualifier', 'playing'] - }, - - // Environment Modifiers - { - id: GameplayModifiers_GameOptions.NoBombs, - name: "No Bombs", - description: "No bombs will appear", - enabled: false, - group: "Environment", - allowedInMode: ['qualifier', 'playing'] - }, - { - id: GameplayModifiers_GameOptions.NoObstacles, - name: "No Walls", - description: "No walls will appear", - enabled: false, - group: "Environment", - allowedInMode: ['qualifier', 'playing'] - }, - - // Note Modifiers (some mutually exclusive) - { - id: GameplayModifiers_GameOptions.NoArrows, - name: "No Arrows", - description: "All notes can be cut in any direction", - enabled: false, - group: "Note Modifiers", - incompatibilityGroup: "arrows", - allowedInMode: ['qualifier', 'playing'] - }, - { - id: GameplayModifiers_GameOptions.DisappearingArrows, - name: "Disappearing Arrows", - description: "Arrows disappear as they approach you", - enabled: false, - group: "Note Modifiers", - incompatibilityGroup: "arrows", - allowedInMode: ['qualifier', 'playing'] - }, - { - id: GameplayModifiers_GameOptions.GhostNotes, - name: "Ghost Notes", - description: "Note colors are hidden", - enabled: false, - group: "Note Modifiers", - allowedInMode: ['qualifier', 'playing'] - }, - - // Speed Modifiers (mutually exclusive) - { - id: GameplayModifiers_GameOptions.SlowSong, - name: "Slower Song", - description: "Reduces the song speed by 15%", - enabled: false, - group: "Speed", - incompatibilityGroup: "speed", - allowedInMode: ['qualifier', 'playing'] - }, - { - id: GameplayModifiers_GameOptions.FastSong, - name: "Faster Song", - description: "Increases the song speed by 20%", - enabled: false, - group: "Speed", - incompatibilityGroup: "speed", - allowedInMode: ['qualifier', 'playing'] - }, - { - id: GameplayModifiers_GameOptions.SuperFastSong, - name: "Super Fast Song", - description: "Increases the song speed by 50%", - enabled: false, - group: "Speed", - incompatibilityGroup: "speed", - allowedInMode: ['qualifier', 'playing'] - }, - - // Visual Modifiers (some mutually exclusive) - { - id: GameplayModifiers_GameOptions.SmallCubes, - name: "Small Notes", - description: "Notes are smaller", - enabled: false, - group: "Visual", - allowedInMode: ['qualifier', 'playing'] - }, - { - id: GameplayModifiers_GameOptions.ProMode, - name: "Pro Mode", - description: "Makes notes smaller, removes debris, and adds hit scores", - enabled: false, - group: "Visual", - allowedInMode: ['qualifier', 'playing'] - }, - - // Special Modifiers - { - id: GameplayModifiers_GameOptions.ZenMode, - name: "Zen Mode", - description: "No fail, no bombs, reduced obstacles", - enabled: false, - group: "Special", - allowedInMode: ['playing'] // Only for playing mode - }, - { - id: GameplayModifiers_GameOptions.StrictAngles, - name: "Strict Angles", - description: "Stricter angle enforcement for cuts", - enabled: false, - group: "Special", - allowedInMode: ['qualifier', 'playing'] - } - ]; + let gameModifiers: Modifier[] = allGameModifiers; // Initialize from existing map data function initializeFromMap() { diff --git a/src/lib/components/popups/ResultsForMap.svelte b/src/lib/components/popups/ResultsForMap.svelte new file mode 100644 index 0000000..39286ac --- /dev/null +++ b/src/lib/components/popups/ResultsForMap.svelte @@ -0,0 +1,828 @@ + + +{#if isVisible && mapData} + + + +{/if} + + \ No newline at end of file diff --git a/src/lib/config.json b/src/lib/config.json index ed73de2..1d54d99 100644 --- a/src/lib/config.json +++ b/src/lib/config.json @@ -1,6 +1,7 @@ { - "discordAuthUrl": "https://discord.com/oauth2/authorize?client_id=1348291960552820757&response_type=token&redirect_uri=http%3A%2F%2Flocalhost%3A1420%2FdiscordAuth&scope=identify", + "discordAuthUrl": "https://discord.com/oauth2/authorize?client_id=1348291960552820757&response_type=token&redirect_uri=https%3A%2F%2Fstaging.taui.shyyluna.dev%2FdiscordAuth&scope=identify", "bkAPIUrl": "https://api.beatkhana.com/api", "prodAuthUrl": "https://discord.com/oauth2/authorize?client_id=1348291960552820757&response_type=token&redirect_uri=https%3A%2F%2Ftaui.shyyluna.dev%2FdiscordAuth&scope=identify", - "stagingAuthUrl": "https://discord.com/oauth2/authorize?client_id=1348291960552820757&response_type=token&redirect_uri=https%3A%2F%2Fstaging.taui.shyyluna.dev%2FdiscordAuth&scope=identify" + "stagingAuthUrl": "https://discord.com/oauth2/authorize?client_id=1348291960552820757&response_type=token&redirect_uri=https%3A%2F%2Fstaging.taui.shyyluna.dev%2FdiscordAuth&scope=identify", + "devAuthUrl": "https://discord.com/oauth2/authorize?client_id=1348291960552820757&response_type=token&redirect_uri=http%3A%2F%2Flocalhost%3A1420%2FdiscordAuth&scope=identify" } \ No newline at end of file diff --git a/src/lib/interfaces/match.interfaces.ts b/src/lib/interfaces/match.interfaces.ts new file mode 100644 index 0000000..b1f0b09 --- /dev/null +++ b/src/lib/interfaces/match.interfaces.ts @@ -0,0 +1,61 @@ +// src/lib/types/match.ts +import type { Map, User, RealtimeScore, Push_SongFinished } from 'moons-ta-client'; +import type { BeatSaverMap } from '$lib/services/beatsaver.js'; + +export interface CustomMap { + taData: Map; + beatsaverData: BeatSaverMap; +} + +export interface RealTimeScoreForPlayers { + player: User; + recentScore: RealtimeScore; +} + +export interface ScoreWithAccuracy extends Push_SongFinished { + accuracy: string; +} + +export interface PreviousResults { + taData: Map; + beatsaverData: BeatSaverMap; + scores: ScoreWithAccuracy[]; + completionType: 'Completed' | 'Still Awaiting Scores' | 'Exited To Menu'; +} + +export enum MapDifficulty { + "Easy" = 0, + "Normal" = 1, + "Hard" = 2, + "Expert" = 3, + "ExpertPlus" = 4, +} + +export const colorPresets = { + primary: 'var(--accent-glow)', + secondary: 'var(--text-secondary)', + success: '#4CAF50', + warning: '#FFC107', + error: '#F44336', + info: '#2196F3', + default: 'var(--text-primary)' +} as const; + +export type ColorPreset = keyof typeof colorPresets; +export type ButtonType = 'primary' | 'secondary' | 'text' | 'outlined'; + +export interface ButtonConfig { + text: string; + type: ButtonType; + href?: string; + color?: ColorPreset; + icon?: string; + action?: () => void; +} + +export interface CustomTAMapPool { + guid: string; + name: string; + image: Uint8Array; + maps: CustomMap[]; +} \ No newline at end of file diff --git a/src/lib/services/gamePlayModifiers.ts b/src/lib/services/gamePlayModifiers.ts new file mode 100644 index 0000000..a10ed84 --- /dev/null +++ b/src/lib/services/gamePlayModifiers.ts @@ -0,0 +1,155 @@ +import { Response_ResponseType, Map, Characteristic, GameplayModifiers_GameOptions } from 'moons-ta-client'; + +// Beat Saber Modifiers with incompatibility groups +export interface Modifier { + id: GameplayModifiers_GameOptions; + name: string; + description: string; + enabled: boolean; + group: string; + incompatibilityGroup?: string; // Modifiers in same group are mutually exclusive + allowedInMode?: ('qualifier' | 'playing')[]; +} + +// Game modifiers with proper grouping and mode restrictions +export const allGameModifiers: Modifier[] = [ + // Failure Prevention (mutually exclusive) + { + id: GameplayModifiers_GameOptions.NoFail, + name: "No Fail", + description: "You can't fail the level", + enabled: false, + group: "Failure Prevention", + incompatibilityGroup: "failure", + allowedInMode: ['qualifier', 'playing'] + }, + { + id: GameplayModifiers_GameOptions.InstaFail, + name: "One Life", + description: "You only have one life", + enabled: false, + group: "Failure Prevention", + incompatibilityGroup: "failure", + allowedInMode: ['qualifier', 'playing'] + }, + { + id: GameplayModifiers_GameOptions.BatteryEnergy, + name: "Four Lives", + description: "You have four lives", + enabled: false, + group: "Failure Prevention", + incompatibilityGroup: "failure", + allowedInMode: ['qualifier', 'playing'] + }, + + // Environment Modifiers + { + id: GameplayModifiers_GameOptions.NoBombs, + name: "No Bombs", + description: "No bombs will appear", + enabled: false, + group: "Environment", + allowedInMode: ['qualifier', 'playing'] + }, + { + id: GameplayModifiers_GameOptions.NoObstacles, + name: "No Walls", + description: "No walls will appear", + enabled: false, + group: "Environment", + allowedInMode: ['qualifier', 'playing'] + }, + + // Note Modifiers (some mutually exclusive) + { + id: GameplayModifiers_GameOptions.NoArrows, + name: "No Arrows", + description: "All notes can be cut in any direction", + enabled: false, + group: "Note Modifiers", + incompatibilityGroup: "arrows", + allowedInMode: ['qualifier', 'playing'] + }, + { + id: GameplayModifiers_GameOptions.DisappearingArrows, + name: "Disappearing Arrows", + description: "Arrows disappear as they approach you", + enabled: false, + group: "Note Modifiers", + incompatibilityGroup: "arrows", + allowedInMode: ['qualifier', 'playing'] + }, + { + id: GameplayModifiers_GameOptions.GhostNotes, + name: "Ghost Notes", + description: "Note colors are hidden", + enabled: false, + group: "Note Modifiers", + allowedInMode: ['qualifier', 'playing'] + }, + + // Speed Modifiers (mutually exclusive) + { + id: GameplayModifiers_GameOptions.SlowSong, + name: "Slower Song", + description: "Reduces the song speed by 15%", + enabled: false, + group: "Speed", + incompatibilityGroup: "speed", + allowedInMode: ['qualifier', 'playing'] + }, + { + id: GameplayModifiers_GameOptions.FastSong, + name: "Faster Song", + description: "Increases the song speed by 20%", + enabled: false, + group: "Speed", + incompatibilityGroup: "speed", + allowedInMode: ['qualifier', 'playing'] + }, + { + id: GameplayModifiers_GameOptions.SuperFastSong, + name: "Super Fast Song", + description: "Increases the song speed by 50%", + enabled: false, + group: "Speed", + incompatibilityGroup: "speed", + allowedInMode: ['qualifier', 'playing'] + }, + + // Visual Modifiers (some mutually exclusive) + { + id: GameplayModifiers_GameOptions.SmallCubes, + name: "Small Notes", + description: "Notes are smaller", + enabled: false, + group: "Visual", + allowedInMode: ['qualifier', 'playing'] + }, + { + id: GameplayModifiers_GameOptions.ProMode, + name: "Pro Mode", + description: "Makes notes smaller, removes debris, and adds hit scores", + enabled: false, + group: "Visual", + allowedInMode: ['qualifier', 'playing'] + }, + + // Special Modifiers + { + id: GameplayModifiers_GameOptions.ZenMode, + name: "Zen Mode", + description: "No fail, no bombs, reduced obstacles", + enabled: false, + group: "Special", + allowedInMode: ['playing'] // Only for playing mode + }, + { + id: GameplayModifiers_GameOptions.StrictAngles, + name: "Strict Angles", + description: "Stricter angle enforcement for cuts", + enabled: false, + group: "Special", + allowedInMode: ['qualifier', 'playing'] + } +]; \ No newline at end of file diff --git a/src/routes/tournaments/[tournamentguid]/mappools/[poolGuid]/+page.svelte b/src/routes/tournaments/[tournamentguid]/mappools/[poolGuid]/+page.svelte index ffca3fd..e839549 100644 --- a/src/routes/tournaments/[tournamentguid]/mappools/[poolGuid]/+page.svelte +++ b/src/routes/tournaments/[tournamentguid]/mappools/[poolGuid]/+page.svelte @@ -8,7 +8,10 @@ import Popup from "$lib/components/notifications/Popup.svelte"; import InfoPopup from "$lib/components/notifications/InfoPopup.svelte"; import EditMapModal from "$lib/components/popups/EditMap.svelte"; + import AddMapsFromFile from "$lib/components/popups/AddMapsFromFile.svelte"; + import { type BeatSaverMap } from "$lib/services/beatsaver.js"; import { goto } from "$app/navigation"; + import type { CustomMap } from "$lib/interfaces/match.interfaces.js"; export let data; @@ -19,37 +22,7 @@ let isLoading = false; let error: string | null = null; let poolName: string = ""; - let maps: { - taData: Map, - beatsaverData: BeatSaverMap - }[] = []; - - 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; - }; + let maps: CustomMap[] = []; enum MapDifficulty { "Easy" = 0, @@ -59,17 +32,6 @@ "Expert+" = 4 } - interface Modifier { - id: GameplayModifiers_GameOptions; - name: string; - description: string; - enabled: boolean; - disabled?: boolean; - group?: string; - incompatibilityGroup?: string; - allowedInMode: string[]; - } - const modifierNameMap: Record = { [GameplayModifiers_GameOptions.None]: "", [GameplayModifiers_GameOptions.NoFail]: "No Fail", @@ -108,6 +70,9 @@ // Success notification let showSuccessNotification = false; let successMessage = ""; + + // Import maps popup + let showLoadMapsFromFilePopup: boolean = false; // Remove 'Bearer ' from the token if ($authTokenStore) { @@ -133,6 +98,14 @@ showEditMapModal = false; currentEditMap = null; } + + function openLoadMapsFromFilePopup() { + showLoadMapsFromFilePopup = true; + } + + function closeLoadMapsFromFilePopup() { + showLoadMapsFromFilePopup = false; + } function openDeleteConfirmation(mapId: string) { mapToDelete = mapId; @@ -147,6 +120,21 @@ function closeNotification() { showSuccessNotification = false; } + + async function handleAddMapsFromFile(event: CustomEvent) { + const toAddMaps = (event.detail.maps as CustomMap[]); + + const taDatasToAdd = await toAddMaps.map((map) => map.taData); + const addResponse = await client.addTournamentPoolMaps(tournamentGuid, poolGuid, taDatasToAdd); + + console.log('Added multiple maps response:', addResponse); + fetchMapPoolData(); + showSuccessNotification = true; + setTimeout(() => { + showSuccessNotification = false; + }, 2500); + successMessage = "Map successfully updated!"; + } async function handleMapUpdated(event: CustomEvent) { await client.updateTournamentPoolMap(tournamentGuid, poolGuid, event.detail.map); @@ -331,6 +319,10 @@ add Add Map +
-

Match: {'Unknown'}

+

Match: {match?.guid || 'Undefined'}

{tournament?.settings?.tournamentName}

@@ -865,6 +1008,7 @@ on:editMap={handleEditMap} on:removeMap={handleRemoveMapFromQueue} on:songLoad={handleLoadMap} + on:addMapsFromTAPool={handleShowAddMapsFromTAPool} /> @@ -872,6 +1016,7 @@
@@ -901,6 +1046,21 @@ autoClose={modalAutoClose} /> + + +{#if allowAddFromTAPool} + +{/if} +