add modifiers
This commit is contained in:
parent
8602b60b02
commit
80f455f1c7
7 changed files with 578 additions and 28 deletions
8
package-lock.json
generated
8
package-lock.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
427
src/lib/components/popups/ModifyGameplay.svelte
Normal file
427
src/lib/components/popups/ModifyGameplay.svelte
Normal file
|
|
@ -0,0 +1,427 @@
|
|||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
// Props
|
||||
export let playerPfp: string;
|
||||
export let playerName: string;
|
||||
export let playerPlatformId: string;
|
||||
export let isVisible: boolean = false;
|
||||
|
||||
// Enum definition (matching your TypeScript enum)
|
||||
enum Command_ModifyGameplay_Modifier {
|
||||
InvertColors = 0,
|
||||
InvertHandedness = 1,
|
||||
DisableBlueNotes = 2,
|
||||
DisableRedNotes = 3
|
||||
}
|
||||
|
||||
// Event dispatcher
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
// Local state for toggles (all start as false/off)
|
||||
let invertColors = false;
|
||||
let invertHandedness = false;
|
||||
let disableBlueNotes = false;
|
||||
let disableRedNotes = false;
|
||||
|
||||
// Calculate bitwise modifiers
|
||||
function calculateModifiers(): number {
|
||||
let modifiers = 0;
|
||||
if (invertColors) modifiers |= (1 << Command_ModifyGameplay_Modifier.InvertColors);
|
||||
if (invertHandedness) modifiers |= (1 << Command_ModifyGameplay_Modifier.InvertHandedness);
|
||||
if (disableBlueNotes) modifiers |= (1 << Command_ModifyGameplay_Modifier.DisableBlueNotes);
|
||||
if (disableRedNotes) modifiers |= (1 << Command_ModifyGameplay_Modifier.DisableRedNotes);
|
||||
return modifiers;
|
||||
}
|
||||
|
||||
// Handle apply modifiers
|
||||
function handleApply() {
|
||||
const modifiers = calculateModifiers();
|
||||
dispatch('sendModifyGameplay', {
|
||||
platformId: playerPlatformId,
|
||||
modifiers: modifiers
|
||||
});
|
||||
isVisible = false;
|
||||
}
|
||||
|
||||
// Handle cancel/close
|
||||
function handleClose() {
|
||||
isVisible = false;
|
||||
}
|
||||
|
||||
// Handle backdrop click
|
||||
function handleBackdropClick(event: MouseEvent) {
|
||||
if (event.target === event.currentTarget) {
|
||||
handleClose();
|
||||
}
|
||||
}
|
||||
|
||||
// Handle escape key
|
||||
function handleKeydown(event: KeyboardEvent) {
|
||||
if (event.key === 'Escape') {
|
||||
handleClose();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window on:keydown={handleKeydown} />
|
||||
|
||||
{#if isVisible}
|
||||
<!-- Backdrop -->
|
||||
<div
|
||||
class="popup-backdrop"
|
||||
on:click={handleBackdropClick}
|
||||
on:keydown={handleKeydown}
|
||||
role="button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<!-- Popup Container -->
|
||||
<div class="popup-container">
|
||||
<!-- Header -->
|
||||
<div class="popup-header">
|
||||
<button class="close-button" on:click={handleClose} aria-label="Close">
|
||||
<span class="material-icons">close</span>
|
||||
</button>
|
||||
<h2>Modify Gameplay</h2>
|
||||
</div>
|
||||
|
||||
<!-- Player Info -->
|
||||
<div class="player-info">
|
||||
<img src={playerPfp} alt={playerName} class="player-avatar" />
|
||||
<div class="player-details">
|
||||
<div class="player-name">{playerName}</div>
|
||||
<div class="player-id">ID: {playerPlatformId}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modifiers Section -->
|
||||
<div class="modifiers-section">
|
||||
<h3>Gameplay Modifiers</h3>
|
||||
|
||||
<!-- Invert Colors Toggle -->
|
||||
<div class="modifier-item">
|
||||
<div class="modifier-info">
|
||||
<span class="material-icons modifier-icon">palette</span>
|
||||
<div class="modifier-text">
|
||||
<div class="modifier-title">Invert Colors</div>
|
||||
<div class="modifier-description">Swap red and blue colors</div>
|
||||
</div>
|
||||
</div>
|
||||
<label class="toggle-switch">
|
||||
<input type="checkbox" bind:checked={invertColors} />
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Invert Handedness Toggle -->
|
||||
<div class="modifier-item">
|
||||
<div class="modifier-info">
|
||||
<span class="material-icons modifier-icon">swap_horiz</span>
|
||||
<div class="modifier-text">
|
||||
<div class="modifier-title">Invert Hands</div>
|
||||
<div class="modifier-description">Swap left and right hand controls</div>
|
||||
</div>
|
||||
</div>
|
||||
<label class="toggle-switch">
|
||||
<input type="checkbox" bind:checked={invertHandedness} />
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Disable Blue Notes Toggle -->
|
||||
<div class="modifier-item">
|
||||
<div class="modifier-info">
|
||||
<span class="material-icons modifier-icon" style="color: #4285f4;">block</span>
|
||||
<div class="modifier-text">
|
||||
<div class="modifier-title">Disable Blue (Right) Notes</div>
|
||||
<div class="modifier-description">Only red (left) notes will appear</div>
|
||||
</div>
|
||||
</div>
|
||||
<label class="toggle-switch">
|
||||
<input type="checkbox" bind:checked={disableBlueNotes} />
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Disable Red Notes Toggle -->
|
||||
<div class="modifier-item">
|
||||
<div class="modifier-info">
|
||||
<span class="material-icons modifier-icon" style="color: #ea4335;">block</span>
|
||||
<div class="modifier-text">
|
||||
<div class="modifier-title">Disable Red (Left) Notes</div>
|
||||
<div class="modifier-description">Only blue (right) notes will appear</div>
|
||||
</div>
|
||||
</div>
|
||||
<label class="toggle-switch">
|
||||
<input type="checkbox" bind:checked={disableRedNotes} />
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="action-buttons">
|
||||
<button class="cancel-button" on:click={handleClose}>
|
||||
<span class="material-icons">cancel</span>
|
||||
Cancel
|
||||
</button>
|
||||
<button class="apply-button" on:click={handleApply}>
|
||||
<span class="material-icons">check_circle</span>
|
||||
Apply Modifiers
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.popup-backdrop {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 9999;
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
|
||||
.popup-container {
|
||||
background-color: #1a1a1a;
|
||||
border-radius: 16px;
|
||||
border: 1px solid #333;
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.5);
|
||||
max-width: 500px;
|
||||
width: 90%;
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
animation: popupAppear 0.2s ease-out;
|
||||
}
|
||||
|
||||
@keyframes popupAppear {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.9) translateY(-20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1) translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.popup-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20px 24px 16px 24px;
|
||||
border-bottom: 1px solid #333;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.close-button {
|
||||
position: absolute;
|
||||
left: 24px;
|
||||
background: none;
|
||||
border: none;
|
||||
color: #999;
|
||||
cursor: pointer;
|
||||
padding: 4px;
|
||||
border-radius: 50%;
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.close-button:hover {
|
||||
color: #fff;
|
||||
background-color: #333;
|
||||
}
|
||||
|
||||
.popup-header h2 {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
color: #fff;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.player-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20px 24px;
|
||||
gap: 16px;
|
||||
border-bottom: 1px solid #333;
|
||||
}
|
||||
|
||||
.player-avatar {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid #444;
|
||||
}
|
||||
|
||||
.player-details {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.player-name {
|
||||
color: #fff;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.player-id {
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.modifiers-section {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.modifiers-section h3 {
|
||||
color: #fff;
|
||||
margin: 0 0 20px 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.modifier-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 16px 0;
|
||||
border-bottom: 1px solid #2a2a2a;
|
||||
}
|
||||
|
||||
.modifier-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.modifier-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.modifier-icon {
|
||||
color: #999;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.modifier-text {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.modifier-title {
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.modifier-description {
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.toggle-switch {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 44px;
|
||||
height: 24px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.toggle-switch input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.toggle-slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: #333;
|
||||
transition: 0.2s ease;
|
||||
border-radius: 24px;
|
||||
}
|
||||
|
||||
.toggle-slider:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
left: 3px;
|
||||
bottom: 3px;
|
||||
background-color: #999;
|
||||
transition: 0.2s ease;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
input:checked + .toggle-slider {
|
||||
background-color: #4285f4;
|
||||
}
|
||||
|
||||
input:checked + .toggle-slider:before {
|
||||
transform: translateX(20px);
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
padding: 20px 24px;
|
||||
border-top: 1px solid #333;
|
||||
}
|
||||
|
||||
.cancel-button,
|
||||
.apply-button {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
padding: 12px 20px;
|
||||
border-radius: 8px;
|
||||
border: none;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.cancel-button {
|
||||
background-color: #333;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.cancel-button:hover {
|
||||
background-color: #444;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.apply-button {
|
||||
background-color: #4285f4;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.apply-button:hover {
|
||||
background-color: #3367d6;
|
||||
}
|
||||
|
||||
.material-icons {
|
||||
font-size: 18px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -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[];
|
||||
}
|
||||
|
|
@ -34,7 +34,10 @@ export function arrayBufferToBase64(buffer: Uint8Array): string {
|
|||
return btoa(binary);
|
||||
}
|
||||
|
||||
export async function convertImageToUint8Array(file: File): Promise<Uint8Array> {
|
||||
export async function convertImageToUint8Array(file: File | null): Promise<Uint8Array> {
|
||||
if(!file) {
|
||||
return new Uint8Array([1]);
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
// Create a FileReader to read the file
|
||||
const reader = new FileReader();
|
||||
|
|
|
|||
|
|
@ -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,9 +258,23 @@
|
|||
|
||||
// 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
|
||||
if (imagePreviewUrl) {
|
||||
|
|
@ -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: []
|
||||
}
|
||||
];
|
||||
|
|
|
|||
|
|
@ -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 @@
|
|||
<p class="player-status-text">{User_DownloadStates[player.downloadState]}</p>
|
||||
<p class="player-status-text">{User_PlayStates[player.playState]}</p>
|
||||
</div>
|
||||
<button class="kick-button" on:click={() => handleKickPlayer(player)}>
|
||||
<span class="material-icons">person_remove</span>
|
||||
</button>
|
||||
<div style="display: inline-block; margin-right: 0.4rem;">
|
||||
<button class="kick-button" on:click={() => handleKickPlayer(player)}>
|
||||
<span class="material-icons">person_remove</span>
|
||||
</button>
|
||||
<button class="modify-button" on:click={() => handleModifyGameplayForPlayer(player)}>
|
||||
<span class="material-icons">settings</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
|
@ -1075,6 +1147,13 @@
|
|||
/>
|
||||
{/if}
|
||||
|
||||
<ModifyGameplay
|
||||
bind:isVisible={isModifyGameplayPopupVisible}
|
||||
playerName={editingPlayerName}
|
||||
playerPfp={editingPlayerPfp}
|
||||
playerPlatformId={editingPlayerPlatformId}
|
||||
/>
|
||||
|
||||
<style>
|
||||
.match-dashboard {
|
||||
display: flex;
|
||||
|
|
@ -1350,6 +1429,26 @@
|
|||
box-shadow: 0 0 10px rgba(239, 68, 68, 0.5);
|
||||
}
|
||||
|
||||
.modify-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
padding: 0.375rem 0.75rem;
|
||||
background-color: var(--accent-color);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 0.375rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.modify-button:hover {
|
||||
background-color: var(--accent-hover);
|
||||
box-shadow: 0 0 10px rgba(239, 68, 68, 0.5);
|
||||
}
|
||||
|
||||
/* Empty State */
|
||||
.empty-state {
|
||||
display: flex;
|
||||
|
|
|
|||
Loading…
Reference in a new issue