From f2ce468d9f263f423e55d1eab196324c1d609f10 Mon Sep 17 00:00:00 2001 From: nexpid Date: Tue, 6 Jan 2026 15:53:30 +0100 Subject: [PATCH] CursorBuddy: local scripts (#621) * CursorBuddy: local scripts * CursorBuddy: load fathorse locally * bleh * bleh * Update fathorse.js --------- Co-authored-by: thororen1234 <78185467+thororen1234@users.noreply.github.com> --- src/equicordplugins/cursorBuddy/fathorse.js | 169 ++++++++++++++++++++ src/equicordplugins/cursorBuddy/index.tsx | 48 +++--- 2 files changed, 192 insertions(+), 25 deletions(-) create mode 100644 src/equicordplugins/cursorBuddy/fathorse.js diff --git a/src/equicordplugins/cursorBuddy/fathorse.js b/src/equicordplugins/cursorBuddy/fathorse.js new file mode 100644 index 000000000..449b474a7 --- /dev/null +++ b/src/equicordplugins/cursorBuddy/fathorse.js @@ -0,0 +1,169 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2026 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +/* eslint-disable */ +/* Sourced from https://raw.githubusercontent.com/nexpid/fatass-horse/5363cf9b5904211de79d2597200374340efac676/horse.js */ + +// https://github.com/adryd325/oneko.js +export default function fathorse(cfg) { + document.getElementById("fathorse")?.remove(); + + // generated + const spritesheet = { right: [[0, 0], [1, 0], [2, 0], [3, 0], [4, 0], [5, 0], [6, 0], [7, 0], [8, 0]], downright: [[0, 1], [1, 1], [2, 1], [3, 1], [4, 1], [5, 1], [6, 1], [7, 1], [8, 1]], down: [[0, 2], [1, 2], [2, 2], [3, 2], [4, 2], [5, 2], [6, 2], [7, 2], [8, 2]], downleft: [[0, 3], [1, 3], [2, 3], [3, 3], [4, 3], [5, 3], [6, 3], [7, 3], [8, 3]], left: [[0, 4], [1, 4], [2, 4], [3, 4], [4, 4], [5, 4], [6, 4], [7, 4], [8, 4]], upleft: [[0, 5], [1, 5], [2, 5], [3, 5], [4, 5], [5, 5], [6, 5], [7, 5], [8, 5]], up: [[0, 6], [1, 6], [2, 6], [3, 6], [4, 6], [5, 6], [6, 6], [7, 6], [8, 6]], upright: [[0, 7], [1, 7], [2, 7], [3, 7], [4, 7], [5, 7], [6, 7], [7, 7], [8, 7]] }; + const gridX = 9, gridY = 8; + + const config = { + speed: cfg.speed ?? 30, + framerate: cfg.fps ?? 24, + size: cfg.size ?? 120, + fade: cfg.fade ?? true, + freeroam: cfg.freeroam ?? true, + shake: cfg.shake || window._horseShake, + image: cfg.image + }; + const horsePos = { + x: config.size / 2, + y: config.size / 2 + }; + const mousePos = { + x: horsePos.x, + y: horsePos.y + }; + + const hz = 1e3 / config.framerate; + const freeroamStart = 9e3; + const freeroamInterval = 3e3; + const freeroamSpeed = 12; + + const fathorse = document.createElement("div"); + + let lastFrame, shakeUntil = 0; + function lifecycle() { + if (!fathorse.parentElement) return; + + if (!lastFrame) lastFrame = Date.now(); + if ((Date.now() - lastFrame) >= hz) { + frame(); + } + + if (config.shake) { + if (shakeUntil >= Date.now()) { + const x = Math.floor(Math.random() * 4 - 2); + const y = Math.floor(Math.random() * 4 - 2); + document.body.style.transform = `translate3d(${x}px, ${y}px, 0)`; + } else { + document.body.style.transform = ""; + } + } + + requestAnimationFrame(lifecycle); + } + + function moved() { + shakeUntil = Date.now() + 20; + } + + let animationFrame = 0; + function update(frame, active) { + const sprites = spritesheet[frame]; + let sprite = sprites[0]; + if (active) sprite = sprites[Math.floor(animationFrame) % sprites.length]; + + fathorse.style.backgroundPosition = `-${sprite[0] * config.size}px -${sprite[1] * config.size}px`; + + fathorse.style.left = `${horsePos.x - config.size / 2}px`; + fathorse.style.top = `${horsePos.y - config.size / 2}px`; + } + + let nextMove = Infinity, isRoaming = false; + function frame() { + lastFrame = Date.now(); + if (config.freeroam && nextMove < Date.now()) { + const angle = Math.random() * Math.PI * 2; + const distance = Math.min(window.innerWidth, window.innerHeight) / config.size * 50; + + mousePos.x = Math.abs((mousePos.x + Math.sin(angle) * distance) % window.innerWidth); + mousePos.y = Math.abs((mousePos.y + Math.cos(angle) * distance) % window.innerHeight); + isRoaming = true; + } + + const diffX = mousePos.x - horsePos.x; + const diffY = mousePos.y - horsePos.y; + const dist = Math.sqrt(diffX ** 2 + diffY ** 2); + + const direction = [ + diffY / dist < -0.5 ? "up" : "", + diffY / dist > 0.5 ? "down" : "", + diffX / dist < -0.5 ? "left" : "", + diffX / dist > 0.5 ? "right" : "", + ].join("") || "down"; + + const speed = isRoaming ? Math.min(config.speed, freeroamSpeed) : config.speed; + + if (dist >= Math.max(speed, config.size * 0.7)) { + animationFrame++; + + if (isRoaming) nextMove = Date.now() + freeroamInterval; + + const innerSize = Math.sqrt(window.innerWidth ** 2 + window.innerHeight ** 2); + const mult = dist / innerSize > 0.5; + if (mult || animationFrame % 2 === 1) { + horsePos.x += (diffX / dist) * speed; + horsePos.y += (diffY / dist) * speed; + + if (!isRoaming) moved(); + } + + const inset = config.size / 2; + horsePos.x = Math.min(Math.max(inset, horsePos.x), window.innerWidth - inset); + horsePos.y = Math.min(Math.max(inset, horsePos.y), window.innerHeight - inset); + + update(direction, true); + } else { + animationFrame = 0; + update(direction); + } + + const hoverLimit = config.size * 0.6; + if (isRoaming) { + fathorse.style.opacity = "80%"; + } else if (config.fade && dist <= hoverLimit) { + fathorse.style.opacity = `${15 + (dist / hoverLimit) * 85}%`; + } else { + fathorse.style.opacity = ""; + } + } + + fathorse.id = "fathorse"; + fathorse.ariaHidden = true; + fathorse.style.width = `${config.size}px`; + fathorse.style.height = `${config.size}px`; + fathorse.style.left = `${horsePos.x - config.size / 2}px`; + fathorse.style.top = `${horsePos.y - config.size / 2}px`; + fathorse.style.position = "fixed"; + fathorse.style.pointerEvents = "none"; + fathorse.style.imageRendering = "pixelated"; + fathorse.style.zIndex = 2147483647; + fathorse.style.backgroundImage = `url(${JSON.stringify(config.image)})`; + fathorse.style.backgroundSize = `${gridX * config.size}px ${gridY * config.size}px`; + fathorse.style.willChange = "left, top, background-position"; + fathorse.style.transition = "opacity 0.1s linear"; + document.body.appendChild(fathorse); + + if (config.shake) document.body.style.willChange = "transform"; + + window.addEventListener("mousemove", ev => { + mousePos.x = ev.clientX; + mousePos.y = ev.clientY; + + nextMove = Date.now() + freeroamStart; + isRoaming = false; + }); + + requestAnimationFrame(lifecycle); + + return mousePos; +}; diff --git a/src/equicordplugins/cursorBuddy/index.tsx b/src/equicordplugins/cursorBuddy/index.tsx index 63eb73c1f..a27f90293 100644 --- a/src/equicordplugins/cursorBuddy/index.tsx +++ b/src/equicordplugins/cursorBuddy/index.tsx @@ -21,11 +21,11 @@ import { definePluginSettings, migratePluginSettings } from "@api/Settings"; import { Devs, EquicordDevs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; -const ONEKO_IMAGE = "https://raw.githubusercontent.com/adryd325/oneko.js/c4ee66353b11a44e4a5b7e914a81f8d33111555e/oneko.gif"; -const ONEKO_SCRIPT = "https://raw.githubusercontent.com/adryd325/oneko.js/c4ee66353b11a44e4a5b7e914a81f8d33111555e/oneko.js"; -const FATASS_HORSE_SCRIPT = "https://raw.githubusercontent.com/nexpid/fatass-horse/351f158bfd8fafd44d9c17faad61f2a80bcd33e3/horse.js"; -const FATASS_HORSE_ORIGINAL_IMAGE = "https://raw.githubusercontent.com/nexpid/fatass-horse/refs/heads/main/sheet.png"; -const FATASS_HORSE_IMAGE = "https://raw.githubusercontent.com/nexpid/fatass-horse/351f158bfd8fafd44d9c17faad61f2a80bcd33e3/sheet.png"; +import fathorse from "./fathorse"; + +const ONEKO_SCRIPT = "https://raw.githubusercontent.com/adryd325/oneko.js/5281d057c4ea9bd4f6f997ee96ba30491aed16c0/oneko.js"; +const ONEKO_IMAGE = "https://raw.githubusercontent.com/adryd325/oneko.js/5281d057c4ea9bd4f6f997ee96ba30491aed16c0/oneko.gif"; +const FATASS_HORSE_IMAGE = "https://raw.githubusercontent.com/nexpid/fatass-horse/08bc4042750d5f995c55327f7b6c6710158f5263/sheet.png"; const settings = definePluginSettings({ buddy: { @@ -45,14 +45,14 @@ const settings = definePluginSettings({ onChange: load, }, speed: { - description: "Speed of Da Cat :3", + description: "Speed of your buddy", type: OptionType.NUMBER, default: 10, isValid: (value: number) => value >= 0 || "Speed must be bigger than 0", onChange: load, }, fps: { - description: "Framerate of the fatass horse", + description: "Framerate of your buddy", type: OptionType.NUMBER, default: 24, isValid: (value: number) => value > 0 || "Framerate must be bigger than 0", @@ -84,9 +84,6 @@ const settings = definePluginSettings({ onChange: load }, }, { - fps: { - disabled() { return this.store.buddy !== "fathorse"; }, - }, size: { disabled() { return this.store.buddy !== "fathorse"; }, }, @@ -114,24 +111,25 @@ function load() { case "oneko": { fetch(ONEKO_SCRIPT) .then(x => x.text()) - .then(s => s.replace("const nekoSpeed = 10;", `const nekoSpeed = ${settings.store.speed};`)) - .then(s => s.replace("./oneko.gif", ONEKO_IMAGE) - .replace("(isReducedMotion)", "(false)")) - .then(eval); + .then(s => s + .replace("(isReducedMotion)", "(false)") + .replace("persistPosition = true;", "persistPosition = false;") + .replace("./oneko.gif", ONEKO_IMAGE) + .replace("nekoSpeed = 10;", `nekoSpeed = ${settings.store.speed};`) + .replace(" > 100", ` > ${1000 / settings.store.fps}`) + ); break; } case "fathorse": { - fetch(FATASS_HORSE_SCRIPT) - .then(x => x.text()) - .then(s => s.replace(FATASS_HORSE_ORIGINAL_IMAGE, FATASS_HORSE_IMAGE)) - .then(s => (0, eval)(s)({ - speed: settings.store.speed, - fps: settings.store.fps, - size: settings.store.size, - fade: settings.store.fade, - freeroam: settings.store.freeroam, - shake: settings.store.shake - })); + fathorse({ + speed: settings.store.speed, + fps: settings.store.fps, + size: settings.store.size, + fade: settings.store.fade, + freeroam: settings.store.freeroam, + shake: settings.store.shake, + image: FATASS_HORSE_IMAGE + }); } } }