diff --git a/client/play/TossupBonusClient.js b/client/play/TossupBonusClient.js index ca0e380bc..793571d12 100644 --- a/client/play/TossupBonusClient.js +++ b/client/play/TossupBonusClient.js @@ -2,7 +2,9 @@ import { BonusClientMixin } from './BonusClient.js'; import { TossupClientMixin } from './TossupClient.js'; import QuestionClient from './QuestionClient.js'; -export default class TossupBonusClient extends BonusClientMixin(TossupClientMixin(QuestionClient)) { +export default class TossupBonusClient extends BonusClientMixin( + TossupClientMixin(QuestionClient) +) { constructor (room, userId, socket) { super(room, userId, socket); attachEventListeners(room, socket); @@ -11,8 +13,10 @@ export default class TossupBonusClient extends BonusClientMixin(TossupClientMixi onmessage (message) { const data = JSON.parse(message); switch (data.type) { - case 'toggle-enable-bonuses': return this.toggleEnableBonuses(data); - default: return super.onmessage(message); + case 'toggle-enable-bonuses': + return this.toggleEnableBonuses(data); + default: + return super.onmessage(message); } } @@ -27,8 +31,13 @@ export default class TossupBonusClient extends BonusClientMixin(TossupClientMixi } function attachEventListeners (room, socket) { - document.getElementById('toggle-enable-bonuses').addEventListener('click', function () { - this.blur(); - socket.sendToServer({ type: 'toggle-enable-bonuses', enableBonuses: this.checked }); - }); + document + .getElementById('toggle-enable-bonuses') + .addEventListener('click', function () { + this.blur(); + socket.sendToServer({ + type: 'toggle-enable-bonuses', + enableBonuses: this.checked + }); + }); } diff --git a/client/play/TossupClient.js b/client/play/TossupClient.js index 8c62e6401..77698e11b 100644 --- a/client/play/TossupClient.js +++ b/client/play/TossupClient.js @@ -3,99 +3,125 @@ import QuestionClient from './QuestionClient.js'; import audio from '../audio/index.js'; import { MODE_ENUM } from '../../quizbowl/constants.js'; -export const TossupClientMixin = (ClientClass) => class extends ClientClass { - constructor (room, userId, socket) { - super(room, userId, socket); - attachEventListeners(room, socket); - } - - onmessage (message) { - const data = JSON.parse(message); - switch (data.type) { - case 'buzz': return this.buzz(data); - case 'end-current-tossup': return this.endCurrentTossup(data); - case 'give-tossup-answer': return this.giveTossupAnswer(data); - case 'pause': return this.pause(data); - case 'reveal-tossup-answer': return this.revealTossupAnswer(data); - case 'set-reading-speed': return this.setReadingSpeed(data); - case 'start-next-tossup': return this.startNextTossup(data); - case 'toggle-powermark-only': return this.togglePowermarkOnly(data); - case 'toggle-rebuzz': return this.toggleRebuzz(data); - case 'update-question': return this.updateQuestion(data); - default: return super.onmessage(message); +export const TossupClientMixin = (ClientClass) => + class extends ClientClass { + constructor (room, userId, socket) { + super(room, userId, socket); + attachEventListeners(room, socket); } - } - buzz ({ userId }) { - document.getElementById('buzz').disabled = true; - document.getElementById('next').disabled = true; - document.getElementById('pause').disabled = true; - if (userId === this.USER_ID && audio.soundEffects) { audio.buzz.play(); } - } + onmessage (message) { + const data = JSON.parse(message); + switch (data.type) { + case 'buzz': + return this.buzz(data); + case 'end-current-tossup': + return this.endCurrentTossup(data); + case 'give-tossup-answer': + return this.giveTossupAnswer(data); + case 'pause': + return this.pause(data); + case 'reveal-tossup-answer': + return this.revealTossupAnswer(data); + case 'set-reading-speed': + return this.setReadingSpeed(data); + case 'start-next-tossup': + return this.startNextTossup(data); + case 'toggle-powermark-only': + return this.togglePowermarkOnly(data); + case 'toggle-rebuzz': + return this.toggleRebuzz(data); + case 'toggle-stop-on-power': + return this.toggleStopOnPower(data); + case 'update-question': + return this.updateQuestion(data); + default: + return super.onmessage(message); + } + } + + buzz ({ userId }) { + document.getElementById('buzz').disabled = true; + document.getElementById('next').disabled = true; + document.getElementById('pause').disabled = true; + if (userId === this.USER_ID && audio.soundEffects) { + audio.buzz.play(); + } + } + + endCurrentTossup ({ starred, tossup }) { + addTossupGameCard({ starred, tossup }); + } + + giveTossupAnswer ({ directive, directedPrompt, score, userId }) { + super.giveAnswer({ directive, directedPrompt, score, userId }); + + if (directive !== 'prompt') { + document.getElementById('next').disabled = false; + } + } + + pause ({ paused }) { + document.getElementById('pause').textContent = paused + ? 'Resume' + : 'Pause'; + } + + revealTossupAnswer ({ answer, question }) { + document.getElementById('question').innerHTML = question; + document.getElementById('answer').innerHTML = 'ANSWER: ' + answer; + document.getElementById('pause').disabled = true; + } + + setMode ({ mode }) { + super.setMode({ mode }); + switch (mode) { + case MODE_ENUM.SET_NAME: + document.getElementById('toggle-powermark-only').disabled = true; + document.getElementById('toggle-standard-only').disabled = true; + break; + case MODE_ENUM.RANDOM: + document.getElementById('toggle-powermark-only').disabled = false; + document.getElementById('toggle-standard-only').disabled = false; + break; + } + } + + setReadingSpeed ({ readingSpeed }) { + document.getElementById('reading-speed').value = readingSpeed; + document.getElementById('reading-speed-display').textContent = + readingSpeed; + } + + startNextTossup ({ tossup, packetLength }) { + this.startNextQuestion({ question: tossup, packetLength }); + document.getElementById('buzz').textContent = 'Buzz'; + document.getElementById('buzz').disabled = false; + document.getElementById('pause').textContent = 'Pause'; + document.getElementById('pause').disabled = false; + this.room.tossup = tossup; + } - endCurrentTossup ({ starred, tossup }) { - addTossupGameCard({ starred, tossup }); - } + togglePowermarkOnly ({ powermarkOnly }) { + document.getElementById('toggle-powermark-only').checked = powermarkOnly; + } - giveTossupAnswer ({ directive, directedPrompt, score, userId }) { - super.giveAnswer({ directive, directedPrompt, score, userId }); + toggleRebuzz ({ rebuzz }) { + document.getElementById('toggle-rebuzz').checked = rebuzz; + } - if (directive !== 'prompt') { - document.getElementById('next').disabled = false; + toggleStopOnPower ({ stopOnPower }) { + console.log('TossupClient.toggleStopOnPower called'); + document.getElementById('toggle-stop-on-power').checked = stopOnPower; } - } - - pause ({ paused }) { - document.getElementById('pause').textContent = paused ? 'Resume' : 'Pause'; - } - - revealTossupAnswer ({ answer, question }) { - document.getElementById('question').innerHTML = question; - document.getElementById('answer').innerHTML = 'ANSWER: ' + answer; - document.getElementById('pause').disabled = true; - } - - setMode ({ mode }) { - super.setMode({ mode }); - switch (mode) { - case MODE_ENUM.SET_NAME: - document.getElementById('toggle-powermark-only').disabled = true; - document.getElementById('toggle-standard-only').disabled = true; - break; - case MODE_ENUM.RANDOM: - document.getElementById('toggle-powermark-only').disabled = false; - document.getElementById('toggle-standard-only').disabled = false; - break; + + updateQuestion ({ word }) { + if (word === '(*)' || word === '[*]') { + return; + } + document.getElementById('question').innerHTML += word + ' '; } - } - - setReadingSpeed ({ readingSpeed }) { - document.getElementById('reading-speed').value = readingSpeed; - document.getElementById('reading-speed-display').textContent = readingSpeed; - } - - startNextTossup ({ tossup, packetLength }) { - this.startNextQuestion({ question: tossup, packetLength }); - document.getElementById('buzz').textContent = 'Buzz'; - document.getElementById('buzz').disabled = false; - document.getElementById('pause').textContent = 'Pause'; - document.getElementById('pause').disabled = false; - this.room.tossup = tossup; - } - - togglePowermarkOnly ({ powermarkOnly }) { - document.getElementById('toggle-powermark-only').checked = powermarkOnly; - } - - toggleRebuzz ({ rebuzz }) { - document.getElementById('toggle-rebuzz').checked = rebuzz; - } - - updateQuestion ({ word }) { - if (word === '(*)' || word === '[*]') { return; } - document.getElementById('question').innerHTML += word + ' '; - } -}; + }; function attachEventListeners (room, socket) { document.getElementById('buzz').addEventListener('click', function () { @@ -106,29 +132,58 @@ function attachEventListeners (room, socket) { document.getElementById('pause').addEventListener('click', function () { this.blur(); - const seconds = parseFloat(document.querySelector('.timer .face').textContent); - const tenths = parseFloat(document.querySelector('.timer .fraction').textContent); + const seconds = parseFloat( + document.querySelector('.timer .face').textContent + ); + const tenths = parseFloat( + document.querySelector('.timer .fraction').textContent + ); const pausedTime = (seconds + tenths) * 10; socket.sendToServer({ type: 'pause', pausedTime }); }); - document.getElementById('reading-speed').addEventListener('change', function () { - socket.sendToServer({ type: 'set-reading-speed', readingSpeed: this.value }); - }); - - document.getElementById('reading-speed').addEventListener('input', function () { - document.getElementById('reading-speed-display').textContent = this.value; - }); - - document.getElementById('toggle-powermark-only').addEventListener('click', function () { - this.blur(); - socket.sendToServer({ type: 'toggle-powermark-only', powermarkOnly: this.checked }); - }); - - document.getElementById('toggle-rebuzz').addEventListener('click', function () { - this.blur(); - socket.sendToServer({ type: 'toggle-rebuzz', rebuzz: this.checked }); - }); + document + .getElementById('reading-speed') + .addEventListener('change', function () { + socket.sendToServer({ + type: 'set-reading-speed', + readingSpeed: this.value + }); + }); + + document + .getElementById('reading-speed') + .addEventListener('input', function () { + document.getElementById('reading-speed-display').textContent = this.value; + }); + + document + .getElementById('toggle-powermark-only') + .addEventListener('click', function () { + this.blur(); + socket.sendToServer({ + type: 'toggle-powermark-only', + powermarkOnly: this.checked + }); + }); + + document + .getElementById('toggle-rebuzz') + .addEventListener('click', function () { + this.blur(); + socket.sendToServer({ type: 'toggle-rebuzz', rebuzz: this.checked }); + }); + + document + .getElementById('toggle-stop-on-power') + .addEventListener('click', function () { + console.log('stop-on-power checkbox has been checked'); + this.blur(); + socket.sendToServer({ + type: 'toggle-stop-on-power', + stopOnPower: this.checked + }); + }); } const TossupClient = TossupClientMixin(QuestionClient); diff --git a/client/play/mp/MultiplayerTossupBonusClient.js b/client/play/mp/MultiplayerTossupBonusClient.js index c6de9bf5c..7db6270d1 100644 --- a/client/play/mp/MultiplayerTossupBonusClient.js +++ b/client/play/mp/MultiplayerTossupBonusClient.js @@ -1,4 +1,3 @@ - import { MODE_ENUM, QUESTION_TYPE_ENUM, TOSSUP_PROGRESS_ENUM } from '../../../quizbowl/constants.js'; import questionStats from '../../scripts/auth/question-stats.js'; import TossupBonusClient from '../TossupBonusClient.js'; @@ -7,726 +6,978 @@ import upsertPlayerItem from '../upsert-player-item.js'; import { setYear } from '../year-slider.js'; export const MultiplayerClientMixin = (ClientClass) => class extends ClientClass { - constructor (room, userId, socket) { - super(room, userId, socket); - this.socket = socket; - } - - onmessage (event) { - const data = JSON.parse(event.data); - switch (data.type) { - case 'chat': return this.chat(data, false); - case 'chat-live-update': return this.chat(data, true); - case 'clear-stats': return this.clearStats(data); - case 'confirm-ban': return this.confirmBan(data); - case 'connection-acknowledged': return this.connectionAcknowledged(data); - case 'connection-acknowledged-query': return this.connectionAcknowledgedQuery(data); - case 'connection-acknowledged-question': return this.connectionAcknowledgedQuestion(data); - case 'enforcing-removal': return this.ackRemovedFromRoom(data); - case 'error': return this.handleError(data); - case 'force-username': return this.forceUsername(data); - case 'give-answer-live-update': return this.logGiveAnswer(data); - case 'initiated-vk': return this.vkInit(data); - case 'join': return this.join(data); - case 'leave': return this.leave(data); - case 'lost-buzzer-race': return this.lostBuzzerRace(data); - case 'mute-player': return this.mutePlayer(data); - case 'no-points-votekick-attempt': return this.failedVotekickPoints(data); - case 'owner-change': return this.ownerChange(data); - case 'set-username': return this.setUsername(data); - case 'successful-vk': return this.vkHandle(data); - case 'toggle-controlled': return this.toggleControlled(data); - case 'toggle-lock': return this.toggleLock(data); - case 'toggle-login-required': return this.toggleLoginRequired(data); - case 'toggle-public': return this.togglePublic(data); - default: return super.onmessage(event.data); - } - } - - // if a banned/kicked user tries to join a this.room they were removed from this is the response - ackRemovedFromRoom ({ removalType }) { - if (removalType === 'kick') { - window.alert('You were kicked from this room by room players, and cannot rejoin it.'); - } else { - window.alert('You were banned from this room by the room owner, and cannot rejoin it.'); - } - setTimeout(() => { - window.location.replace('../'); - }, 100); - } - - buzz ({ userId, username }) { - this.logEventConditionally(username, 'buzzed'); - if (userId === this.USER_ID) { - document.getElementById('answer-input-group').classList.remove('d-none'); - document.getElementById('answer-input').focus(); - } - super.buzz({ userId }); - } - - chat ({ message, userId, username }, live = false) { - if (this.room.muteList.includes(userId)) { - return; - } - if (!live && message === '') { - document.getElementById('live-chat-' + userId).parentElement.remove(); - return; - } - - if (!live && message) { - document.getElementById('live-chat-' + userId).className = ''; - document.getElementById('live-chat-' + userId).id = ''; - return; - } - - if (document.getElementById('live-chat-' + userId)) { - document.getElementById('live-chat-' + userId).textContent = message; - return; - } - - const b = document.createElement('b'); - b.textContent = username; - - const span = document.createElement('span'); - span.classList.add('text-muted'); - span.id = 'live-chat-' + userId; - span.textContent = message; - - const li = document.createElement('li'); - li.appendChild(b); - li.appendChild(document.createTextNode(' ')); - li.appendChild(span); - document.getElementById('room-history').prepend(li); - } - - clearStats ({ userId }) { - for (const field of ['celerity', 'negs', 'points', 'powers', 'tens', 'tuh', 'zeroes']) { - this.room.players[userId][field] = 0; - } - upsertPlayerItem(this.room.players[userId], this.USER_ID, this.room.ownerId, this.socket, this.room.public, this.room.teams[this.room.players[userId].teamId]); - this.sortPlayerListGroup(); - } - - confirmBan ({ targetId, targetUsername }) { - if (targetId === this.USER_ID) { - window.alert('You were banned from this room by the room owner.'); + constructor (room, userId, socket) { + super(room, userId, socket); + this.socket = socket; + } + + onmessage (event) { + const data = JSON.parse(event.data); + switch (data.type) { + case 'chat': return this.chat(data, false); + case 'chat-live-update': return this.chat(data, true); + case 'clear-stats': return this.clearStats(data); + case 'confirm-ban': return this.confirmBan(data); + case 'connection-acknowledged': return this.connectionAcknowledged(data); + case 'connection-acknowledged-query': return this.connectionAcknowledgedQuery(data); + case 'connection-acknowledged-question': return this.connectionAcknowledgedQuestion(data); + case 'enforcing-removal': return this.ackRemovedFromRoom(data); + case 'error': return this.handleError(data); + case 'force-username': return this.forceUsername(data); + case 'give-answer-live-update': return this.logGiveAnswer(data); + case 'initiated-vk': return this.vkInit(data); + case 'join': return this.join(data); + case 'leave': return this.leave(data); + case 'lost-buzzer-race': return this.lostBuzzerRace(data); + case 'mute-player': return this.mutePlayer(data); + case 'no-points-votekick-attempt': return this.failedVotekickPoints(data); + case 'owner-change': return this.ownerChange(data); + case 'set-username': return this.setUsername(data); + case 'successful-vk': return this.vkHandle(data); + case 'toggle-controlled': return this.toggleControlled(data); + case 'toggle-lock': return this.toggleLock(data); + case 'toggle-login-required': return this.toggleLoginRequired(data); + case 'toggle-public': return this.togglePublic(data); + case 'toggle-stop-on-power': return this.toggleStopOnPower(data); + default: return super.onmessage(event.data); + } + } + + // if a banned/kicked user tries to join a this.room they were removed from this is the response + ackRemovedFromRoom ({ removalType }) { + if (removalType === 'kick') { + window.alert('You were kicked from this room by room players, and cannot rejoin it.'); + } else { + window.alert('You were banned from this room by the room owner, and cannot rejoin it.'); + } setTimeout(() => { window.location.replace('../'); }, 100); - } else { - this.logEventConditionally(targetUsername + ' has been banned from this room.'); - } - } - - connectionAcknowledged ({ - bonusEligibleTeamId, - bonusProgress, - buzzedIn, - canBuzz, - currentQuestionType, - isPermanent, - ownerId, - mode, - packetLength, - players, - settings, - packetCount, - teams, - tossupProgress, - userId - }) { - this.room.bonusEligibleTeamId = bonusEligibleTeamId; - this.room.public = settings.public; - this.room.ownerId = ownerId; - this.room.setLength = packetCount; - this.USER_ID = userId; - window.localStorage.setItem('USER_ID', this.USER_ID); - - document.getElementById('buzz').disabled = !canBuzz; - document.getElementById('reveal').disabled = currentQuestionType === QUESTION_TYPE_ENUM.TOSSUP || userId !== bonusEligibleTeamId; - - if (isPermanent) { - document.getElementById('category-select-button').disabled = true; - document.getElementById('toggle-enable-bonuses').disabled = true; - document.getElementById('permanent-room-warning').classList.remove('d-none'); - document.getElementById('reading-speed').disabled = true; - document.getElementById('set-strictness').disabled = true; - document.getElementById('set-mode').disabled = true; - document.getElementById('toggle-public').disabled = true; - } - - for (const userId of Object.keys(players)) { - const teamId = players[userId].teamId; - players[userId].celerity = players[userId].celerity.correct.average; - this.room.players[userId] = players[userId]; - this.room.teams[teamId] = teams[teamId]; - upsertPlayerItem(this.room.players[userId], this.USER_ID, this.room.ownerId, this.socket, this.room.public, this.room.teams[teamId]); - } - this.sortPlayerListGroup(); - - document.getElementById('packet-length-info').textContent = mode === MODE_ENUM.SET_NAME ? packetLength : '-'; - - if (currentQuestionType === QUESTION_TYPE_ENUM.TOSSUP) { - switch (tossupProgress) { - case TOSSUP_PROGRESS_ENUM.NOT_STARTED: - document.getElementById('buzz').disabled = true; - document.getElementById('next').textContent = 'Start'; - document.getElementById('next').classList.remove('btn-primary'); - document.getElementById('next').classList.add('btn-success'); - break; - case TOSSUP_PROGRESS_ENUM.READING: - document.getElementById('next').textContent = 'Skip'; - document.getElementById('settings').classList.add('d-none'); - if (buzzedIn) { - document.getElementById('buzz').disabled = true; - document.getElementById('next').disabled = true; - document.getElementById('pause').disabled = true; - } else { - document.getElementById('buzz').disabled = false; - document.getElementById('pause').disabled = false; - } - break; - case TOSSUP_PROGRESS_ENUM.ANSWER_REVEALED: - document.getElementById('buzz').disabled = true; - document.getElementById('next').textContent = 'Next'; - document.getElementById('settings').classList.add('d-none'); - break; + } + + buzz ({ userId, username }) { + this.logEventConditionally(username, 'buzzed'); + if (userId === this.USER_ID) { + document.getElementById('answer-input-group').classList.remove('d-none'); + document.getElementById('answer-input').focus(); } - } else if (currentQuestionType === QUESTION_TYPE_ENUM.BONUS) { - document.getElementById('buzz').disabled = true; - } - - this.toggleEnableBonuses({ enableBonuses: settings.enableBonuses }); - this.toggleLock({ lock: settings.lock }); - this.toggleLoginRequired({ loginRequired: settings.loginRequired }); - this.toggleRebuzz({ rebuzz: settings.rebuzz }); - this.toggleSkip({ skip: settings.skip }); - this.toggleTimer({ timer: settings.timer }); - this.setMode({ mode }); - this.setReadingSpeed({ readingSpeed: settings.readingSpeed }); - this.setStrictness({ strictness: settings.strictness }); - - if (settings.controlled) { - this.toggleControlled({ controlled: settings.controlled }); - } - if (settings.public) { - this.togglePublic({ public: settings.public }); - } - } - - async connectionAcknowledgedQuery ({ - difficulties = [], - minYear, - maxYear, - packetNumbers = [], - powermarkOnly, - setName = '', - standardOnly, - alternateSubcategories, - categories, - subcategories, - percentView, - categoryPercents - }) { - this.setDifficulties({ difficulties }); - - // need to set min year first to avoid conflicts between saved max year and default min year - setYear(minYear, 'min-year'); - setYear(maxYear, 'max-year'); - - document.getElementById('packet-number').value = arrayToRange(packetNumbers); - document.getElementById('set-name').value = setName; - document.getElementById('toggle-powermark-only').checked = powermarkOnly; - - if (setName !== '' && this.room.setLength === 0) { - document.getElementById('set-name').classList.add('is-invalid'); - } - - document.getElementById('toggle-standard-only').checked = standardOnly; - - this.setCategories({ categories, subcategories, alternateSubcategories, percentView, categoryPercents }); - } - - connectionAcknowledgedQuestion ({ currentQuestionType, question }) { - document.getElementById('set-name-info').textContent = this.question?.set?.name ?? ''; - document.getElementById('packet-number-info').textContent = this.question?.packet?.number ?? '-'; - document.getElementById('question-number-info').textContent = this.question?.number ?? '-'; - - if (currentQuestionType === QUESTION_TYPE_ENUM.TOSSUP) { - this.room.tossup = question; - } else if (currentQuestionType === QUESTION_TYPE_ENUM.BONUS) { - this.room.bonus = question; - } - } - - endCurrentBonus ({ bonus, lastPartRevealed, pointsPerPart, starred, teamId }) { - super.endCurrentBonus({ bonus, starred }); - if (lastPartRevealed) { - const points = pointsPerPart.reduce((a, b) => a + b, 0); - this.room.teams[teamId].bonusStats[points]++; - upsertPlayerItem(this.room.players[teamId], this.USER_ID, this.room.ownerId, this.socket, this.room.public, this.room.teams[teamId]); - this.sortPlayerListGroup(); + super.buzz({ userId }); } - } - failedVotekickPoints ({ userId }) { - if (userId === this.USER_ID) { - window.alert('You can only votekick once you have answered a question correctly!'); + chat ({ message, userId, username }, live = false) { + if (this.room.muteList.includes(userId)) { + return; + } + if (!live && message === '') { + document.getElementById('live-chat-' + userId).parentElement.remove(); + return; + } + + if (!live && message) { + document.getElementById('live-chat-' + userId).className = ''; + document.getElementById('live-chat-' + userId).id = ''; + return; + } + + if (document.getElementById('live-chat-' + userId)) { + document.getElementById('live-chat-' + userId).textContent = message; + return; + } + + const b = document.createElement('b'); + b.textContent = username; + + const span = document.createElement('span'); + span.classList.add('text-muted'); + span.id = 'live-chat-' + userId; + span.textContent = message; + + const li = document.createElement('li'); + li.appendChild(b); + li.appendChild(document.createTextNode(' ')); + li.appendChild(span); + document.getElementById('room-history').prepend(li); } - } - forceUsername ({ message, username }) { - window.alert(message); - window.localStorage.setItem('multiplayer-username', username); - document.querySelector('#username').value = username; - } + clearStats ({ userId }) { + for (const field of ['celerity', 'negs', 'points', 'powers', 'tens', 'tuh', 'zeroes']) { + this.room.players[userId][field] = 0; + } + upsertPlayerItem(this.room.players[userId], this.USER_ID, this.room.ownerId, this.socket, this.room.public, this.room.teams[this.room.players[userId].teamId]); + this.sortPlayerListGroup(); + } - async giveBonusAnswer ({ currentPartNumber, directive, directedPrompt, givenAnswer, score, userId, username }) { - this.logGiveAnswer({ directive, givenAnswer, questionType: QUESTION_TYPE_ENUM.BONUS, username }); - if (directive === 'prompt' && directedPrompt) { - this.logEventConditionally(username, `was prompted with "${directedPrompt}"`); - } else if (directive === 'prompt') { - this.logEventConditionally(username, 'was prompted'); + confirmBan ({ targetId, targetUsername }) { + if (targetId === this.USER_ID) { + window.alert('You were banned from this room by the room owner.'); + setTimeout(() => { + window.location.replace('../'); + }, 100); + } else { + this.logEventConditionally(targetUsername + ' has been banned from this room.'); + } } - super.giveBonusAnswer({ currentPartNumber, directive, directedPrompt, userId }); - } - async giveTossupAnswer ({ celerity, tossup, perQuestionCelerity, directive, directedPrompt, givenAnswer, score, userId, username }) { - this.logGiveAnswer({ directive, givenAnswer, questionType: QUESTION_TYPE_ENUM.TOSSUP, username }); - if (directive === 'prompt' && directedPrompt) { - this.logEventConditionally(username, `was prompted with "${directedPrompt}"`); - } else if (directive === 'prompt') { - this.logEventConditionally(username, 'was prompted'); - } else { - this.logEventConditionally(username, `${score > 0 ? '' : 'in'}correctly answered for ${score} points`); + connectionAcknowledged ({ + bonusEligibleTeamId, + bonusProgress, + buzzedIn, + canBuzz, + currentQuestionType, + isPermanent, + ownerId, + mode, + packetLength, + players, + settings, + packetCount, + teams, + tossupProgress, + userId + }) { + this.room.bonusEligibleTeamId = bonusEligibleTeamId; + this.room.public = settings.public; + this.room.ownerId = ownerId; + this.room.setLength = packetCount; + this.USER_ID = userId; + window.localStorage.setItem('USER_ID', this.USER_ID); + + document.getElementById('buzz').disabled = !canBuzz; + document.getElementById('reveal').disabled = currentQuestionType === QUESTION_TYPE_ENUM.TOSSUP || userId !== bonusEligibleTeamId; + + if (isPermanent) { + document.getElementById('category-select-button').disabled = true; + document.getElementById('toggle-enable-bonuses').disabled = true; + document.getElementById('permanent-room-warning').classList.remove('d-none'); + document.getElementById('reading-speed').disabled = true; + document.getElementById('set-strictness').disabled = true; + document.getElementById('set-mode').disabled = true; + document.getElementById('toggle-public').disabled = true; + } + + for (const userId of Object.keys(players)) { + const teamId = players[userId].teamId; + players[userId].celerity = players[userId].celerity.correct.average; + this.room.players[userId] = players[userId]; + this.room.teams[teamId] = teams[teamId]; + upsertPlayerItem(this.room.players[userId], this.USER_ID, this.room.ownerId, this.socket, this.room.public, this.room.teams[teamId]); + } + this.sortPlayerListGroup(); + + document.getElementById('packet-length-info').textContent = mode === MODE_ENUM.SET_NAME ? packetLength : '-'; + + if (currentQuestionType === QUESTION_TYPE_ENUM.TOSSUP) { + switch (tossupProgress) { + case TOSSUP_PROGRESS_ENUM.NOT_STARTED: + document.getElementById('buzz').disabled = true; + document.getElementById('next').textContent = 'Start'; + document.getElementById('next').classList.remove('btn-primary'); + document.getElementById('next').classList.add('btn-success'); + break; + case TOSSUP_PROGRESS_ENUM.READING: + document.getElementById('next').textContent = 'Skip'; + document.getElementById('settings').classList.add('d-none'); + if (buzzedIn) { + document.getElementById('buzz').disabled = true; + document.getElementById('next').disabled = true; + document.getElementById('pause').disabled = true; + } else { + document.getElementById('buzz').disabled = false; + document.getElementById('pause').disabled = false; + } + break; + case TOSSUP_PROGRESS_ENUM.ANSWER_REVEALED: + document.getElementById('buzz').disabled = true; + document.getElementById('next').textContent = 'Next'; + document.getElementById('settings').classList.add('d-none'); + break; + } + } else if (currentQuestionType === QUESTION_TYPE_ENUM.BONUS) { + document.getElementById('buzz').disabled = true; + } + + this.toggleEnableBonuses({ enableBonuses: settings.enableBonuses }); + this.toggleLock({ lock: settings.lock }); + this.toggleLoginRequired({ loginRequired: settings.loginRequired }); + this.toggleRebuzz({ rebuzz: settings.rebuzz }); + this.toggleSkip({ skip: settings.skip }); + this.toggleTimer({ timer: settings.timer }); + this.setMode({ mode }); + this.setReadingSpeed({ readingSpeed: settings.readingSpeed }); + this.setStrictness({ strictness: settings.strictness }); + + if (settings.controlled) { + this.toggleControlled({ controlled: settings.controlled }); + } + if (settings.public) { + this.togglePublic({ public: settings.public }); + } } - super.giveTossupAnswer({ directive, directedPrompt, givenAnswer, score, userId, username }); - if (directive === 'prompt') { return; } + async connectionAcknowledgedQuery ({ + difficulties = [], + minYear, + maxYear, + packetNumbers = [], + powermarkOnly, + setName = '', + standardOnly, + alternateSubcategories, + categories, + subcategories, + percentView, + categoryPercents + }) { + this.setDifficulties({ difficulties }); + + // need to set min year first to avoid conflicts between saved max year and default min year + setYear(minYear, 'min-year'); + setYear(maxYear, 'max-year'); + + document.getElementById('packet-number').value = + arrayToRange(packetNumbers); + document.getElementById('set-name').value = setName; + document.getElementById('toggle-powermark-only').checked = powermarkOnly; + + if (setName !== '' && this.room.setLength === 0) { + document.getElementById('set-name').classList.add('is-invalid'); + } - document.getElementById('pause').disabled = false; + document.getElementById('toggle-standard-only').checked = standardOnly; - if (directive === 'accept') { - document.getElementById('buzz').disabled = true; - Array.from(document.getElementsByClassName('tuh')).forEach(element => { - element.textContent = parseInt(element.innerHTML) + 1; + this.setCategories({ + categories, + subcategories, + alternateSubcategories, + percentView, + categoryPercents }); - this.room.bonusEligibleTeamId = this.room.players[userId].teamId; } - if (directive === 'reject') { - document.getElementById('buzz').disabled = !document.getElementById('toggle-rebuzz').checked && userId === this.USER_ID; - } + connectionAcknowledgedQuestion ({ currentQuestionType, question }) { + document.getElementById('set-name-info').textContent = + this.question?.set?.name ?? ''; + document.getElementById('packet-number-info').textContent = + this.question?.packet?.number ?? '-'; + document.getElementById('question-number-info').textContent = + this.question?.number ?? '-'; - if (score > 10) { - this.room.players[userId].powers++; - } else if (score === 10) { - this.room.players[userId].tens++; - } else if (score < 0) { - this.room.players[userId].negs++; + if (currentQuestionType === QUESTION_TYPE_ENUM.TOSSUP) { + this.room.tossup = question; + } else if (currentQuestionType === QUESTION_TYPE_ENUM.BONUS) { + this.room.bonus = question; + } } - this.room.players[userId].points += score; - this.room.players[userId].tuh++; - this.room.players[userId].celerity = celerity; + endCurrentBonus ({ + bonus, + lastPartRevealed, + pointsPerPart, + starred, + teamId + }) { + super.endCurrentBonus({ bonus, starred }); + if (lastPartRevealed) { + const points = pointsPerPart.reduce((a, b) => a + b, 0); + this.room.teams[teamId].bonusStats[points]++; + upsertPlayerItem( + this.room.players[teamId], + this.USER_ID, + this.room.ownerId, + this.socket, + this.room.public, + this.room.teams[teamId] + ); + this.sortPlayerListGroup(); + } + } - upsertPlayerItem(this.room.players[userId], this.USER_ID, this.room.ownerId, this.socket, this.room.public, this.room.teams[this.room.players[userId].teamId]); - this.sortPlayerListGroup(); + failedVotekickPoints ({ userId }) { + if (userId === this.USER_ID) { + window.alert( + 'You can only votekick once you have answered a question correctly!' + ); + } + } - if (userId === this.USER_ID) { - questionStats.recordTossup({ - _id: tossup._id, - celerity: perQuestionCelerity, - isCorrect: score > 0, - multiplayer: true, - pointValue: score + forceUsername ({ message, username }) { + window.alert(message); + window.localStorage.setItem('multiplayer-username', username); + document.querySelector('#username').value = username; + } + + async giveBonusAnswer ({ + currentPartNumber, + directive, + directedPrompt, + givenAnswer, + score, + userId, + username + }) { + this.logGiveAnswer({ + directive, + givenAnswer, + questionType: QUESTION_TYPE_ENUM.BONUS, + username + }); + if (directive === 'prompt' && directedPrompt) { + this.logEventConditionally( + username, + `was prompted with "${directedPrompt}"` + ); + } else if (directive === 'prompt') { + this.logEventConditionally(username, 'was prompted'); + } + super.giveBonusAnswer({ + currentPartNumber, + directive, + directedPrompt, + userId }); } - } - handleError ({ message }) { - this.socket.close(3000); - window.alert(message); - window.location.href = '/multiplayer'; - } + async giveTossupAnswer ({ + celerity, + tossup, + perQuestionCelerity, + directive, + directedPrompt, + givenAnswer, + score, + userId, + username + }) { + this.logGiveAnswer({ + directive, + givenAnswer, + questionType: QUESTION_TYPE_ENUM.TOSSUP, + username + }); + if (directive === 'prompt' && directedPrompt) { + this.logEventConditionally( + username, + `was prompted with "${directedPrompt}"` + ); + } else if (directive === 'prompt') { + this.logEventConditionally(username, 'was prompted'); + } else { + this.logEventConditionally( + username, + `${score > 0 ? '' : 'in'}correctly answered for ${score} points` + ); + } + super.giveTossupAnswer({ + directive, + directedPrompt, + givenAnswer, + score, + userId, + username + }); + + if (directive === 'prompt') { + return; + } + + document.getElementById('pause').disabled = false; + + if (directive === 'accept') { + document.getElementById('buzz').disabled = true; + Array.from(document.getElementsByClassName('tuh')).forEach( + (element) => { + element.textContent = parseInt(element.innerHTML) + 1; + } + ); + this.room.bonusEligibleTeamId = this.room.players[userId].teamId; + } + + if (directive === 'reject') { + document.getElementById('buzz').disabled = + !document.getElementById('toggle-rebuzz').checked && + userId === this.USER_ID; + } - join ({ isNew, team, user, userId, username }) { - this.logEventConditionally(username, 'joined the game'); - if (userId === this.USER_ID) { return; } - this.room.players[userId] = user; - this.room.teams[user.teamId] = team; + if (score > 10) { + this.room.players[userId].powers++; + } else if (score === 10) { + this.room.players[userId].tens++; + } else if (score < 0) { + this.room.players[userId].negs++; + } - if (isNew) { - user.celerity = user.celerity.correct.average; - upsertPlayerItem(user, this.USER_ID, this.room.ownerId, this.socket, this.room.public, this.room.teams[user.teamId]); + this.room.players[userId].points += score; + this.room.players[userId].tuh++; + this.room.players[userId].celerity = celerity; + + upsertPlayerItem( + this.room.players[userId], + this.USER_ID, + this.room.ownerId, + this.socket, + this.room.public, + this.room.teams[this.room.players[userId].teamId] + ); this.sortPlayerListGroup(); - } else { - document.getElementById(`list-group-${userId}`).classList.remove('offline'); - document.getElementById('points-' + userId).classList.add('bg-success'); - document.getElementById('points-' + userId).classList.remove('bg-secondary'); - document.getElementById('username-' + userId).textContent = username; - } - } - - leave ({ userId, username }) { - this.logEventConditionally(username, 'left the game'); - this.room.players[userId].online = false; - document.getElementById(`list-group-${userId}`).classList.add('offline'); - document.getElementById(`points-${userId}`).classList.remove('bg-success'); - document.getElementById(`points-${userId}`).classList.add('bg-secondary'); - } - - /** - * Log the event, but only if `username !== undefined`. - * If username is undefined, do nothing, regardless of the value of message. - * @param {string | undefined} username - * @param {string | undefined} message - */ - logEventConditionally (username, message) { - if (username === undefined) { return; } - - const span1 = document.createElement('span'); - span1.textContent = username; - - const span2 = document.createElement('span'); - span2.textContent = message; - - const i = document.createElement('i'); - i.appendChild(span1); - i.appendChild(document.createTextNode(' ')); - i.appendChild(span2); - - const li = document.createElement('li'); - li.appendChild(i); - - document.getElementById('room-history').prepend(li); - } - - logGiveAnswer ({ directive = null, givenAnswer, questionType, username }) { - const badge = document.createElement('span'); - badge.textContent = questionType === QUESTION_TYPE_ENUM.TOSSUP ? 'Buzz' : 'Answer'; - switch (directive) { - case 'accept': - badge.className = 'badge text-dark bg-success'; - break; - case 'reject': - badge.className = 'badge text-light bg-danger'; - break; - case 'prompt': - badge.className = 'badge text-dark bg-warning'; - break; - default: - badge.className = 'badge text-light bg-primary'; - break; - } - - const b = document.createElement('b'); - b.textContent = username; - - const span = document.createElement('span'); - span.textContent = givenAnswer; - - let li; - if (document.getElementById('live-buzz')) { - li = document.getElementById('live-buzz'); - li.textContent = ''; - } else { - li = document.createElement('li'); - li.id = 'live-buzz'; + + if (userId === this.USER_ID) { + questionStats.recordTossup({ + _id: tossup._id, + celerity: perQuestionCelerity, + isCorrect: score > 0, + multiplayer: true, + pointValue: score + }); + } + } + + handleError ({ message }) { + this.socket.close(3000); + window.alert(message); + window.location.href = '/multiplayer'; + } + + join ({ isNew, team, user, userId, username }) { + this.logEventConditionally(username, 'joined the game'); + if (userId === this.USER_ID) { + return; + } + this.room.players[userId] = user; + this.room.teams[user.teamId] = team; + + if (isNew) { + user.celerity = user.celerity.correct.average; + upsertPlayerItem( + user, + this.USER_ID, + this.room.ownerId, + this.socket, + this.room.public, + this.room.teams[user.teamId] + ); + this.sortPlayerListGroup(); + } else { + document + .getElementById(`list-group-${userId}`) + .classList.remove('offline'); + document.getElementById('points-' + userId).classList.add('bg-success'); + document + .getElementById('points-' + userId) + .classList.remove('bg-secondary'); + document.getElementById('username-' + userId).textContent = username; + } + } + + leave ({ userId, username }) { + this.logEventConditionally(username, 'left the game'); + this.room.players[userId].online = false; + document.getElementById(`list-group-${userId}`).classList.add('offline'); + document + .getElementById(`points-${userId}`) + .classList.remove('bg-success'); + document.getElementById(`points-${userId}`).classList.add('bg-secondary'); + } + + /** + * Log the event, but only if `username !== undefined`. + * If username is undefined, do nothing, regardless of the value of message. + * @param {string | undefined} username + * @param {string | undefined} message + */ + logEventConditionally (username, message) { + if (username === undefined) { + return; + } + + const span1 = document.createElement('span'); + span1.textContent = username; + + const span2 = document.createElement('span'); + span2.textContent = message; + + const i = document.createElement('i'); + i.appendChild(span1); + i.appendChild(document.createTextNode(' ')); + i.appendChild(span2); + + const li = document.createElement('li'); + li.appendChild(i); + document.getElementById('room-history').prepend(li); } - li.appendChild(badge); - li.appendChild(document.createTextNode(' ')); - li.appendChild(b); - li.appendChild(document.createTextNode(' ')); - li.appendChild(span); + logGiveAnswer ({ directive = null, givenAnswer, questionType, username }) { + const badge = document.createElement('span'); + badge.textContent = + questionType === QUESTION_TYPE_ENUM.TOSSUP ? 'Buzz' : 'Answer'; + switch (directive) { + case 'accept': + badge.className = 'badge text-dark bg-success'; + break; + case 'reject': + badge.className = 'badge text-light bg-danger'; + break; + case 'prompt': + badge.className = 'badge text-dark bg-warning'; + break; + default: + badge.className = 'badge text-light bg-primary'; + break; + } + + const b = document.createElement('b'); + b.textContent = username; - if (directive === 'accept' || directive === 'reject') { - const secondBadge = document.createElement('span'); - secondBadge.className = badge.className; + const span = document.createElement('span'); + span.textContent = givenAnswer; - if (directive === 'accept') { - secondBadge.textContent = 'Correct'; - } else if (directive === 'reject') { - secondBadge.textContent = 'Incorrect'; + let li; + if (document.getElementById('live-buzz')) { + li = document.getElementById('live-buzz'); + li.textContent = ''; + } else { + li = document.createElement('li'); + li.id = 'live-buzz'; + document.getElementById('room-history').prepend(li); } + li.appendChild(badge); li.appendChild(document.createTextNode(' ')); - li.appendChild(secondBadge); - } + li.appendChild(b); + li.appendChild(document.createTextNode(' ')); + li.appendChild(span); - if (directive) { li.id = ''; } - } + if (directive === 'accept' || directive === 'reject') { + const secondBadge = document.createElement('span'); + secondBadge.className = badge.className; - lostBuzzerRace ({ username, userId }) { - this.logEventConditionally(username, 'lost the buzzer race'); - if (userId === this.USER_ID) { document.getElementById('answer-input-group').classList.add('d-none'); } - } + if (directive === 'accept') { + secondBadge.textContent = 'Correct'; + } else if (directive === 'reject') { + secondBadge.textContent = 'Incorrect'; + } - mutePlayer ({ targetId, targetUsername, muteStatus }) { - if (muteStatus === 'Mute') { - if (!this.room.muteList.includes(targetId)) { - this.room.muteList.push(targetId); - this.logEventConditionally(targetUsername, 'was muted'); + li.appendChild(document.createTextNode(' ')); + li.appendChild(secondBadge); } - } else { - if (this.room.muteList.includes(targetId)) { - this.room.muteList = this.room.muteList.filter(Id => Id !== targetId); - this.logEventConditionally(targetUsername, 'was unmuted'); + + if (directive) { + li.id = ''; } } - } - ownerChange ({ newOwner }) { - if (this.room.players[newOwner]) { - this.room.ownerId = newOwner; - this.logEventConditionally(this.room.players[newOwner].username, 'became the room owner'); - } else this.logEventConditionally(newOwner, 'became the room owner'); + lostBuzzerRace ({ username, userId }) { + this.logEventConditionally(username, 'lost the buzzer race'); + if (userId === this.USER_ID) { + document.getElementById('answer-input-group').classList.add('d-none'); + } + } - Object.keys(this.room.players).forEach((player) => { - upsertPlayerItem(this.room.players[player], this.USER_ID, this.room.ownerId, this.socket, this.room.public, this.room.teams[this.room.players[player].teamId]); - }); + mutePlayer ({ targetId, targetUsername, muteStatus }) { + if (muteStatus === 'Mute') { + if (!this.room.muteList.includes(targetId)) { + this.room.muteList.push(targetId); + this.logEventConditionally(targetUsername, 'was muted'); + } + } else { + if (this.room.muteList.includes(targetId)) { + this.room.muteList = this.room.muteList.filter( + (Id) => Id !== targetId + ); + this.logEventConditionally(targetUsername, 'was unmuted'); + } + } + } - document.getElementById('toggle-controlled').disabled = this.room.public || (this.room.ownerId !== this.USER_ID); - } + ownerChange ({ newOwner }) { + if (this.room.players[newOwner]) { + this.room.ownerId = newOwner; + this.logEventConditionally( + this.room.players[newOwner].username, + 'became the room owner' + ); + } else this.logEventConditionally(newOwner, 'became the room owner'); + + Object.keys(this.room.players).forEach((player) => { + upsertPlayerItem( + this.room.players[player], + this.USER_ID, + this.room.ownerId, + this.socket, + this.room.public, + this.room.teams[this.room.players[player].teamId] + ); + }); - pause ({ paused, username }) { - this.logEventConditionally(username, `${paused ? '' : 'un'}paused the game`); - super.pause({ paused }); - } + document.getElementById('toggle-controlled').disabled = + this.room.public || this.room.ownerId !== this.USER_ID; + } - revealAnswer ({ answer, question }) { - super.revealAnswer({ answer, question }); - document.getElementById('next').textContent = 'Next'; - document.getElementById('next').disabled = false; - } + pause ({ paused, username }) { + this.logEventConditionally( + username, + `${paused ? '' : 'un'}paused the game` + ); + super.pause({ paused }); + } - revealNextAnswer ({ answer, currentPartNumber, lastPartRevealed }) { - super.revealNextAnswer({ answer, currentPartNumber, lastPartRevealed }); - if (lastPartRevealed) { + revealAnswer ({ answer, question }) { + super.revealAnswer({ answer, question }); document.getElementById('next').textContent = 'Next'; document.getElementById('next').disabled = false; } - } - setCategories ({ alternateSubcategories, categories, subcategories, percentView, categoryPercents, username }) { - this.logEventConditionally(username, 'updated the categories'); - this.room.categoryManager.import({ categories, subcategories, alternateSubcategories, percentView, categoryPercents }); - if (!document.getElementById('category-modal')) { return; } - super.setCategories(); - } + revealNextAnswer ({ answer, currentPartNumber, lastPartRevealed }) { + super.revealNextAnswer({ answer, currentPartNumber, lastPartRevealed }); + if (lastPartRevealed) { + document.getElementById('next').textContent = 'Next'; + document.getElementById('next').disabled = false; + } + } + + setCategories ({ + alternateSubcategories, + categories, + subcategories, + percentView, + categoryPercents, + username + }) { + this.logEventConditionally(username, 'updated the categories'); + this.room.categoryManager.import({ + categories, + subcategories, + alternateSubcategories, + percentView, + categoryPercents + }); + if (!document.getElementById('category-modal')) { + return; + } + super.setCategories(); + } + + setDifficulties ({ difficulties, username = undefined }) { + this.logEventConditionally( + username, + difficulties.length > 0 + ? `set the difficulties to ${difficulties}` + : 'cleared the difficulties' + ); + + if (!document.getElementById('difficulties')) { + this.room.difficulties = difficulties; + return; + } + + Array.from(document.getElementById('difficulties').children).forEach( + (li) => { + const input = li.querySelector('input'); + if (difficulties.includes(parseInt(input.value))) { + input.checked = true; + li.classList.add('active'); + } else { + input.checked = false; + li.classList.remove('active'); + } + } + ); + } + + setMinYear ({ minYear, username }) { + const maxYear = parseInt( + document.getElementById('max-year-label').textContent + ); + this.logEventConditionally( + username, + `changed the year range to ${minYear}-${maxYear}` + ); + super.setMinYear({ minYear }); + } + + setMaxYear ({ maxYear, username }) { + const minYear = parseInt( + document.getElementById('min-year-label').textContent + ); + this.logEventConditionally( + username, + `changed the year range to ${minYear}-${maxYear}` + ); + super.setMaxYear({ maxYear }); + } + + setMode ({ mode, username }) { + this.logEventConditionally(username, 'changed the mode to ' + mode); + this.room.mode = mode; + super.setMode({ mode }); + } + + setPacketNumbers ({ username, packetNumbers }) { + super.setPacketNumbers({ packetNumbers }); + this.logEventConditionally( + username, + packetNumbers.length > 0 + ? `changed packet numbers to ${arrayToRange(packetNumbers)}` + : 'cleared packet numbers' + ); + } + + setReadingSpeed ({ username, readingSpeed }) { + super.setReadingSpeed({ readingSpeed }); + this.logEventConditionally( + username, + `changed the reading speed to ${readingSpeed}` + ); + } + + setStrictness ({ strictness, username }) { + this.logEventConditionally( + username, + `changed the strictness to ${strictness}` + ); + super.setStrictness({ strictness }); + } + + setSetName ({ username, setName, setLength }) { + this.logEventConditionally( + username, + setName.length > 0 + ? `changed set name to ${setName}` + : 'cleared set name' + ); + this.room.setLength = setLength; + super.setSetName({ setName, setLength }); + } + + setUsername ({ oldUsername, newUsername, userId }) { + this.logEventConditionally( + oldUsername, + `changed their username to ${newUsername}` + ); + document.getElementById('username-' + userId).textContent = newUsername; + this.room.players[userId].username = newUsername; + this.sortPlayerListGroup(); + + if (userId === this.USER_ID) { + this.room.username = newUsername; + window.localStorage.setItem('multiplayer-username', this.room.username); + document.getElementById('username').value = this.room.username; + } + upsertPlayerItem( + this.room.players[userId], + this.USER_ID, + this.room.ownerId, + this.socket, + this.room.public, + this.room.teams[this.room.players[userId].teamId] + ); + } + + sortPlayerListGroup (descending = true) { + const listGroup = document.getElementById('player-list-group'); + const items = Array.from(listGroup.children); + const offset = 'list-group-'.length; + items + .sort((a, b) => { + const aPoints = parseInt( + document.getElementById('points-' + a.id.substring(offset)) + .textContent + ); + const bPoints = parseInt( + document.getElementById('points-' + b.id.substring(offset)) + .textContent + ); + // if points are equal, sort alphabetically by username + if (aPoints === bPoints) { + const aUsername = document.getElementById( + 'username-' + a.id.substring(offset) + ).textContent; + const bUsername = document.getElementById( + 'username-' + b.id.substring(offset) + ).textContent; + return descending + ? aUsername.localeCompare(bUsername) + : bUsername.localeCompare(aUsername); + } + return descending ? bPoints - aPoints : aPoints - bPoints; + }) + .forEach((item) => { + listGroup.appendChild(item); + }); + } + + startNextQuestion ({ packetLength, question }) { + document.getElementById('next').classList.add('btn-primary'); + document.getElementById('next').classList.remove('btn-success'); + document.getElementById('next').textContent = 'Skip'; + super.startNextQuestion({ packetLength, question }); + } + + startNextBonus ({ bonus, packetLength, username }) { + this.logEventConditionally(username, 'started the next bonus'); + super.startNextBonus({ bonus, packetLength }); + } + + startNextTossup ({ tossup, packetLength, username }) { + this.logEventConditionally(username, 'started the next tossup'); + super.startNextTossup({ tossup, packetLength }); + } + + toggleControlled ({ controlled, username }) { + this.logEventConditionally( + username, + `${controlled ? 'enabled' : 'disabled'} controlled mode` + ); + + document.getElementById('toggle-controlled').checked = controlled; + document + .getElementById('controlled-room-warning') + .classList.toggle('d-none', !controlled); + document.getElementById('toggle-public').disabled = controlled; + + controlled = controlled && this.USER_ID !== this.room.ownerId; + document.getElementById('toggle-enable-bonuses').disabled = controlled; + document.getElementById('toggle-lock').disabled = controlled; + document.getElementById('toggle-login-required').disabled = controlled; + document.getElementById('toggle-timer').disabled = controlled; + document.getElementById('toggle-powermark-only').disabled = controlled; + document.getElementById('toggle-rebuzz').disabled = controlled; + document.getElementById('toggle-skip').disabled = controlled; + document.getElementById('toggle-standard-only').disabled = controlled; + document.getElementById('category-select-button').disabled = controlled; + document.getElementById('reading-speed').disabled = controlled; + document.getElementById('set-mode').disabled = controlled; + document.getElementById('set-strictness').disabled = controlled; + } + + toggleEnableBonuses ({ enableBonuses, username }) { + this.logEventConditionally( + username, + `${enableBonuses ? 'enabled' : 'disabled'} bonuses` + ); + super.toggleEnableBonuses({ enableBonuses }); + } + + toggleLock ({ lock, username }) { + this.logEventConditionally( + username, + `${lock ? 'locked' : 'unlocked'} the room` + ); + document.getElementById('toggle-lock').checked = lock; + } + + toggleLoginRequired ({ loginRequired, username }) { + this.logEventConditionally( + username, + `${loginRequired ? 'enabled' : 'disabled'} requiring players to be logged in` + ); + document.getElementById('toggle-login-required').checked = loginRequired; + } + + togglePowermarkOnly ({ powermarkOnly, username }) { + this.logEventConditionally( + username, + `${powermarkOnly ? 'enabled' : 'disabled'} powermark only` + ); + super.togglePowermarkOnly({ powermarkOnly }); + } + + toggleRebuzz ({ rebuzz, username }) { + this.logEventConditionally( + username, + `${rebuzz ? 'enabled' : 'disabled'} multiple buzzes (effective next question)` + ); + super.toggleRebuzz({ rebuzz }); + } + + toggleSkip ({ skip, username }) { + this.logEventConditionally( + username, + `${skip ? 'enabled' : 'disabled'} skipping` + ); + super.toggleSkip({ skip }); + } + + toggleStandardOnly ({ standardOnly, username }) { + this.logEventConditionally( + username, + `${standardOnly ? 'enabled' : 'disabled'} standard format only` + ); + super.toggleStandardOnly({ standardOnly }); + } + + toggleTimer ({ timer, username }) { + this.logEventConditionally( + username, + `${timer ? 'enabled' : 'disabled'} the timer` + ); + super.toggleTimer({ timer }); + } + + toggleThreePartBonuses ({ threePartBonuses, username }) { + this.logEventConditionally( + username, + `${threePartBonuses ? 'enabled' : 'disabled'} three-part bonuses only` + ); + super.toggleThreePartBonuses({ threePartBonuses }); + } + + togglePublic ({ public: isPublic, username }) { + this.logEventConditionally( + username, + `made the room ${isPublic ? 'public' : 'private'}` + ); + document.getElementById('chat').disabled = isPublic; + document.getElementById('toggle-controlled').disabled = + isPublic || this.room.ownerId !== this.USER_ID; + document.getElementById('toggle-lock').disabled = isPublic; + document.getElementById('toggle-login-required').disabled = isPublic; + document.getElementById('toggle-public').checked = isPublic; + document.getElementById('toggle-timer').disabled = isPublic; + this.room.public = isPublic; + if (isPublic) { + document.getElementById('toggle-lock').checked = false; + document.getElementById('toggle-login-required').checked = false; + this.toggleTimer({ timer: true }); + } + Object.keys(this.room.players).forEach((player) => { + upsertPlayerItem( + this.room.players[player], + this.USER_ID, + this.room.ownerId, + this.socket, + this.room.public, + this.room.teams[this.room.players[player].teamId] + ); + }); + } - setDifficulties ({ difficulties, username = undefined }) { - this.logEventConditionally(username, difficulties.length > 0 ? `set the difficulties to ${difficulties}` : 'cleared the difficulties'); + stopOnPower ({ stopOnPower, username }) { + this.logEventConditionally( + username, + `${stopOnPower ? 'enabled' : 'disabled'} stop on power` + ); + } - if (!document.getElementById('difficulties')) { - this.room.difficulties = difficulties; - return; + vkInit ({ targetUsername, threshold }) { + this.logEventConditionally( + `A votekick has been started against user ${targetUsername} and needs ${threshold} votes to succeed.` + ); } - Array.from(document.getElementById('difficulties').children).forEach(li => { - const input = li.querySelector('input'); - if (difficulties.includes(parseInt(input.value))) { - input.checked = true; - li.classList.add('active'); + vkHandle ({ targetUsername, targetId }) { + if (this.USER_ID === targetId) { + window.alert('You were vote kicked from this room by others.'); + setTimeout(() => { + window.location.replace('../'); + }, 100); } else { - input.checked = false; - li.classList.remove('active'); - } - }); - } - - setMinYear ({ minYear, username }) { - const maxYear = parseInt(document.getElementById('max-year-label').textContent); - this.logEventConditionally(username, `changed the year range to ${minYear}-${maxYear}`); - super.setMinYear({ minYear }); - } - - setMaxYear ({ maxYear, username }) { - const minYear = parseInt(document.getElementById('min-year-label').textContent); - this.logEventConditionally(username, `changed the year range to ${minYear}-${maxYear}`); - super.setMaxYear({ maxYear }); - } - - setMode ({ mode, username }) { - this.logEventConditionally(username, 'changed the mode to ' + mode); - this.room.mode = mode; - super.setMode({ mode }); - } - - setPacketNumbers ({ username, packetNumbers }) { - super.setPacketNumbers({ packetNumbers }); - this.logEventConditionally(username, packetNumbers.length > 0 ? `changed packet numbers to ${arrayToRange(packetNumbers)}` : 'cleared packet numbers'); - } - - setReadingSpeed ({ username, readingSpeed }) { - super.setReadingSpeed({ readingSpeed }); - this.logEventConditionally(username, `changed the reading speed to ${readingSpeed}`); - } - - setStrictness ({ strictness, username }) { - this.logEventConditionally(username, `changed the strictness to ${strictness}`); - super.setStrictness({ strictness }); - } - - setSetName ({ username, setName, setLength }) { - this.logEventConditionally(username, setName.length > 0 ? `changed set name to ${setName}` : 'cleared set name'); - this.room.setLength = setLength; - super.setSetName({ setName, setLength }); - } - - setUsername ({ oldUsername, newUsername, userId }) { - this.logEventConditionally(oldUsername, `changed their username to ${newUsername}`); - document.getElementById('username-' + userId).textContent = newUsername; - this.room.players[userId].username = newUsername; - this.sortPlayerListGroup(); - - if (userId === this.USER_ID) { - this.room.username = newUsername; - window.localStorage.setItem('multiplayer-username', this.room.username); - document.getElementById('username').value = this.room.username; - } - upsertPlayerItem(this.room.players[userId], this.USER_ID, this.room.ownerId, this.socket, this.room.public, this.room.teams[this.room.players[userId].teamId]); - } - - sortPlayerListGroup (descending = true) { - const listGroup = document.getElementById('player-list-group'); - const items = Array.from(listGroup.children); - const offset = 'list-group-'.length; - items.sort((a, b) => { - const aPoints = parseInt(document.getElementById('points-' + a.id.substring(offset)).textContent); - const bPoints = parseInt(document.getElementById('points-' + b.id.substring(offset)).textContent); - // if points are equal, sort alphabetically by username - if (aPoints === bPoints) { - const aUsername = document.getElementById('username-' + a.id.substring(offset)).textContent; - const bUsername = document.getElementById('username-' + b.id.substring(offset)).textContent; - return descending ? aUsername.localeCompare(bUsername) : bUsername.localeCompare(aUsername); - } - return descending ? bPoints - aPoints : aPoints - bPoints; - }).forEach(item => { - listGroup.appendChild(item); - }); - } - - startNextQuestion ({ packetLength, question }) { - document.getElementById('next').classList.add('btn-primary'); - document.getElementById('next').classList.remove('btn-success'); - document.getElementById('next').textContent = 'Skip'; - super.startNextQuestion({ packetLength, question }); - } - - startNextBonus ({ bonus, packetLength, username }) { - this.logEventConditionally(username, 'started the next bonus'); - super.startNextBonus({ bonus, packetLength }); - } - - startNextTossup ({ tossup, packetLength, username }) { - this.logEventConditionally(username, 'started the next tossup'); - super.startNextTossup({ tossup, packetLength }); - } - - toggleControlled ({ controlled, username }) { - this.logEventConditionally(username, `${controlled ? 'enabled' : 'disabled'} controlled mode`); - - document.getElementById('toggle-controlled').checked = controlled; - document.getElementById('controlled-room-warning').classList.toggle('d-none', !controlled); - document.getElementById('toggle-public').disabled = controlled; - - controlled = controlled && (this.USER_ID !== this.room.ownerId); - document.getElementById('toggle-enable-bonuses').disabled = controlled; - document.getElementById('toggle-lock').disabled = controlled; - document.getElementById('toggle-login-required').disabled = controlled; - document.getElementById('toggle-timer').disabled = controlled; - document.getElementById('toggle-powermark-only').disabled = controlled; - document.getElementById('toggle-rebuzz').disabled = controlled; - document.getElementById('toggle-skip').disabled = controlled; - document.getElementById('toggle-standard-only').disabled = controlled; - document.getElementById('category-select-button').disabled = controlled; - document.getElementById('reading-speed').disabled = controlled; - document.getElementById('set-mode').disabled = controlled; - document.getElementById('set-strictness').disabled = controlled; - } - - toggleEnableBonuses ({ enableBonuses, username }) { - this.logEventConditionally(username, `${enableBonuses ? 'enabled' : 'disabled'} bonuses`); - super.toggleEnableBonuses({ enableBonuses }); - } - - toggleLock ({ lock, username }) { - this.logEventConditionally(username, `${lock ? 'locked' : 'unlocked'} the room`); - document.getElementById('toggle-lock').checked = lock; - } - - toggleLoginRequired ({ loginRequired, username }) { - this.logEventConditionally(username, `${loginRequired ? 'enabled' : 'disabled'} requiring players to be logged in`); - document.getElementById('toggle-login-required').checked = loginRequired; - } - - togglePowermarkOnly ({ powermarkOnly, username }) { - this.logEventConditionally(username, `${powermarkOnly ? 'enabled' : 'disabled'} powermark only`); - super.togglePowermarkOnly({ powermarkOnly }); - } - - toggleRebuzz ({ rebuzz, username }) { - this.logEventConditionally(username, `${rebuzz ? 'enabled' : 'disabled'} multiple buzzes (effective next question)`); - super.toggleRebuzz({ rebuzz }); - } - - toggleSkip ({ skip, username }) { - this.logEventConditionally(username, `${skip ? 'enabled' : 'disabled'} skipping`); - super.toggleSkip({ skip }); - } - - toggleStandardOnly ({ standardOnly, username }) { - this.logEventConditionally(username, `${standardOnly ? 'enabled' : 'disabled'} standard format only`); - super.toggleStandardOnly({ standardOnly }); - } - - toggleTimer ({ timer, username }) { - this.logEventConditionally(username, `${timer ? 'enabled' : 'disabled'} the timer`); - super.toggleTimer({ timer }); - } - - toggleThreePartBonuses ({ threePartBonuses, username }) { - this.logEventConditionally(username, `${threePartBonuses ? 'enabled' : 'disabled'} three-part bonuses only`); - super.toggleThreePartBonuses({ threePartBonuses }); - } - - togglePublic ({ public: isPublic, username }) { - this.logEventConditionally(username, `made the room ${isPublic ? 'public' : 'private'}`); - document.getElementById('chat').disabled = isPublic; - document.getElementById('toggle-controlled').disabled = isPublic || (this.room.ownerId !== this.USER_ID); - document.getElementById('toggle-lock').disabled = isPublic; - document.getElementById('toggle-login-required').disabled = isPublic; - document.getElementById('toggle-public').checked = isPublic; - document.getElementById('toggle-timer').disabled = isPublic; - this.room.public = isPublic; - if (isPublic) { - document.getElementById('toggle-lock').checked = false; - document.getElementById('toggle-login-required').checked = false; - this.toggleTimer({ timer: true }); - } - Object.keys(this.room.players).forEach((player) => { - upsertPlayerItem(this.room.players[player], this.USER_ID, this.room.ownerId, this.socket, this.room.public, this.room.teams[this.room.players[player].teamId]); - }); - } - - vkInit ({ targetUsername, threshold }) { - this.logEventConditionally(`A votekick has been started against user ${targetUsername} and needs ${threshold} votes to succeed.`); - } - - vkHandle ({ targetUsername, targetId }) { - if (this.USER_ID === targetId) { - window.alert('You were vote kicked from this room by others.'); - setTimeout(() => { - window.location.replace('../'); - }, 100); - } else { - this.logEventConditionally(targetUsername + ' has been vote kicked from this room.'); + this.logEventConditionally( + targetUsername + ' has been vote kicked from this room.' + ); + } } - } -}; + }; const MultiplayerTossupBonusClient = MultiplayerClientMixin(TossupBonusClient); export default MultiplayerTossupBonusClient; diff --git a/client/play/mp/room.html b/client/play/mp/room.html index 2a8ecca22..91dd4f9f2 100644 --- a/client/play/mp/room.html +++ b/client/play/mp/room.html @@ -1,327 +1,794 @@ +
+