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}
+
+