您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Smoother Note Quota for MPP
// ==UserScript== // @name Dynamic NQ // @namespace seq.wtf // @match *://mppclone.com/* // @match *://mpp.8448.space/* // @match *://multiplayerpiano.net/* // @match *://beta-mpp.cedms.jp/* // @match *://beta-mpp.csys64.com/* // @match *://mpp.terrium.net/* // @match *://mppfork.netlify.app/* // @match *://mpp.hri7566.info/* // @match *://www.multiplayerpiano.dev/* // @match *://mc.hri7566.info:8080/* // @match *://mpp176.tk/* // @match *://piano.ourworldofpixels.com/* // @match *://mpp.lapishusky.dev/* // @match *://mpp.hyye.tk/* // @match *://openmpp.tk/* // @match *://staging-mpp.sad.ovh/* // @run-at document-start // @grant none // @version 1.0.1 // @author Seq // @description Smoother Note Quota for MPP // @license Beerware // ==/UserScript== class NoteQuota { // allowance and maxHistLen are unused but kept for backwards compatibility static PARAMS_LOBBY = { allowance: 200, max: 600, maxHistLen: 3 }; static PARAMS_NORMAL = { allowance: 400, max: 1200, maxHistLen: 3 }; static PARAMS_RIDICULOUS = { allowance: 600, max: 1800, maxHistLen: 3 }; static PARAMS_OFFLINE = { allowance: 8000, max: 24000, maxHistLen: 3 }; static PARAMS_UNLIMITED = { allowance: 1000000, max: 3000000, maxHistLen: 3 }; static PARAMS_INFINITE = { allowance: Infinity, max: Infinity, maxHistLen: 3 }; #points = 0; #cachedPoints = 0; #regenRate = 0; // pts/ms; dynamic based on max #cacheTimestamp = 0; // 0 = invalid #lastUpdateTimestamp = 0; #recalc(timestamp) { const timeElapsed = timestamp - this.#lastUpdateTimestamp; const regeneratedPoints = timeElapsed > 0 ? timeElapsed * this.#regenRate : 0; return Math.min(this.#points + regeneratedPoints, this.max); } constructor(cb, params) { // this.cb = cb; // UPDATES MANUALLY -> updateQuotaBar() this.max = 0; this.CACHE_DURATION_MS = 10; this.setParams(params); } get points() { const now = Date.now(); if (this.#cacheTimestamp > 0 && now - this.#cacheTimestamp < this.CACHE_DURATION_MS) { return this.#cachedPoints; } const currentPoints = this.#recalc(now); // prime cache this.#cachedPoints = currentPoints; this.#cacheTimestamp = now; return currentPoints; } getParams() { return { m: "nq", allowance: this.max / 3, max: this.max, maxHistLen: 3 }; } setParams(params) { params = params || NoteQuota.PARAMS_OFFLINE; const max = params.max || this.max || NoteQuota.PARAMS_OFFLINE.max; if (max !== this.max) { this.max = max; this.#regenRate = max / 6000; this.resetPoints(); return true; } return false; } resetPoints() { this.#points = this.max; this.#lastUpdateTimestamp = Date.now(); // invalidate cache // priming would also work, but this is rarely called, // so invalidating is acceptable this.#cacheTimestamp = 0; if (this.cb) this.cb(this.points); } spend(needed) { const now = Date.now(); // saves a Date.now() call over just using this.points const currentPoints = this.#recalc(now); if (currentPoints < needed) return false; this.#points = currentPoints - needed; this.#lastUpdateTimestamp = now; // prime cache this.#cachedPoints = this.#points; this.#cacheTimestamp = now; if (this.cb) this.cb(this.#points); return true; } // legacy tick() { // the MPP client will call this every two seconds // do nothing } get allowance() { return this.max / 3; } get maxHistLen() { return 3; } get history() { const p = this.points; return [p, p, p]; } } // trap the NoteQuota definition let patched = false; Object.defineProperty(window, "NoteQuota", { configurable: true, enumerable: true, get: () => NoteQuota, set: () => { patched = true; delete window.NoteQuota; window.NoteQuota = NoteQuota; } }); // very cursed race condition fallback // (the MPP object could become initialized before we get the chance to hijack the NoteQuota object) const fallbackInterval = setInterval(() => { if (patched || window.MPP?.noteQuota?.CACHE_DURATION_MS) return clearInterval(fallbackInterval); // patched successfully if (!window.MPP?.noteQuota) return; // not initialized yet console.warn("[DynamicNQ] Race condition lost! Post-patching..."); const originalInstance = window.MPP.noteQuota; const customInstance = new NoteQuota(originalInstance.cb); const customProps = new Set([ ...Object.getOwnPropertyNames(customInstance), ...Object.getOwnPropertyNames(NoteQuota.prototype) ]); for (const prop of customProps) { if (prop === "constructor") continue; const descriptor = Object.getOwnPropertyDescriptor(customInstance, prop) || Object.getOwnPropertyDescriptor(NoteQuota.prototype, prop); if (typeof descriptor.value === 'function') { originalInstance[prop] = descriptor.value.bind(customInstance); } else { Object.defineProperty(originalInstance, prop, { configurable: true, get: () => customInstance[prop], set: v => customInstance[prop] = v, }); } } delete originalInstance.cb; patched = true; clearInterval(fallbackInterval); }); let nqBar = null; (function updateQuotaBar() { window.requestAnimationFrame(updateQuotaBar); if (!window.$ || !window.MPP?.noteQuota) return; if (!nqBar) nqBar = $("#quota .value"); const nq = window.MPP.noteQuota; nqBar.css("width", `${(nq.points / nq.max) * 100}%`); })();