SpinShare Referee Bot refbot.ellite.dev/overlay

add ready-check as a valid progresslevel

+183 -177
+1 -1
models/Match.js
··· 65 65 const matchSchema = new Schema({ 66 66 progressLevel: { 67 67 type: String, 68 - enum: ['check-in', 'ban-phase', 'playing', 'picking-post-result', 'finished'], 68 + enum: ['check-in', 'ban-phase', 'ready-check', 'playing', 'picking-post-result', 'finished'], 69 69 default: 'check-in', 70 70 }, 71 71 meta: {
+182 -176
overlay/script.js
··· 1 + /* eslint-disable indent */ 1 2 /* eslint-disable max-statements-per-line */ 2 3 let ws = null; 3 4 let headerHideTimeout = null; ··· 99 100 } 100 101 101 102 function handleMessage({ event, data }) { 103 + console.log(data); 102 104 if (!data) return; 103 105 showMatchView(); 104 106 105 107 if (data.mappool?.length) mappool = data.mappool; 106 108 107 109 switch (event) { 108 - case 'match.checkIn': 109 - updateCheckIn(data, false); 110 - break; 110 + case 'match.checkIn': 111 + updateCheckIn(data, false); 112 + break; 111 113 112 - case 'match.approved': 113 - updateCheckIn(data, true); 114 - addFeed('Check-in approved - match starting!', 'feed-pick'); 115 - break; 114 + case 'match.approved': 115 + updateCheckIn(data, true); 116 + addFeed('Check-in approved - match starting!', 'feed-pick'); 117 + break; 116 118 117 - case 'match.snapshot': { 118 - currentChartName = data.currentChart ?? null; 119 - chartIsLive = data.progressLevel === 'playing'; 120 - p1Ready = data.players?.[0]?.ready ?? false; 121 - p2Ready = data.players?.[1]?.ready ?? false; 122 - updateReadyState(); 123 - updateScoreboard(data); 124 - if (currentChartName) updateCurrentChart(getEntry(currentChartName)); 125 - else clearCurrentChart(); 126 - renderMapPool(); 127 - updatePhaseBarFromState(data); 128 - showMatchView(); 129 - break; 130 - } 119 + case 'match.snapshot': { 120 + currentChartName = data.currentChart ?? null; 121 + chartIsLive = data.progressLevel === 'playing'; 122 + p1Ready = data.players?.[0]?.ready ?? false; 123 + p2Ready = data.players?.[1]?.ready ?? false; 124 + updateReadyState(); 125 + updateScoreboard(data); 126 + if (currentChartName) updateCurrentChart(getEntry(currentChartName)); 127 + else clearCurrentChart(); 128 + renderMapPool(); 129 + updatePhaseBarFromState(data); 130 + showMatchView(); 131 + break; 132 + } 131 133 132 - case 'match.start': 133 - document.getElementById('checkin-banner').classList.remove('visible'); 134 - document.getElementById('end-banner').style.display = 'none'; 135 - currentChartName = null; 136 - chartIsLive = false; 137 - p1Ready = false; 138 - p2Ready = false; 139 - updateReadyState(); 140 - updateScoreboard(data); 141 - renderMapPool(); 142 - clearCurrentChart(); 143 - addFeed('Match started - ban phase beginning', 'feed-pick'); 144 - showMatchView(); 145 - break; 134 + case 'match.start': 135 + document.getElementById('checkin-banner').classList.remove('visible'); 136 + document.getElementById('end-banner').style.display = 'none'; 137 + currentChartName = null; 138 + chartIsLive = false; 139 + p1Ready = false; 140 + p2Ready = false; 141 + updateReadyState(); 142 + updateScoreboard(data); 143 + renderMapPool(); 144 + clearCurrentChart(); 145 + addFeed('Match started - ban phase beginning', 'feed-pick'); 146 + showMatchView(); 147 + break; 146 148 147 - case 'match.banOrderDecided': { 148 - const firstBanner = data.players?.find(p => p.discordId === data.firstBannerDiscordId) 149 - ?? data.players?.find(p => p.discordId === data.banPhase?.currentBannerDiscordId); 150 - const fbn = firstBanner?.displayName ?? '?'; 151 - addFeed(`${fbn} will ban first`, 'feed-ban'); 152 - updatePhaseBar('banning', `${fbn} is banning...`); 153 - updateScoreboard(data); 154 - renderMapPool(); 155 - break; 156 - } 149 + case 'match.banOrderDecided': { 150 + const firstBanner = data.players?.find(p => p.discordId === data.firstBannerDiscordId) 151 + ?? data.players?.find(p => p.discordId === data.banPhase?.currentBannerDiscordId); 152 + const fbn = firstBanner?.displayName ?? '?'; 153 + addFeed(`${fbn} will ban first`, 'feed-ban'); 154 + updatePhaseBar('banning', `${fbn} is banning...`); 155 + updateScoreboard(data); 156 + renderMapPool(); 157 + break; 158 + } 157 159 158 - case 'match.ban': { 159 - const banner = data.players?.find(p => p.discordId === data.bannedByDiscordId); 160 - const bannerName = banner?.displayName ?? 'Someone'; 161 - const bannedEntry = data.mappool?.find(e => e.csvName === data.bannedChart); 162 - const bannedDisplay = bannedEntry ? entryDisplay(bannedEntry) : data.bannedChart; 163 - addFeed(`${bannerName} banned ${bannedDisplay}`, 'feed-ban'); 164 - const nextBanner = data.players?.find(p => p.discordId === data.banPhase?.currentBannerDiscordId); 165 - if (nextBanner) { 166 - updatePhaseBar('banning', `${nextBanner.displayName} is banning...`); 160 + case 'match.ban': { 161 + const banner = data.players?.find(p => p.discordId === data.bannedByDiscordId); 162 + const bannerName = banner?.displayName ?? 'Someone'; 163 + const bannedEntry = data.mappool?.find(e => e.csvName === data.bannedChart); 164 + const bannedDisplay = bannedEntry ? entryDisplay(bannedEntry) : data.bannedChart; 165 + addFeed(`${bannerName} banned ${bannedDisplay}`, 'feed-ban'); 166 + const nextBanner = data.players?.find(p => p.discordId === data.banPhase?.currentBannerDiscordId); 167 + if (nextBanner) { 168 + updatePhaseBar('banning', `${nextBanner.displayName} is banning...`); 169 + } 170 + else { 171 + updatePhaseBar(null); 172 + } 173 + updateScoreboard(data); 174 + renderMapPool(); 175 + break; 167 176 } 168 - else { 169 - updatePhaseBar(null); 170 - } 171 - updateScoreboard(data); 172 - renderMapPool(); 173 - break; 174 - } 175 177 176 - case 'match.firstChartDetermined': { 177 - const entry = getEntry(data.chart ?? data.currentChart); 178 - currentChartName = data.chart ?? data.currentChart ?? null; 179 - chartIsLive = false; 180 - p1Ready = false; 181 - p2Ready = false; 182 - updateReadyState(); 183 - updateScoreboard(data); 184 - if (entry) updateCurrentChart(entry); 185 - renderMapPool(); 186 - addFeed(`Last map standing: ${entry ? entryDisplay(entry) : currentChartName}`, 'feed-pick'); 187 - updatePhaseBar('playing', `Ready check: ${entry ? entryDisplay(entry) : '...'}`); 188 - break; 189 - } 190 - 191 - case 'match.pickPhaseStart': { 192 - const firstPicker = data.players?.find(p => p.discordId === data.currentPickerDiscordId); 193 - const fpn = firstPicker?.displayName ?? '?'; 194 - updatePhaseBar('picking', `${fpn} is picking...`); 195 - updateScoreboard(data); 196 - renderMapPool(); 197 - clearCurrentChart(); 198 - addFeed(`Bans complete - ${fpn} picks first`, 'feed-pick'); 199 - break; 200 - } 201 - 202 - case 'match.pick': { 203 - currentChartName = data.currentChart ?? null; 204 - chartIsLive = false; 205 - p1Ready = false; 206 - p2Ready = false; 207 - updateReadyState(); 208 - updateScoreboard(data); 209 - const pickedEntry = currentChartName ? getEntry(currentChartName) : null; 210 - if (pickedEntry) updateCurrentChart(pickedEntry); 211 - renderMapPool(); 212 - if (pickedEntry) { 213 - const picker = data.players?.find(p => p.discordId === data.pickedByDiscordId); 214 - const pn = picker?.displayName ?? '?'; 215 - addFeed(`${pn} picked ${entryDisplay(pickedEntry)}`, 'feed-pick'); 178 + case 'match.firstChartDetermined': { 179 + const entry = getEntry(data.chart ?? data.currentChart); 180 + currentChartName = data.chart ?? data.currentChart ?? null; 181 + chartIsLive = false; 182 + p1Ready = false; 183 + p2Ready = false; 184 + updateReadyState(); 185 + updateScoreboard(data); 186 + if (entry) updateCurrentChart(entry); 187 + renderMapPool(); 188 + addFeed(`Last map standing: ${entry ? entryDisplay(entry) : currentChartName}`, 'feed-pick'); 189 + updatePhaseBar('playing', `Ready check: ${entry ? entryDisplay(entry) : '...'}`); 190 + break; 216 191 } 217 - updatePhaseBar('playing', `Ready check: ${pickedEntry ? entryDisplay(pickedEntry) : '...'}`); 218 - break; 219 - } 220 192 221 - case 'match.playerReady': { 222 - const prevP1Ready = p1Ready; 223 - const prevP2Ready = p2Ready; 224 - p1Ready = data.players?.[0]?.ready ?? false; 225 - p2Ready = data.players?.[1]?.ready ?? false; 226 - updateReadyState(); 227 - const newlyReadyName = !prevP1Ready && p1Ready 228 - ? (data.players?.[0]?.displayName ?? 'P1') 229 - : !prevP2Ready && p2Ready 230 - ? (data.players?.[1]?.displayName ?? 'P2') 231 - : null; 232 - if (newlyReadyName) addFeed(`${newlyReadyName} is ready!`, 'feed-win'); 233 - break; 234 - } 193 + case 'match.pickPhaseStart': { 194 + const firstPicker = data.players?.find(p => p.discordId === data.currentPickerDiscordId); 195 + const fpn = firstPicker?.displayName ?? '?'; 196 + updatePhaseBar('picking', `${fpn} is picking...`); 197 + updateScoreboard(data); 198 + renderMapPool(); 199 + clearCurrentChart(); 200 + addFeed(`Bans complete - ${fpn} picks first`, 'feed-pick'); 201 + break; 202 + } 235 203 236 - case 'match.chartStart': { 237 - p1Ready = true; 238 - p2Ready = true; 239 - updateReadyState(); 240 - currentChartName = data.currentChart ?? currentChartName; 241 - chartIsLive = true; 242 - updateScoreboard(data); 243 - const liveEntry = currentChartName ? getEntry(currentChartName) : null; 244 - if (liveEntry) updateCurrentChart(liveEntry); 245 - renderMapPool(); 246 - if (liveEntry) addFeed(`Now playing: ${entryDisplay(liveEntry)}`, 'feed-pick'); 247 - updatePhaseBar('playing', `Playing: ${liveEntry ? entryDisplay(liveEntry) : '...'}`); 248 - break; 249 - } 204 + case 'match.pick': { 205 + currentChartName = data.currentChart ?? null; 206 + chartIsLive = false; 207 + p1Ready = false; 208 + p2Ready = false; 209 + updateReadyState(); 210 + updateScoreboard(data); 211 + const pickedEntry = currentChartName ? getEntry(currentChartName) : null; 212 + if (pickedEntry) updateCurrentChart(pickedEntry); 213 + renderMapPool(); 214 + if (pickedEntry) { 215 + const picker = data.players?.find(p => p.discordId === data.pickedByDiscordId); 216 + const pn = picker?.displayName ?? '?'; 217 + addFeed(`${pn} picked ${entryDisplay(pickedEntry)}`, 'feed-pick'); 218 + } 219 + updatePhaseBar('playing', `Ready check: ${pickedEntry ? entryDisplay(pickedEntry) : '...'}`); 220 + break; 221 + } 250 222 251 - case 'match.chartResult': { 252 - chartIsLive = false; 253 - p1Ready = false; 254 - p2Ready = false; 255 - updateReadyState(); 256 - updateScoreboard(data); 257 - const resultEntry = (data.mappool ?? []) 258 - .filter(e => e.status?.played && e.result) 259 - .sort((a, b) => new Date(b.status.playedAt) - new Date(a.status.playedAt))[0] ?? null; 260 - currentChartName = null; 261 - clearCurrentChart(); 262 - renderMapPool(); 263 - const chartTitle = resultEntry ? entryDisplay(resultEntry) : 'Chart'; 264 - const p1n = data.players?.[0]?.displayName ?? 'P1'; 265 - const p2n = data.players?.[1]?.displayName ?? 'P2'; 266 - const result = resultEntry?.result ?? {}; 267 - const s1 = fmtScore(result.score1, result.fc1, result.pfc1); 268 - const s2 = fmtScore(result.score2, result.fc2, result.pfc2); 269 - const winnerPlayer = data.players?.find(p => p.discordId === result.winnerDiscordId); 270 - addFeed(`${chartTitle}: ${p1n} ${s1} vs ${p2n} ${s2} - ${winnerPlayer?.displayName ?? 'someone'} wins!`, 'feed-win'); 271 - const nextPicker = data.players?.find(p => p.discordId === data.currentPickerDiscordId); 272 - if (nextPicker) updatePhaseBar('picking', `${nextPicker.displayName} is picking...`); 273 - break; 274 - } 223 + case 'match.playerReady': { 224 + const prevP1Ready = p1Ready; 225 + const prevP2Ready = p2Ready; 226 + p1Ready = data.players?.[0]?.ready ?? false; 227 + p2Ready = data.players?.[1]?.ready ?? false; 228 + updateReadyState(); 229 + const newlyReadyName = !prevP1Ready && p1Ready 230 + ? (data.players?.[0]?.displayName ?? 'P1') 231 + : !prevP2Ready && p2Ready 232 + ? (data.players?.[1]?.displayName ?? 'P2') 233 + : null; 234 + if (newlyReadyName) addFeed(`${newlyReadyName} is ready!`, 'feed-win'); 235 + break; 236 + } 275 237 276 - case 'match.end': 277 - chartIsLive = false; 278 - p1Ready = false; 279 - p2Ready = false; 280 - updateReadyState(); 281 - updateScoreboard(data); 282 - currentChartName = null; 283 - clearCurrentChart(); 284 - renderMapPool(); 285 - updatePhaseBar(null); 286 - if (data.winner) { 287 - showEndBanner(data.winner); 288 - addFeed(`Match over! ${data.winner} wins!`, 'feed-end'); 238 + case 'match.chartStart': { 239 + p1Ready = true; 240 + p2Ready = true; 241 + updateReadyState(); 242 + currentChartName = data.currentChart ?? currentChartName; 243 + chartIsLive = true; 244 + updateScoreboard(data); 245 + const liveEntry = currentChartName ? getEntry(currentChartName) : null; 246 + if (liveEntry) updateCurrentChart(liveEntry); 247 + renderMapPool(); 248 + if (liveEntry) addFeed(`Now playing: ${entryDisplay(liveEntry)}`, 'feed-pick'); 249 + updatePhaseBar('playing', `Playing: ${liveEntry ? entryDisplay(liveEntry) : '...'}`); 250 + break; 289 251 } 290 - break; 291 252 292 - default: 293 - updateScoreboard(data); 294 - if (data.currentChart) { 295 - currentChartName = data.currentChart; 296 - updateCurrentChart(getEntry(currentChartName)); 253 + case 'match.chartResult': { 254 + chartIsLive = false; 255 + p1Ready = false; 256 + p2Ready = false; 257 + updateReadyState(); 258 + updateScoreboard(data); 259 + const resultEntry = (data.mappool ?? []) 260 + .filter(e => e.status?.played && e.result) 261 + .sort((a, b) => new Date(b.status.playedAt) - new Date(a.status.playedAt))[0] ?? null; 262 + currentChartName = null; 263 + clearCurrentChart(); 264 + renderMapPool(); 265 + const chartTitle = resultEntry ? entryDisplay(resultEntry) : 'Chart'; 266 + const p1n = data.players?.[0]?.displayName ?? 'P1'; 267 + const p2n = data.players?.[1]?.displayName ?? 'P2'; 268 + const result = resultEntry?.result ?? {}; 269 + const s1 = fmtScore(result.score1, result.fc1, result.pfc1); 270 + const s2 = fmtScore(result.score2, result.fc2, result.pfc2); 271 + const winnerPlayer = data.players?.find(p => p.discordId === result.winnerDiscordId); 272 + addFeed(`${chartTitle}: ${p1n} ${s1} vs ${p2n} ${s2} - ${winnerPlayer?.displayName ?? 'someone'} wins!`, 'feed-win'); 273 + const nextPicker = data.players?.find(p => p.discordId === data.currentPickerDiscordId); 274 + if (nextPicker) updatePhaseBar('picking', `${nextPicker.displayName} is picking...`); 275 + break; 297 276 } 298 - renderMapPool(); 277 + 278 + case 'match.end': 279 + chartIsLive = false; 280 + p1Ready = false; 281 + p2Ready = false; 282 + updateReadyState(); 283 + updateScoreboard(data); 284 + currentChartName = null; 285 + clearCurrentChart(); 286 + renderMapPool(); 287 + updatePhaseBar(null); 288 + if (data.winner) { 289 + showEndBanner(data.winner); 290 + addFeed(`Match over! ${data.winner} wins!`, 'feed-end'); 291 + } 292 + break; 293 + 294 + default: 295 + updateScoreboard(data); 296 + if (data.currentChart) { 297 + currentChartName = data.currentChart; 298 + updateCurrentChart(getEntry(currentChartName)); 299 + } 300 + renderMapPool(); 299 301 } 300 302 } 301 303 ··· 305 307 const banner = data.players?.find(p => p.discordId === data.banPhase?.currentBannerDiscordId); 306 308 if (banner) updatePhaseBar('banning', `${banner.displayName} is banning...`); 307 309 else updatePhaseBar(null); 310 + } 311 + else if (level === 'ready-check') { 312 + const entry = data.currentChart ? getEntry(data.currentChart) : null; 313 + updatePhaseBar('ready-check', `Readying up for: ${entry ? entryDisplay(entry) : '...'}`); 308 314 } 309 315 else if (level === 'picking-post-result') { 310 316 const picker = data.players?.find(p => p.discordId === data.currentPickerDiscordId);