diff --git a/src/lib/components/popups/EditMap.svelte b/src/lib/components/popups/EditMap.svelte index 6e74d51..1142ca8 100644 --- a/src/lib/components/popups/EditMap.svelte +++ b/src/lib/components/popups/EditMap.svelte @@ -113,29 +113,18 @@ { id: GameplayModifiers_GameOptions.StrictAngles, name: "Strict Angles", description: "Stricter angle enforcement for cuts", enabled: false, group: "Environment" } ]; - // Group modifiers that can't be active at the same time - const incompatibleModifiers = [ - [GameplayModifiers_GameOptions.SlowSong, GameplayModifiers_GameOptions.FastSong, GameplayModifiers_GameOptions.SuperFastSong], - [GameplayModifiers_GameOptions.InstaFail, GameplayModifiers_GameOptions.BatteryEnergy], - [GameplayModifiers_GameOptions.BatteryEnergy, GameplayModifiers_GameOptions.ZenMode], - [GameplayModifiers_GameOptions.SmallCubes, GameplayModifiers_GameOptions.ProMode], - ]; - // Custom tournament assistant features let limitAttemptsEnabled = false; - if(map?.taData.gameplayParameters?.attempts !== 0) { + if (map?.taData.gameplayParameters?.attempts !== 0) { limitAttemptsEnabled = true; } - let numberOfAttempts = map?.taData.gameplayParameters?.attempts; // Default value + let numberOfAttempts = map?.taData.gameplayParameters?.attempts || 1; - // Initialise modifiers from map data - // map.gameplayParameters?.gameplayModifiers?.options is a decimal number generated by protobuf - if (map && map.taData.gameplayParameters?.gameplayModifiers?.options) { + // Initialize modifiers from map data + if (map?.taData.gameplayParameters?.gameplayModifiers?.options) { const modifierValue = map.taData.gameplayParameters.gameplayModifiers.options; - // Check each modifier to see if its bit is set in the options value for (const modifier of gameModifiers) { - // Bitwise AND to check if this modifier is enabled modifier.enabled = (modifierValue & modifier.id) === modifier.id; } } @@ -154,48 +143,43 @@ return groups; } - function handleModifierChange(modifier: Modifier) { - // Create a new copy of modifiers to work with - const updatedModifiers = [...gameModifiers]; + function updateIncompatibilityStates() { + // Reset all disabled states first + gameModifiers.forEach(modifier => { + modifier.disabled = false; + }); - // Find the modifier in the array and toggle its enabled state - const modifierIndex = updatedModifiers.findIndex(m => m.id === modifier.id); + // Check each enabled modifier for incompatibilities + gameModifiers + .filter(modifier => modifier.enabled && modifier.incompatibilites) + .forEach(enabledModifier => { + enabledModifier.incompatibilites!.forEach(incompatibleId => { + const incompatibleModifier = gameModifiers.find(m => m.id === incompatibleId); + if (incompatibleModifier) { + // Forcefully disable incompatible modifier + incompatibleModifier.enabled = false; + // Lock it so it can't be enabled + incompatibleModifier.disabled = true; + } + }); + }); + } + + function handleModifierChange(changedModifier: Modifier) { + // Find and update the specific modifier + const modifierIndex = gameModifiers.findIndex(m => m.id === changedModifier.id); if (modifierIndex >= 0) { - updatedModifiers[modifierIndex] = { - ...updatedModifiers[modifierIndex], - enabled: modifier.enabled - }; + gameModifiers[modifierIndex] = { ...changedModifier }; } - // if the modifier was just turned on - if(modifier.enabled) { - // check if incompatibilities exist - if(modifier.incompatibilites) { - // fetch all of the incompatible modifiers - let allIncompatableModifiers: Modifier[] = updatedModifiers.filter(x => modifier.incompatibilites?.includes(x.id)); - // loop through all of the incompatable modifiers and set their enabled and disabled states - allIncompatableModifiers.map(async(modifier) => { - modifier.enabled = false; - modifier.disabled = true; - }); - } - } - // if the modifier was just turned off - else { - if(modifier.incompatibilites) { - let allIncompatableModifiers: Modifier[] = updatedModifiers.filter(x => modifier.incompatibilites?.includes(x.id)); - allIncompatableModifiers.map(async(modifier) => { - modifier.disabled = false; - }); - } - } + // Update incompatibility states for all modifiers + updateIncompatibilityStates(); - // Update the state with the new array - gameModifiers = updatedModifiers; + // Trigger reactivity + gameModifiers = [...gameModifiers]; } function getActiveModifiersAsBitmap(): number { - // Combine all enabled modifiers using bitwise OR return gameModifiers .filter(modifier => modifier.enabled) .reduce((bitmap, modifier) => bitmap | modifier.id, 0); @@ -208,9 +192,7 @@ } } - // Handle characteristic selection change function handleCharacteristicChange() { - // Reset difficulty when characteristic changes selectedDifficulty = availableDifficulties[selectedCharacteristic]?.[0] || ""; } @@ -219,10 +201,7 @@ mapLoadError = null; try { - // Try to determine if the input is a hash or a key const isHash = /^[0-9a-fA-F]{40}$/.test(key); - - // Construct the appropriate API endpoint const endpoint = isHash ? `https://api.beatsaver.com/maps/hash/${key}` : `https://api.beatsaver.com/maps/id/${key}`; @@ -234,8 +213,6 @@ } const data: BeatSaverMap = await response.json(); - - // Use the latest version const latestVersion = data.versions[0]; // Map basic details @@ -250,7 +227,6 @@ new Set(latestVersion.diffs.map(diff => diff.characteristic)) ); - // Group difficulties by characteristic availableDifficulties = {}; for (const diff of latestVersion.diffs) { if (!availableDifficulties[diff.characteristic]) { @@ -259,11 +235,9 @@ availableDifficulties[diff.characteristic].push(diff.difficulty); } - // Set initial selections selectedCharacteristic = availableCharacteristics[0] || ""; selectedDifficulty = availableDifficulties[selectedCharacteristic]?.[0] || ""; - // Set Beat Saver ID for the map mapBeatmapId = `custom_level_${latestVersion.hash.toUpperCase()}`; return data; @@ -276,6 +250,18 @@ } } + function getDifficultyNumber(difficulty: string): number { + switch (difficulty) { + case "Easy": return 0; + case "Normal": return 1; + case "Hard": return 2; + case "Expert": return 3; + case "Expert+": + case "ExpertPlus": return 4; + default: return 0; + } + } + async function handleSubmit() { if (!mapName || !mapBeatmapId) { error = "Map name and beatmap ID are required"; @@ -291,54 +277,26 @@ error = null; try { - // Connect to the TA server const connectResult = await client.connect($TAServerUrl, $TAServerPort); if (connectResult.details.oneofKind === "connect" && connectResult.type === Response_ResponseType.Fail) { throw new Error(connectResult.details.connect.message); } - // Join the tournament const joinResult = await client.joinTournament(tournamentGuid); if (joinResult.details.oneofKind !== 'join' || joinResult.type === Response_ResponseType.Fail) { throw new Error('Could not join tournament'); } - // Get modifiers as a bitmap const modifiersBitmap = getActiveModifiersAsBitmap(); - // Add custom data for "Limit Attempts" if enabled - const customData: any = {}; - if (limitAttemptsEnabled) { - customData.limitAttempts = { - enabled: true, - count: numberOfAttempts - }; - } - - let mapDifficulty: number = 0; - if(selectedDifficulty == "Easy") { - mapDifficulty = 0; - } else if(selectedDifficulty == "Normal") { - mapDifficulty = 1; - } else if(selectedDifficulty == "Hard") { - mapDifficulty = 2; - } else if(selectedDifficulty == "Expert") { - mapDifficulty = 3; - } else if(selectedDifficulty == "Expert+") { - mapDifficulty = 4; - } else if(selectedDifficulty == "ExpertPlus") { - mapDifficulty = 4; - } - - // Create a new map object or update existing one const mapData: Map = { guid: map?.taData.guid || uuidv4(), gameplayParameters: { beatmap: { name: mapName, levelId: mapBeatmapId, - difficulty: mapDifficulty, + difficulty: getDifficultyNumber(selectedDifficulty), characteristic: { serializedName: selectedCharacteristic, difficulties: [] @@ -347,7 +305,7 @@ gameplayModifiers: { options: modifiersBitmap }, - attempts: numberOfAttempts || 0, + attempts: limitAttemptsEnabled ? numberOfAttempts : 0, disableCustomNotesOnStream: false, disableFail: false, disablePause: false, @@ -360,13 +318,11 @@ let response; if (map) { - // Update existing map response = await client.updateTournamentPoolMap(tournamentGuid, poolGuid, mapData); if (response.type === Response_ResponseType.Success) { dispatch('mapUpdated'); } } else { - // Add new map response = await client.addTournamentPoolMaps(tournamentGuid, poolGuid, [mapData]); if (response.type === Response_ResponseType.Success) { dispatch('mapAdded'); @@ -391,39 +347,23 @@ dispatch('close'); } - // Initialise from existing map data if available onMount(() => { - // Initialise disabled states based on incompatible groups - for (const incompatibleGroup of incompatibleModifiers) { - // Find any enabled modifier in this group - const enabledModifier = gameModifiers.find(m => - incompatibleGroup.includes(m.id) && m.enabled - ); - - // If there's an enabled modifier, disable others in the same group - if (enabledModifier) { - for (const modifierToUpdate of gameModifiers) { - if (incompatibleGroup.includes(modifierToUpdate.id) && - modifierToUpdate.id !== enabledModifier.id) { - modifierToUpdate.disabled = true; - } - } - } - } - if (map && map.taData.gameplayParameters?.beatmap?.levelId) { - // Extract hash from levelId (format: custom_level_HASH) + // Initialize incompatibility states based on existing enabled modifiers + updateIncompatibilityStates(); + + if (map?.taData.gameplayParameters?.beatmap?.levelId) { const levelIdParts = map.taData.gameplayParameters.beatmap.levelId.split('_'); if (levelIdParts.length === 3) { beatSaverKey = levelIdParts[2]; fetchMapFromBeatSaver(beatSaverKey); - // For existing maps, try to set the characteristic and difficulty if (map.taData.gameplayParameters?.beatmap?.characteristic) { selectedCharacteristic = map.taData.gameplayParameters.beatmap.characteristic.serializedName; } - if (map.taData.gameplayParameters?.beatmap?.difficulty) { - selectedDifficulty = MapDifficulty[map.taData.gameplayParameters.beatmap.characteristic?.difficulties[0]!]; + if (map.taData.gameplayParameters?.beatmap?.difficulty !== undefined) { + const difficultyNames = ["Easy", "Normal", "Hard", "Expert", "Expert+"]; + selectedDifficulty = difficultyNames[map.taData.gameplayParameters.beatmap.difficulty] || "Easy"; } } } diff --git a/src/lib/services/taImages.ts b/src/lib/services/taImages.ts index e6fd428..766473e 100644 --- a/src/lib/services/taImages.ts +++ b/src/lib/services/taImages.ts @@ -151,6 +151,6 @@ export async function linkToUint8Array(link: string): Promise { throw new Error(`Failed to fetch image: ${error instanceof Error ? error.message : String(error)}`); } } else { - throw new Error('Unsupported link format. Must be a data URL or HTTP/HTTPS URL'); + return new Uint8Array(1); } } \ No newline at end of file