您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds a user interface to new game mode to keep track of guesses
// ==UserScript== // @name AMQ New Game Mode UI ver.🦝 // @namespace xTsuSaKu // @version 1.20 // @description Adds a user interface to new game mode to keep track of guesses // @author kempanator x racoonseki x xTsuSaKu // @match https://animemusicquiz.com/* // @grant none // @require https://update.greasyfork.org/scripts/534648/1581420/AMQ%20Window%20Script.js // @license MIT // ==/UserScript== /* Author: kempanator x racoonseki x xTsuSaKu. Github: https://github.com/xtsusaku Greasyfork: https://greasyfork.org/en/users/1515915-xtsusaku Ko-fi: https://ko-fi.com/xtsusaku */ "use strict"; if (typeof Listener === "undefined") return; let loadInterval = setInterval(() => { if ($("#loadingScreen").hasClass("hidden")) { clearInterval(loadInterval); setup(); } }, 500); const version = "1.20"; let ngmWindow; let initialGuessCount = []; let guessCounter = []; let countButtons = []; let answers = {}; let teamNumber = null; let teamList = []; let teamSlot = null; let teamNames = []; let correctGuesses = 0; let remainingGuesses = 0; let autoTrackCount = false; let autoThrowSelfCount = false; let autoSendTeamCount = 0; let autocomplete = []; let answerValidation = 1; let sendFireMessage = 0; $("#qpOptionContainer").width($("#qpOptionContainer").width() + 35); $("#qpOptionContainer > div").append($(`<div id="qpNGM" class="clickAble qpOption"> <img class="qpMenuItem" src="https://i.ibb.co/sdmDCN0y/NTR2.png"> </div>`).click(() => ngmWindow.isVisible() ? ngmWindow.close() : ngmWindow.open()).popover({ content: "New Game Mode UI", trigger: "hover", placement: "bottom" })); function setup() { [ ["game chat update", (p) => p.messages.forEach(m => m.sender === selfName && parseMessage(m.message))], ["Game Chat Message", (p) => p.sender === selfName && parseMessage(p.message)], ["Game Starting", (p) => { let self = p.players.find(pl => pl.name === selfName); (self?.inGame && hostModal.$teamSize.slider("getValue") > 1 && hostModal.$scoring.slider("getValue") === quiz.SCORE_TYPE_IDS.LIVES) ? updateWindow(p.players) : clearWindow(); }], ["Join Game", (p) => (p.quizState && p.settings.teamSize > 1 && p.settings.scoreType === quiz.SCORE_TYPE_IDS.LIVES) ? updateWindow(p.quizState.players) : clearWindow()], ["Spectate Game", clearWindow], ["quiz over", clearWindow], ["play next song", () => { if (quiz.teamMode && !quiz.isSpectator && hostModal.$scoring.slider("getValue") === quiz.SCORE_TYPE_IDS.LIVES) { answers = {}; if (autoThrowSelfCount && guessCounter.length) { setTimeout(() => socket.sendCommand({ type: "quiz", command: "quiz answer", data: { answer: String(guessCounter[teamSlot]) } }), 100); } } }], ["team member answer", (p) => { if (quiz.teamMode && hostModal.$scoring.slider("getValue") === quiz.SCORE_TYPE_IDS.LIVES) { answers[p.gamePlayerId] = { id: p.gamePlayerId, text: p.answer }; } }], ["player answered", (data) => { if (quiz.teamMode && !quiz.isSpectator && hostModal.$scoring.slider("getValue") === quiz.SCORE_TYPE_IDS.LIVES) { data.forEach(({ gamePlayerIds, answerTime }) => { gamePlayerIds.forEach(id => { if(Object.keys(answers).includes(id.toString())){ answers[id].speed = answerTime; answers[id].valid = autocomplete.includes(answers[id].text.toLowerCase()); } }) }) } }], ["answer results", (p) => { if (!(quiz.teamMode && !quiz.isSpectator && hostModal.$scoring.slider("getValue") === quiz.SCORE_TYPE_IDS.LIVES)) return; const me = p.players.find(pl => pl.gamePlayerId === quiz.ownGamePlayerId); if (!me || me.score <= 0) return; if (autoTrackCount && Object.keys(answers).length) { const selfPlayer = p.players.find(pl => pl.gamePlayerId === quiz.ownGamePlayerId); const allCorrect = [...p.songInfo.altAnimeNames, ...p.songInfo.altAnimeNamesAnswers].map(x => x.toLowerCase()); const correct = Object.values(answers).filter(a => allCorrect.includes(a.text.toLowerCase())); let validSet = correct; if (answerValidation === 1 && validSet.length === 0) { const allAnswers = Object.values(answers).map(a => a.text.trim().toLowerCase()); const combined = allAnswers.join(" "); if (allCorrect.includes(combined)) { const freq = {}; allAnswers.forEach(text => { freq[text] = (freq[text] || 0) + 1; }); const repeated = Object.entries(freq).find(([word, count]) => count > 1); if (repeated) { const [word] = repeated; const contributors = Object.values(answers).filter(a => a.text.trim().toLowerCase() === word); const chosen = contributors[Math.floor(Math.random() * contributors.length)]; const index = teamList.indexOf(chosen.id); countButtons[index].addClass("ngmAnimateCorrect"); setTimeout(() => countButtons[index].removeClass("ngmAnimateCorrect"), 2000); guessCounter[index]--; if (guessCounter.every(x => x <= 0)) guessCounter = [...initialGuessCount]; countButtons.forEach((btn, i) => btn.text(guessCounter[i])); if (sendFireMessage === 1 || sendFireMessage === 2) { const chosenName = teamNames[index]; const otherNames = contributors .filter(p => p && typeof p === "object" && p.id !== chosen.id) .map(p => teamNames[teamList.indexOf(p.id)]) .join(", "); const msg = otherNames ? `${otherNames} กระสุนด้าน ${chosenName} ลั่นกระสุน!! (เหลือ ${guessCounter[index]} นัด)` : `${chosenName} ลั่นกระสุน!! (เหลือ ${guessCounter[index]} นัด)`; sendChatMessage(msg, sendFireMessage === 1); } if (autoSendTeamCount) sendChatMessage(guessCounter.join(" "), autoSendTeamCount === 1); return; } } validSet = Object.values(answers).filter(a => a.text.trim()); } if (selfPlayer.correct && validSet.length) { const fastest = Math.min(...validSet.map(a => a.speed)); const fastestPlayers = validSet.filter(a => a.speed === fastest); if (fastestPlayers.length >= 1) { const chosen = fastestPlayers[Math.floor(Math.random() * fastestPlayers.length)]; const index = teamList.indexOf(chosen.id); countButtons[index].addClass("ngmAnimateCorrect"); setTimeout(() => countButtons[index].removeClass("ngmAnimateCorrect"), 2000); guessCounter[index]--; if (guessCounter.every(x => x <= 0)) guessCounter = [...initialGuessCount]; countButtons.forEach((btn, i) => btn.text(guessCounter[i])); if (sendFireMessage === 1 || sendFireMessage === 2) { const chosenName = teamNames[index]; const otherNames = fastestPlayers .filter(p => p && typeof p === "object" && p.id !== chosen.id) .map(p => teamNames[teamList.indexOf(p.id)]) .join(", "); const msg = otherNames ? `${otherNames} กระสุนด้าน ${chosenName} ลั่นกระสุน!! (เหลือ ${guessCounter[index]} นัด)` : `${chosenName} ลั่นกระสุน!! (เหลือ ${guessCounter[index]} นัด)`; sendChatMessage(msg, sendFireMessage === 1); } if (autoSendTeamCount) sendChatMessage(guessCounter.join(" "), autoSendTeamCount === 1); } else { console.log("NGM: Incorrect answer, no counter deducted."); } } } correctGuesses = me.correctGuesses; $("#ngmCorrectAnswers").text(`Correct Answers: ${correctGuesses}`); if (initialGuessCount.length) { const total = initialGuessCount.reduce((a, b) => a + b); remainingGuesses = total - (correctGuesses % total); } else remainingGuesses = null; $("#ngmRemainingGuesses").text(remainingGuesses ? `Remaining Guesses: ${remainingGuesses}` : ""); }] ].forEach(([event, func]) => new Listener(event, func).bindListener()); ["get all song names", "update all song names"].forEach(event => new Listener(event, () => setTimeout(() => { autocomplete = quiz.answerInput.typingInput.autoCompleteController.list.map(x => x.toLowerCase()); }, 10)).bindListener() ); ngmWindow = new AMQWindow({ id: "ngmWindow", title: "NGM", zIndex: 1060, resizable: true, draggable: true }); ngmWindow.addPanel({ id: "ngmPanel", width: 1.0, height: "auto" }); setupNGMWindow(); applyStyles(); } // === Functions === function parseMessage(content) { if (content === "/ngm") { ngmWindow.isVisible() ? ngmWindow.close() : ngmWindow.open(); } } function sendChatMessage(text, teamChat) { socket.sendCommand({ type: "lobby", command: "game chat message", data: { msg: " " + text, teamMessage: !!teamChat } }); } function clearWindow() { $("#ngmGuessContainer").empty().append($(`<div id="ngmNotInGame">Not in team game with lives</div>`)); $("#ngmCorrectAnswers").text(""); $("#ngmRemainingGuesses").text(""); guessCounter = []; countButtons = []; teamNumber = null; teamList = []; teamSlot = null; correctGuesses = 0; remainingGuesses = 0; } function shortenName(name, maxLength = 10) { return name.length > maxLength ? name.slice(0, maxLength - 1) + "…" : name; } function updateWindow(players) { const selfPlayer = players.find(p => p.name === selfName); const teamNumber = selfPlayer.teamNumber; const myTeamPlayers = players.filter(p => p.teamNumber === teamNumber); teamList = myTeamPlayers.map(p => p.gamePlayerId); teamNames = myTeamPlayers.map(p => p.name); const $ngmGuessContainer = $("#ngmGuessContainer").empty(); countButtons = []; teamSlot = teamList.indexOf(selfPlayer.gamePlayerId); correctGuesses = selfPlayer.correctGuesses; for (let i = 0; i < teamList.length; i++) { const $slot = $(`<div class="ngmPlayerSlot"></div>`); const shortName = shortenName(teamNames[i]); const $nameLabel = $(`<div class="ngmPlayerName" title="${teamNames[i]}">${shortName}</div>`); const $button = $(`<div class="ngmButton ngmCount">${guessCounter[i] != null ? guessCounter[i] : ''}</div>`) .click(() => { guessCounter[i] = guessCounter[i] <= 0 ? initialGuessCount[i] : guessCounter[i] - 1; $button.text(guessCounter[i]); }); countButtons.push($button); $slot.append($nameLabel, $button); $ngmGuessContainer.append($slot); } resetCounter(); } function resetCounter() { if (!teamList.length) return; const countText = $("#ngmInitialGuessCountInput").val().trim(); if (!/^[0-9]+$/.test(countText)) { return counterError(); } if (countText.length === 1) { initialGuessCount = Array(teamList.length).fill(parseInt(countText, 10)); } else if (countText.length === teamList.length) { initialGuessCount = countText.split("").map(x => parseInt(x, 10)); } else { return counterError(); } guessCounter = [...initialGuessCount]; countButtons.forEach((btn, i) => { btn.removeClass("disabled").text(guessCounter[i]); }); const totalGuesses = initialGuessCount.reduce((a, b) => a + b, 0); if (totalGuesses === 0) { return counterError(); } remainingGuesses = totalGuesses - (correctGuesses % totalGuesses); $("#ngmRemainingGuesses").text( remainingGuesses ? `Remaining Guesses: ${remainingGuesses}` : "" ); $("#ngmCorrectAnswers").text(`Correct Answers: ${correctGuesses}`); } function counterError() { guessCounter = []; initialGuessCount = []; countButtons.forEach((x) => x.addClass("disabled").text("-")); $("#ngmCorrectAnswers").text("Invalid Settings"); $("#ngmRemainingGuesses").text(""); } function setupNGMWindow() { ngmWindow.window.find(".modal-header h2").remove(); ngmWindow.window.find(".modal-header").append(`<div id="ngmTitle">New Game Mode</div><div id="ngmCorrectAnswers"></div><div id="ngmRemainingGuesses"></div>`); ngmWindow.panels[0].panel.append(`<div id="ngmGuessContainer" class="ngmRow"><div id="ngmNotInGame">Not in team game with lives</div></div>`); let $row1 = $(`<div class="ngmRow"></div>`); let $row2 = $(`<div class="ngmRow"></div>`); $row1.append($(`<div class="ngmButton" style="width: 60px; background-color: #ffffff; border-color: #cccccc; color: #000000;">Reset</div>`) .click(() => { quiz.inQuiz ? resetCounter() : clearWindow(); }) ); $row1.append($(`<input type="text" id="ngmInitialGuessCountInput">`) .popover({ title: "Initial Guess Count", content: "<p>Use 1 digit: everyone same<br>Use multiple digits: each player</p><p>Example: 5 or 5454</p>", placement: "bottom", trigger: "hover", container: "body", animation: false, html: true }) .val("5") ); $row2.append($(`<div class="ngmButton" style="width: 50px; background-color: #ffffff; border-color: #cccccc; color: #000000;">Auto</div>`) .click(function() { autoTrackCount = !autoTrackCount; if (autoTrackCount) { $(this).css({"background-color": "#4497ea", "border-color": "#006ab7", "color": "#ffffff"}); $("#ngmSelfCountButton").removeClass("disabled"); $("#ngmTeamCountButton").removeClass("disabled"); } else { $(this).css({"background-color": "#ffffff", "border-color": "#cccccc", "color": "#000000"}); $("#ngmSelfCountButton").addClass("disabled"); $("#ngmTeamCountButton").addClass("disabled"); } }) .popover({ title: "Auto Track", content: "<p>Auto update team guess count when answering.</p>", placement: "bottom", trigger: "hover", container: "body", animation: false, html: true }) ); $row2.append($(`<div id="ngmSelfCountButton" class="ngmButton ngmBtnSmall disabled"><i class="fa fa-user"></i></div>`) .click(function() { autoThrowSelfCount = !autoThrowSelfCount; if (autoThrowSelfCount) { $(this).css({"background-color": "#4497ea", "border-color": "#006ab7", "color": "#ffffff"}); } else { $(this).css({"background-color": "#ffffff", "border-color": "#cccccc", "color": "#333333"}); } }) .popover({ title: "Auto Throw Self Count", content: "Auto send your own count as answer when a song starts.", placement: "bottom", trigger: "hover", container: "body", animation: false, html: true }) ); $row2.append($(`<div id="ngmTeamCountButton" class="ngmButton ngmBtnSmall disabled"><i class="fa fa-comment"></i></div>`) .click(function() { autoSendTeamCount = (autoSendTeamCount + 1) % 3; if (autoSendTeamCount === 0) { $(this).css({"background-color": "#ffffff", "border-color": "#cccccc", "color": "#333333"}); } else if (autoSendTeamCount === 1) { $(this).css({"background-color": "#4497ea", "border-color": "#006ab7", "color": "#ffffff"}); } else if (autoSendTeamCount === 2) { $(this).css({"background-color": "#9444EA", "border-color": "#6C00B7", "color": "#ffffff"}); } }) .popover({ title: "Auto Send Team Count", content: "<p>Send team count after answer:<br>Blue = Team Chat<br>Purple = Public Chat</p>", placement: "bottom", trigger: "hover", container: "body", animation: false, html: true }) ); $row2.append($(`<div id="ngmFireMessageToggle" class="ngmButton ngmBtnSmall"> <i class="fa fa-bullhorn"></i> </div>`) .click(function () { sendFireMessage = (sendFireMessage + 1) % 3; // 0 → 1 → 2 → 0 ... if (sendFireMessage === 0) { $(this).css({ "background-color": "#ffffff", "border-color": "#cccccc", "color": "#333333" }); // White = Off } else if (sendFireMessage === 1) { $(this).css({ "background-color": "#4497ea", "border-color": "#006ab7", "color": "#ffffff" }); // Blue = Team Chat } else if (sendFireMessage === 2) { $(this).css({ "background-color": "#9444EA", "border-color": "#6C00B7", "color": "#ffffff" }); // Purple = Public Chat } }) .popover({ title: "Fire Message", content: "<p>Announce who answered fastest:<br>Blue = Team Chat<br>Purple = Public Chat<br>White = Off</p>", placement: "bottom", trigger: "hover", container: "body", animation: false, html: true })); ngmWindow.panels[0].panel.append($row1).append($row2); } function applyStyles() { const style = document.createElement("style"); style.id = "newGameModeUIStyle"; style.textContent = ` #qpNGM { width: 30px; margin-right: 5px; } #ngmWindow { min-width: 300px; min-height: 250px !important; height: auto !important; font-family: 'Segoe UI', 'Helvetica Neue', sans-serif; } #ngmWindow .modal-content { height: auto !important; max-height: none !important; padding-bottom: 12px; } #ngmWindow .modal-header { padding: 10px 15px; line-height: normal; flex-direction: column; align-items: flex-start; border: none; background: transparent; } #ngmWindow .close { top: 10px; right: 10px; position: absolute; } #ngmWindow .customWindowContent { position: relative !important; } #ngmWindow .modal-dialog { height: auto !important; max-height: none !important; } #ngmTitle { font-size: 16px; font-weight: 600; margin-bottom: 4px; color: #ffffff; } #ngmCorrectAnswers, #ngmRemainingGuesses { font-size: 13px; color: #aaa; } #ngmGuessContainer { display: flex; flex-wrap: wrap; justify-content: center; } .ngmPlayerSlot { display: flex; flex-direction: column; align-items: center; max-width: 100px; word-break: break-word; } .ngmPlayerName { font-size: 11px; color: #ccc; text-align: center; width: 100%; max-width: 100px; } .ngmCount { background-color: #f6f6f6; color: #222; border: none; border-radius: 6px; width: 44px; height: 36px; font-weight: 500; font-size: 14px; text-align: center; line-height: 36px; } .ngmRow { display: flex; justify-content: center; align-items: center; gap: 5px; margin-bottom: 7px; margin-top: 7px; flex-wrap: wrap; } .ngmButton { background-color: #eeeeee; color: #111; border: none; border-radius: 6px; height: 36px; padding: 0 12px; font-size: 14px; font-weight: 500; min-width: 60px; box-sizing: border-box; transition: background-color 0.2s ease; display: flex; align-items: center; justify-content: center; cursor: pointer; } .ngmButton:hover { background-color: #dddddd; } .ngmBtnSmall { width: 36px; height: 36px; min-width: 36px; padding: 0; font-size: 14px; display: flex; justify-content: center; align-items: center; } #ngmInitialGuessCountInput { height: 36px; padding: 0 12px; font-size: 14px; border-radius: 6px; border: none; background-color: #f9f9f9; color: #111; min-width: 130px; box-sizing: border-box; } #ngmNotInGame { text-align: center; color: #888; width: 100%; margin: 12px 0; } .ngmAnimateCorrect { animation: ngmColorAnimationBlue 2s ease-in; } .ngmAnimateWrong { animation: ngmColorAnimationRed 2s ease-in; } @keyframes ngmColorAnimationBlue { from { background-color: #4497ea; color: #fff; } to { background-color: #f6f6f6; color: #222; } } @keyframes ngmColorAnimationRed { from { background-color: #d9534f; color: #fff; } to { background-color: #f6f6f6; color: #222; } } `; document.head.appendChild(style); }