diff --git a/package-lock.json b/package-lock.json index 49b0341..5d63e36 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,7 @@ "d3": "^7.9.0", "file-saver": "^2.0.5", "marked": "^14.1.4", - "moons-ta-client": "^1.2.0", + "moons-ta-client": "^1.2.1", "pako": "^2.1.0", "prismjs": "^1.30.0", "uuid": "^11.1.0", @@ -3235,9 +3235,9 @@ } }, "node_modules/moons-ta-client": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/moons-ta-client/-/moons-ta-client-1.2.0.tgz", - "integrity": "sha512-QB/rXVrA4/2JOybg1pVB1qVHH4n3ZbtbQuTi2VpC8+TQLDgOcpbqQE+GsEof6PD2/utIwau+e/qlJQm8bcE2Fw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/moons-ta-client/-/moons-ta-client-1.2.1.tgz", + "integrity": "sha512-oADroR3opRRb0HCja3WbQ3LyXd2hMx9vHnTwDAo4TYm3kc718fiSWSTOIk2G3RZ9dM94fu2t6fT03rbNU8EInw==", "dependencies": { "@protobuf-ts/plugin": "^2.6.0", "events": "^3.3.0", diff --git a/package.json b/package.json index 7855510..9c897cd 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "d3": "^7.9.0", "file-saver": "^2.0.5", "marked": "^14.1.4", - "moons-ta-client": "^1.2.0", + "moons-ta-client": "^1.2.1", "pako": "^2.1.0", "prismjs": "^1.30.0", "uuid": "^11.1.0", diff --git a/src/lib/components/popups/ModifyGameplay.svelte b/src/lib/components/popups/ModifyGameplay.svelte new file mode 100644 index 0000000..4f87eb6 --- /dev/null +++ b/src/lib/components/popups/ModifyGameplay.svelte @@ -0,0 +1,427 @@ + + + + +{#if isVisible} + + +{/if} + + \ No newline at end of file diff --git a/src/lib/interfaces/match.interfaces.ts b/src/lib/interfaces/match.interfaces.ts index b1f0b09..93066d4 100644 --- a/src/lib/interfaces/match.interfaces.ts +++ b/src/lib/interfaces/match.interfaces.ts @@ -12,14 +12,10 @@ export interface RealTimeScoreForPlayers { recentScore: RealtimeScore; } -export interface ScoreWithAccuracy extends Push_SongFinished { - accuracy: string; -} - export interface PreviousResults { taData: Map; beatsaverData: BeatSaverMap; - scores: ScoreWithAccuracy[]; + scores: Push_SongFinished[]; completionType: 'Completed' | 'Still Awaiting Scores' | 'Exited To Menu'; } @@ -56,6 +52,6 @@ export interface ButtonConfig { export interface CustomTAMapPool { guid: string; name: string; - image: Uint8Array; + image: string; maps: CustomMap[]; } \ No newline at end of file diff --git a/src/lib/services/taImages.ts b/src/lib/services/taImages.ts index 766473e..0138b07 100644 --- a/src/lib/services/taImages.ts +++ b/src/lib/services/taImages.ts @@ -34,7 +34,10 @@ export function arrayBufferToBase64(buffer: Uint8Array): string { return btoa(binary); } -export async function convertImageToUint8Array(file: File): Promise { +export async function convertImageToUint8Array(file: File | null): Promise { + if(!file) { + return new Uint8Array([1]); + } return new Promise((resolve, reject) => { // Create a FileReader to read the file const reader = new FileReader(); diff --git a/src/routes/tournaments/+page.svelte b/src/routes/tournaments/+page.svelte index 0da6fbf..e97a6e2 100644 --- a/src/routes/tournaments/+page.svelte +++ b/src/routes/tournaments/+page.svelte @@ -14,6 +14,7 @@ name: string; image: string; guid: string; + myPermissions: string[]; // authorisedUsers: any; } @@ -85,7 +86,7 @@ imageUrl = t.settings.tournamentImage; } // If the image is a Uint8Array - else if (t.settings.tournamentImage instanceof Uint8Array) { + else if (typeof t.settings.tournamentImage === 'object') { // Create and properly dispose of object URLs to prevent memory leaks try { imageUrl = bufferToImageUrl(t.settings.tournamentImage); @@ -109,9 +110,12 @@ name: t.settings?.tournamentName || 'Unnamed Tournament', image: imageUrl, guid: t.guid, + myPermissions: t.settings!.myPermissions // authorisedUsers: authorisedUsersPromise // Wait for the promise to resolve }; })); + + console.log("tournaments", tournaments) } catch (connErr) { console.error('TAClient connection error:', connErr); authError = connErr instanceof Error ? connErr.message : 'Failed to connect to TA server'; @@ -216,7 +220,8 @@ qualifiers: [], settings: { tournamentName: newTournamentName.trim(), - tournamentImage: new Uint8Array([1]), // Default placeholder + // tournamentImage: new Uint8Array([1]), // Default placeholder + tournamentImage: "", enableTeams: false, enablePools: false, showTournamentButton: true, @@ -225,7 +230,9 @@ scoreUpdateFrequency: 30, bannedMods: [], pools: [], - allowUnauthorizedView: true + allowUnauthorizedView: true, + roles: [], + myPermissions: [] }, server: { name: `${$TAServerUrl}:${$TAServerPort}`, @@ -238,7 +245,10 @@ // Convert image to Uint8Array if an image was provided if (newTournamentImage) { try { - tournament.settings!.tournamentImage = await convertImageToUint8Array(newTournamentImage); + // old way of making an image empty in TA, changed in fileserver update + // tournament.settings!.tournamentImage = await convertImageToUint8Array(newTournamentImage); + // new way is just an empty string + tournament.settings!.tournamentImage = ""; console.log("Image converted successfully", tournament.settings!.tournamentImage.length, "bytes"); } catch (error) { console.error("Failed to convert image:", error); @@ -248,8 +258,22 @@ // Create the tournament console.log("Creating tournament:", tournament); - const response = await client.createTournament(tournament); + const response = await client.createTournament( + tournament.server!.address, + tournament.server!.name, + tournament.server!.port.toString(), + tournament.server!.websocketPort.toString(), + tournament.settings!.tournamentName, + await convertImageToUint8Array(newTournamentImage), + false, + false, + true, + true, + + ); console.log("Tournament created:", response); + + const responseTournament = (response.details as any).tournament; // Add the new tournament to the list (for immediate display) let imageUrl = '/images/tournaments/default.jpg'; // Default fallback @@ -260,10 +284,11 @@ tournaments = [ ...tournaments, { - id: tournament.guid.substring(0, 8), - name: tournament.settings!.tournamentName, + id: responseTournament.guid.substring(0, 8), + name: responseTournament.settings!.tournamentName, image: imageUrl, - guid: tournament.guid, + guid: responseTournament.guid, + myPermissions: responseTournament.settings!.myPermissions // authorisedUsers: [] } ]; diff --git a/src/routes/tournaments/[tournamentguid]/matches/[matchGuid]/+page.svelte b/src/routes/tournaments/[tournamentguid]/matches/[matchGuid]/+page.svelte index d80bdb7..9869cac 100644 --- a/src/routes/tournaments/[tournamentguid]/matches/[matchGuid]/+page.svelte +++ b/src/routes/tournaments/[tournamentguid]/matches/[matchGuid]/+page.svelte @@ -15,7 +15,10 @@ Map, User_DownloadStates, User_PlayStates, - Tournament_TournamentSettings_Pool + Tournament_TournamentSettings_Pool, + + Command_ModifyGameplay_Modifier + } from 'moons-ta-client'; import { writable } from "svelte/store"; import { goto } from "$app/navigation"; @@ -26,12 +29,12 @@ import PreviouslyPlayedSongs from "$lib/components/modules/PreviouslyPlayedSongs.svelte"; import ResultsForMap from "$lib/components/popups/ResultsForMap.svelte"; import AddMapsFromTAPool from "$lib/components/popups/AddMapsFromTAPool.svelte"; + import ModifyGameplay from "$lib/components/popups/ModifyGameplay.svelte"; // Types import { type CustomMap, type RealTimeScoreForPlayers, - type ScoreWithAccuracy, type PreviousResults, type ButtonConfig, type ColorPreset, @@ -97,6 +100,10 @@ let showAddFromTAPoolPopup: boolean = false; let allowAddFromTAPool: boolean = true; + // User editing state + let isModifyGameplayPopupVisible: boolean = false; + let editingPlayerPlatformId: string, editingPlayerName: string, editingPlayerPfp: string = ""; + // Active song tracking const activeSongPlayers = new Set<`${string}-${string}`>(); // Format: "userGuid-levelId" @@ -616,14 +623,14 @@ } // Main accuracy calculation logic - function calculateAccuracy(score: Push_SongFinished, mapWithSongInfo: CustomMap): string { + function calculateAccuracy(score: Push_SongFinished, mapWithSongInfo: CustomMap): number { const maxScore = getMaxScore( mapWithSongInfo.beatsaverData, score.beatmap?.characteristic?.serializedName ?? "Standard", getDifficultyAsString(score.beatmap?.difficulty ?? 4) || "ExpertPlus", ); - const accuracy = ((score.score / maxScore) * 100).toFixed(2); + const accuracy = parseFloat(((score.score / maxScore) * 100).toFixed(2)); return accuracy; } @@ -645,7 +652,7 @@ return; } - let newRts: ScoreWithAccuracy = {...rts, accuracy: "0"}; + let newRts: Push_SongFinished = {...rts, accuracy: 0}; newRts.accuracy = calculateAccuracy(rts, map); // Check if any players are still playing this song @@ -806,6 +813,65 @@ } } + async function handleSetGameplayModifiersForUsersVerbose(userIds: string[], modifiers: number) { + try { + const results: any[] = []; + + // Helper function to check if a specific modifier is enabled + const isModifierEnabled = (modifier: Command_ModifyGameplay_Modifier): boolean => { + return (modifiers & (1 << modifier)) !== 0; + }; + + // Apply each modifier if enabled + if (isModifierEnabled(Command_ModifyGameplay_Modifier.InvertColors)) { + console.log('Inverting colors for users:', userIds); + const response = await client.flipColors(tournamentGuid, userIds); + results.push({ type: 'InvertColors', response }); + } + + if (isModifierEnabled(Command_ModifyGameplay_Modifier.InvertHandedness)) { + console.log('Inverting handedness for users:', userIds); + const response = await client.flipHands(tournamentGuid, userIds); + results.push({ type: 'InvertHandedness', response }); + } + + if (isModifierEnabled(Command_ModifyGameplay_Modifier.DisableBlueNotes)) { + console.log('Disabling blue notes for users:', userIds); + const response = await client.disableBlueNotes(tournamentGuid, userIds); + results.push({ type: 'DisableBlueNotes', response }); + } + + if (isModifierEnabled(Command_ModifyGameplay_Modifier.DisableRedNotes)) { + console.log('Disabling red notes for users:', userIds); + const response = await client.disableRedNotes(tournamentGuid, userIds); + results.push({ type: 'DisableRedNotes', response }); + } + + console.log(`Applied ${results.length} modifiers successfully`); + return results; + + } catch (error) { + console.error('Error applying gameplay modifiers:', error); + throw error; + } + } + + async function handleModifyGameplayForPlayer(player: User) { + editingPlayerName = player.name; + editingPlayerPfp = player.discordInfo?.avatarUrl || ""; + editingPlayerPlatformId = player.platformId; + isModifyGameplayPopupVisible = true; + } + + async function handleSendModifyGameplay(event: CustomEvent) { + const { platformId, modifiers } = event.detail; + + // Convert platformId to userIds array if needed + const userIds = [platformId]; // or however you map platform ID to user IDs + + await handleSetGameplayModifiersForUsersVerbose(userIds, modifiers); + } + const beforeUnloadHandler = (event: BeforeUnloadEvent) => { event.preventDefault(); // Not necessary in some browsers but safe }; @@ -1005,9 +1071,15 @@

{User_DownloadStates[player.downloadState]}

{User_PlayStates[player.playState]}

- +
+ + +
+ {/each} @@ -1075,6 +1147,13 @@ /> {/if} + +