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",
|
"d3": "^7.9.0",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"marked": "^14.1.4",
|
"marked": "^14.1.4",
|
||||||
"moons-ta-client": "^1.2.0",
|
"moons-ta-client": "^1.2.1",
|
||||||
"pako": "^2.1.0",
|
"pako": "^2.1.0",
|
||||||
"prismjs": "^1.30.0",
|
"prismjs": "^1.30.0",
|
||||||
"uuid": "^11.1.0",
|
"uuid": "^11.1.0",
|
||||||
|
|
@ -3235,9 +3235,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/moons-ta-client": {
|
"node_modules/moons-ta-client": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/moons-ta-client/-/moons-ta-client-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/moons-ta-client/-/moons-ta-client-1.2.1.tgz",
|
||||||
"integrity": "sha512-QB/rXVrA4/2JOybg1pVB1qVHH4n3ZbtbQuTi2VpC8+TQLDgOcpbqQE+GsEof6PD2/utIwau+e/qlJQm8bcE2Fw==",
|
"integrity": "sha512-oADroR3opRRb0HCja3WbQ3LyXd2hMx9vHnTwDAo4TYm3kc718fiSWSTOIk2G3RZ9dM94fu2t6fT03rbNU8EInw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@protobuf-ts/plugin": "^2.6.0",
|
"@protobuf-ts/plugin": "^2.6.0",
|
||||||
"events": "^3.3.0",
|
"events": "^3.3.0",
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@
|
||||||
"d3": "^7.9.0",
|
"d3": "^7.9.0",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"marked": "^14.1.4",
|
"marked": "^14.1.4",
|
||||||
"moons-ta-client": "^1.2.0",
|
"moons-ta-client": "^1.2.1",
|
||||||
"pako": "^2.1.0",
|
"pako": "^2.1.0",
|
||||||
"prismjs": "^1.30.0",
|
"prismjs": "^1.30.0",
|
||||||
"uuid": "^11.1.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;
|
recentScore: RealtimeScore;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ScoreWithAccuracy extends Push_SongFinished {
|
|
||||||
accuracy: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PreviousResults {
|
export interface PreviousResults {
|
||||||
taData: Map;
|
taData: Map;
|
||||||
beatsaverData: BeatSaverMap;
|
beatsaverData: BeatSaverMap;
|
||||||
scores: ScoreWithAccuracy[];
|
scores: Push_SongFinished[];
|
||||||
completionType: 'Completed' | 'Still Awaiting Scores' | 'Exited To Menu';
|
completionType: 'Completed' | 'Still Awaiting Scores' | 'Exited To Menu';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -56,6 +52,6 @@ export interface ButtonConfig {
|
||||||
export interface CustomTAMapPool {
|
export interface CustomTAMapPool {
|
||||||
guid: string;
|
guid: string;
|
||||||
name: string;
|
name: string;
|
||||||
image: Uint8Array;
|
image: string;
|
||||||
maps: CustomMap[];
|
maps: CustomMap[];
|
||||||
}
|
}
|
||||||
|
|
@ -34,7 +34,10 @@ export function arrayBufferToBase64(buffer: Uint8Array): string {
|
||||||
return btoa(binary);
|
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) => {
|
return new Promise((resolve, reject) => {
|
||||||
// Create a FileReader to read the file
|
// Create a FileReader to read the file
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
name: string;
|
name: string;
|
||||||
image: string;
|
image: string;
|
||||||
guid: string;
|
guid: string;
|
||||||
|
myPermissions: string[];
|
||||||
// authorisedUsers: any;
|
// authorisedUsers: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -85,7 +86,7 @@
|
||||||
imageUrl = t.settings.tournamentImage;
|
imageUrl = t.settings.tournamentImage;
|
||||||
}
|
}
|
||||||
// If the image is a Uint8Array
|
// 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
|
// Create and properly dispose of object URLs to prevent memory leaks
|
||||||
try {
|
try {
|
||||||
imageUrl = bufferToImageUrl(t.settings.tournamentImage);
|
imageUrl = bufferToImageUrl(t.settings.tournamentImage);
|
||||||
|
|
@ -109,9 +110,12 @@
|
||||||
name: t.settings?.tournamentName || 'Unnamed Tournament',
|
name: t.settings?.tournamentName || 'Unnamed Tournament',
|
||||||
image: imageUrl,
|
image: imageUrl,
|
||||||
guid: t.guid,
|
guid: t.guid,
|
||||||
|
myPermissions: t.settings!.myPermissions
|
||||||
// authorisedUsers: authorisedUsersPromise // Wait for the promise to resolve
|
// authorisedUsers: authorisedUsersPromise // Wait for the promise to resolve
|
||||||
};
|
};
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
console.log("tournaments", tournaments)
|
||||||
} catch (connErr) {
|
} catch (connErr) {
|
||||||
console.error('TAClient connection error:', connErr);
|
console.error('TAClient connection error:', connErr);
|
||||||
authError = connErr instanceof Error ? connErr.message : 'Failed to connect to TA server';
|
authError = connErr instanceof Error ? connErr.message : 'Failed to connect to TA server';
|
||||||
|
|
@ -216,7 +220,8 @@
|
||||||
qualifiers: [],
|
qualifiers: [],
|
||||||
settings: {
|
settings: {
|
||||||
tournamentName: newTournamentName.trim(),
|
tournamentName: newTournamentName.trim(),
|
||||||
tournamentImage: new Uint8Array([1]), // Default placeholder
|
// tournamentImage: new Uint8Array([1]), // Default placeholder
|
||||||
|
tournamentImage: "",
|
||||||
enableTeams: false,
|
enableTeams: false,
|
||||||
enablePools: false,
|
enablePools: false,
|
||||||
showTournamentButton: true,
|
showTournamentButton: true,
|
||||||
|
|
@ -225,7 +230,9 @@
|
||||||
scoreUpdateFrequency: 30,
|
scoreUpdateFrequency: 30,
|
||||||
bannedMods: [],
|
bannedMods: [],
|
||||||
pools: [],
|
pools: [],
|
||||||
allowUnauthorizedView: true
|
allowUnauthorizedView: true,
|
||||||
|
roles: [],
|
||||||
|
myPermissions: []
|
||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
name: `${$TAServerUrl}:${$TAServerPort}`,
|
name: `${$TAServerUrl}:${$TAServerPort}`,
|
||||||
|
|
@ -238,7 +245,10 @@
|
||||||
// Convert image to Uint8Array if an image was provided
|
// Convert image to Uint8Array if an image was provided
|
||||||
if (newTournamentImage) {
|
if (newTournamentImage) {
|
||||||
try {
|
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");
|
console.log("Image converted successfully", tournament.settings!.tournamentImage.length, "bytes");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to convert image:", error);
|
console.error("Failed to convert image:", error);
|
||||||
|
|
@ -248,9 +258,23 @@
|
||||||
|
|
||||||
// Create the tournament
|
// Create the tournament
|
||||||
console.log("Creating tournament:", 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);
|
console.log("Tournament created:", response);
|
||||||
|
|
||||||
|
const responseTournament = (response.details as any).tournament;
|
||||||
|
|
||||||
// Add the new tournament to the list (for immediate display)
|
// Add the new tournament to the list (for immediate display)
|
||||||
let imageUrl = '/images/tournaments/default.jpg'; // Default fallback
|
let imageUrl = '/images/tournaments/default.jpg'; // Default fallback
|
||||||
if (imagePreviewUrl) {
|
if (imagePreviewUrl) {
|
||||||
|
|
@ -260,10 +284,11 @@
|
||||||
tournaments = [
|
tournaments = [
|
||||||
...tournaments,
|
...tournaments,
|
||||||
{
|
{
|
||||||
id: tournament.guid.substring(0, 8),
|
id: responseTournament.guid.substring(0, 8),
|
||||||
name: tournament.settings!.tournamentName,
|
name: responseTournament.settings!.tournamentName,
|
||||||
image: imageUrl,
|
image: imageUrl,
|
||||||
guid: tournament.guid,
|
guid: responseTournament.guid,
|
||||||
|
myPermissions: responseTournament.settings!.myPermissions
|
||||||
// authorisedUsers: []
|
// authorisedUsers: []
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,10 @@
|
||||||
Map,
|
Map,
|
||||||
User_DownloadStates,
|
User_DownloadStates,
|
||||||
User_PlayStates,
|
User_PlayStates,
|
||||||
Tournament_TournamentSettings_Pool
|
Tournament_TournamentSettings_Pool,
|
||||||
|
|
||||||
|
Command_ModifyGameplay_Modifier
|
||||||
|
|
||||||
} from 'moons-ta-client';
|
} from 'moons-ta-client';
|
||||||
import { writable } from "svelte/store";
|
import { writable } from "svelte/store";
|
||||||
import { goto } from "$app/navigation";
|
import { goto } from "$app/navigation";
|
||||||
|
|
@ -26,12 +29,12 @@
|
||||||
import PreviouslyPlayedSongs from "$lib/components/modules/PreviouslyPlayedSongs.svelte";
|
import PreviouslyPlayedSongs from "$lib/components/modules/PreviouslyPlayedSongs.svelte";
|
||||||
import ResultsForMap from "$lib/components/popups/ResultsForMap.svelte";
|
import ResultsForMap from "$lib/components/popups/ResultsForMap.svelte";
|
||||||
import AddMapsFromTAPool from "$lib/components/popups/AddMapsFromTAPool.svelte";
|
import AddMapsFromTAPool from "$lib/components/popups/AddMapsFromTAPool.svelte";
|
||||||
|
import ModifyGameplay from "$lib/components/popups/ModifyGameplay.svelte";
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import {
|
import {
|
||||||
type CustomMap,
|
type CustomMap,
|
||||||
type RealTimeScoreForPlayers,
|
type RealTimeScoreForPlayers,
|
||||||
type ScoreWithAccuracy,
|
|
||||||
type PreviousResults,
|
type PreviousResults,
|
||||||
type ButtonConfig,
|
type ButtonConfig,
|
||||||
type ColorPreset,
|
type ColorPreset,
|
||||||
|
|
@ -97,6 +100,10 @@
|
||||||
let showAddFromTAPoolPopup: boolean = false;
|
let showAddFromTAPoolPopup: boolean = false;
|
||||||
let allowAddFromTAPool: boolean = true;
|
let allowAddFromTAPool: boolean = true;
|
||||||
|
|
||||||
|
// User editing state
|
||||||
|
let isModifyGameplayPopupVisible: boolean = false;
|
||||||
|
let editingPlayerPlatformId: string, editingPlayerName: string, editingPlayerPfp: string = "";
|
||||||
|
|
||||||
// Active song tracking
|
// Active song tracking
|
||||||
const activeSongPlayers = new Set<`${string}-${string}`>(); // Format: "userGuid-levelId"
|
const activeSongPlayers = new Set<`${string}-${string}`>(); // Format: "userGuid-levelId"
|
||||||
|
|
||||||
|
|
@ -616,14 +623,14 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// Main accuracy calculation logic
|
// Main accuracy calculation logic
|
||||||
function calculateAccuracy(score: Push_SongFinished, mapWithSongInfo: CustomMap): string {
|
function calculateAccuracy(score: Push_SongFinished, mapWithSongInfo: CustomMap): number {
|
||||||
const maxScore = getMaxScore(
|
const maxScore = getMaxScore(
|
||||||
mapWithSongInfo.beatsaverData,
|
mapWithSongInfo.beatsaverData,
|
||||||
score.beatmap?.characteristic?.serializedName ?? "Standard",
|
score.beatmap?.characteristic?.serializedName ?? "Standard",
|
||||||
getDifficultyAsString(score.beatmap?.difficulty ?? 4) || "ExpertPlus",
|
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;
|
return accuracy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -645,7 +652,7 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let newRts: ScoreWithAccuracy = {...rts, accuracy: "0"};
|
let newRts: Push_SongFinished = {...rts, accuracy: 0};
|
||||||
newRts.accuracy = calculateAccuracy(rts, map);
|
newRts.accuracy = calculateAccuracy(rts, map);
|
||||||
|
|
||||||
// Check if any players are still playing this song
|
// 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) => {
|
const beforeUnloadHandler = (event: BeforeUnloadEvent) => {
|
||||||
event.preventDefault(); // Not necessary in some browsers but safe
|
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_DownloadStates[player.downloadState]}</p>
|
||||||
<p class="player-status-text">{User_PlayStates[player.playState]}</p>
|
<p class="player-status-text">{User_PlayStates[player.playState]}</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div style="display: inline-block; margin-right: 0.4rem;">
|
||||||
<button class="kick-button" on:click={() => handleKickPlayer(player)}>
|
<button class="kick-button" on:click={() => handleKickPlayer(player)}>
|
||||||
<span class="material-icons">person_remove</span>
|
<span class="material-icons">person_remove</span>
|
||||||
</button>
|
</button>
|
||||||
|
<button class="modify-button" on:click={() => handleModifyGameplayForPlayer(player)}>
|
||||||
|
<span class="material-icons">settings</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1075,6 +1147,13 @@
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<ModifyGameplay
|
||||||
|
bind:isVisible={isModifyGameplayPopupVisible}
|
||||||
|
playerName={editingPlayerName}
|
||||||
|
playerPfp={editingPlayerPfp}
|
||||||
|
playerPlatformId={editingPlayerPlatformId}
|
||||||
|
/>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.match-dashboard {
|
.match-dashboard {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -1350,6 +1429,26 @@
|
||||||
box-shadow: 0 0 10px rgba(239, 68, 68, 0.5);
|
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 */
|
||||||
.empty-state {
|
.empty-state {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue