// ==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);
}