您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds in Osu! combo bursts into woomy, specifically from this skin: https://osu.ppy.sh/community/forums/topics/1249007
// ==UserScript== // @name Woomy Combo Bursts // @namespace http://tampermonkey.net/ // @version 1.0 // @description Adds in Osu! combo bursts into woomy, specifically from this skin: https://osu.ppy.sh/community/forums/topics/1249007 // @author PowfuArras // Discord: @xskt // @match *://*.woomy.app/* // @icon https://xskt.glitch.me/assets/images/icon.png // @grant none // @run-at document-start // @license FLORRIM DEVELOPER GROUP LICENSE (https://github.com/Florrim/license/blob/main/LICENSE.md) // ==/UserScript== (async function() { "use strict"; const canvasElement = document.createElement("canvas"); canvasElement.style.position = "absolute"; canvasElement.style.top = "0px"; canvasElement.style.left = "0px"; canvasElement.style.pointerEvents = "none"; const ctx = canvasElement.getContext("2d"); function resizeEvent() { canvasElement.width = window.innerWidth; canvasElement.height = window.innerHeight; } window.addEventListener("resize", () => resizeEvent()); resizeEvent(); window.addEventListener("load", function () { document.body.appendChild(canvasElement); }); function easeOutQuad(t) { return 1 - (1 - t) * (1 - t); } function lerp(start, end, t) { return start + t * (end - start); } class Combobursts { static url = "https://xskt.glitch.me/assets/"; static imagePath = "images/combobursts%id%.png"; static soundPath = "sounds/combobursts%id%.wav"; static soundBreakPath = "sounds/combobreak.ogg"; static imageDeathPath = "images/death.png"; static imageConnectingPath = "images/connecting.png"; static imageIconPath = "images/icon.png"; static imageSettingsPath = "images/settings.gif"; static images = []; static sounds = []; static breakSound = null; static deathImage = null; static connectingImage = null; static loaded = false; static connected = true; static connectedLerp = 0; static dead = false; static deadLerped = 0; static currentImageIndex = 0; static currentSoundIndex = 0; static currentSide = "right"; static bursts = []; static muteSounds = localStorage.getItem("Powfuarras_ComboBurstsMuted") === "true" ?? true; static audioTypes = new Map([ ["mp3", "audio/mpeg"], ["ogg", "audio/ogg"], ["wav", "audio/wav"] ]); static getAudioType(file) { return this.audioTypes.get(file.split(".").slice(-1)) ?? ""; } static async loadImage(src) { const imgElement = document.createElement("img"); imgElement.src = src; return new Promise(resolve => void (imgElement.onload = () => resolve(imgElement))); } static doBurst() { this.bursts.unshift({ side: this.currentSide, image: this.images[this.currentImageIndex], frame: 0 }); if (this.bursts.length > 2) this.bursts.length = 2; this.currentSide = this.currentSide === "left" ? "right" : "left"; this.currentImageIndex = (this.currentImageIndex + 1) % this.images.length; if (!this.muteSounds) this.sounds[this.currentSoundIndex++ % this.sounds.length].play(); } static drawBackground(image) { const scaleX = canvasElement.width / image.width; const scaleY = canvasElement.height / image.height; const scale = Math.max(scaleX, scaleY); const offsetX = (canvasElement.width - image.width * scale) / 2; const offsetY = (canvasElement.height - image.height * scale) / 2; ctx.drawImage(image, offsetX, offsetY, image.width * scale, image.height * scale); } static draw() { requestAnimationFrame(() => this.draw()); if (!this.loaded) { return; } ctx.clearRect(0, 0, canvasElement.width, canvasElement.height); this.bursts.forEach((burst, i) => { if (burst.frame === 101) { return void this.bursts.splice(i, 1); } const width = canvasElement.height / burst.image.height * burst.image.width; const ratio = burst.frame / 100; const moveRatio = Math.min(1, easeOutQuad(ratio) * 1.05); ctx.globalAlpha = (1 - ratio ** 3); if (burst.side === "left") { ctx.drawImage(burst.image, -width * (1 - moveRatio), 0, width, canvasElement.height); } else { ctx.scale(-1, 1); ctx.drawImage(burst.image, -canvasElement.width -width * (1 - moveRatio), 0, width, canvasElement.height); ctx.setTransform(1, 0, 0, 1, 0, 0); } burst.frame++; }); this.deadLerped = lerp(this.deadLerped, +this.dead, 0.1); if (this.deadLerped > 0.01) { ctx.globalAlpha = this.deadLerped; this.drawBackground(this.deathImage); } this.connectedLerp = lerp(this.connectedLerp, +!this.connected, 0.1); if (this.connectedLerp > 0.01) { ctx.globalAlpha = this.connectedLerp; this.drawBackground(this.connectingImage); } } static loadAudio(src) { const sourceElement = document.createElement("source"); sourceElement.src = src; sourceElement.type = this.getAudioType(src); const audioElement = document.createElement("audio"); return new Promise(function (resolve) { audioElement.oncanplaythrough = () => resolve(audioElement); audioElement.appendChild(sourceElement); audioElement.volume = 0.2; }); } static async initiate(imageAmount, soundAmount, iconElement) { const images = []; const sounds = []; iconElement.src = `${this.url}${this.imageIconPath}`; for (let i = 0; i < imageAmount; i++) { images.push(await this.loadImage(`${this.url}${this.imagePath}`.replace("%id%", `${i}`.padStart(2, "0")))); } for (let i = 0; i < soundAmount; i++) { sounds.push(await this.loadAudio(`${this.url}${this.soundPath}`.replace("%id%", `${i}`.padStart(2, "0")))); } this.breakSound = await this.loadAudio(`${this.url}${this.soundBreakPath}`); this.deathImage = await this.loadImage(`${this.url}${this.imageDeathPath}`); this.connectingImage = await this.loadImage(`${this.url}${this.imageConnectingPath}`); await new Promise(resolve => { let interval = setInterval(() => { try { const element = document.getElementById("settings-button"); element.style.backgroundImage = `url('${this.url}${this.imageSettingsPath}')`; element.style.backgroundSize = "cover"; element.style.width = "60px"; element.style.height = "60px"; element.style.cursor = "pointer"; element.style.opacity = "1"; element.style.borderRadius = "100%"; clearInterval(interval); resolve(); } catch {} }, 10); }); this.bursts = []; this.images = images; this.sounds = sounds; this.loaded = true; } } window.addEventListener("keydown", event => event.keyCode === 13 && (Combobursts.dead = false)); if (typeof ({}).encode === "function" && !Object.hasOwn(window, "woomyprotocol")) alert("A script is trying to hook into the protocol via an unsafe method causing a conflict. Disable disable or update conflicting scripts, please!"); if (!Object.hasOwn(window, "woomyprotocol")) { class Listener { _listeners = new Map(); _listenerID = 0; listen(callback) { this._listeners.set(this._listenerID++, callback); } unlisten(index) { this._listeners.remove(index); } _fire(data) { this._listeners.forEach(callback => { try { callback(data); } catch {} }); } } const module = { beforeEncode: new Listener(), beforeDecode: new Listener(), _encode: null, _decode: null }; const protocol = { encode: function (message, callback) { if (message != null) module.beforeEncode._fire(message); return callback(message); }, decode: function (data, callback) { const message = callback(data); if (message != null) module.beforeDecode._fire(message); return message; } }; for (const key in protocol) { const callback = protocol[`${key}`]; Object.defineProperty(Object.prototype, key, { get() { return function (data) { return callback(data, protocol[key]); }; }, set(value) { protocol[key] = value; module[`_${key}`] = value; } }); } window.woomyprotocol = module; } window.woomyprotocol.beforeDecode.listen(function (message) { if (Combobursts.loaded) { switch (message[0]) { case "AA": if (message[1] === 0) Combobursts.doBurst(); break; case "F": if (!Combobursts.muteSounds) Combobursts.breakSound.play(); Combobursts.dead = true; break; case "R": case "r": Combobursts.connected = true; } } }); window.addEventListener("load", function () { Combobursts.initiate(3, 2, document.querySelectorAll(".icon")[0]); setTimeout(() => { if (Combobursts.loaded) return; alert("Failed to fetch required resources for combobursts, reloading!\nAlso note this can take multiple times due too an issue with tampermonkey."); location.reload(); }, 6e3); const backgroundElement = document.getElementsByClassName("background")[0]; let interval = setInterval(function () { if (!document.body.contains(backgroundElement)) { clearInterval(interval); Combobursts.connected = false; } }, 10); }); requestAnimationFrame(() => Combobursts.draw()); let interval = setInterval(function () { try { const element = document.getElementById("Woomy_mainMenuStyle").parentElement.parentElement.cloneNode(true); clearInterval(interval); element.childNodes[0].textContent = "Mute Combobursts: "; const container = document.querySelector(".optionsFlexHolder"); container.insertBefore(element, document.getElementById("Woomy_mainMenuStyle").parentElement.parentElement); element.children[0].children[0].id = "Powfuarras_ComboBurstsMuted"; element.children[0].children[0].checked = Combobursts.muteSounds; element.children[0].children[0].onchange = function () { const checked = element.children[0].children[0].checked; Combobursts.muteSounds = checked localStorage.setItem("Powfuarras_ComboBurstsMuted", checked); } } catch { } }, 1000); })();