MediaWiki:Gadget-oneko.js: Difference between revisions
Jump to navigation
Jump to search
Vendicated (talk | contribs) No edit summary |
Vendicated (talk | contribs) No edit summary |
||
Line 3: | Line 3: | ||
// SPDX-License-Identifier: MIT | // SPDX-License-Identifier: MIT | ||
// https://github.com/adryd325/oneko.js | // https://github.com/adryd325/oneko.js | ||
// compiled to es5 with babel because mediawiki is insane and needs es5 | |||
(function oneko() { | (function oneko() { | ||
var isReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)") === true || window.matchMedia("(prefers-reduced-motion: reduce)").matches === true; | |||
if (isReducedMotion) return; | if (isReducedMotion) return; | ||
var nekoEl = document.createElement("div"); | |||
var nekoPosX = 32; | |||
var nekoPosY = 32; | |||
var mousePosX = 0; | |||
var mousePosY = 0; | |||
var frameCount = 0; | |||
var idleTime = 0; | |||
var idleAnimation = null; | |||
var idleAnimationFrame = 0; | |||
var nekoSpeed = 10; | |||
var spriteSets = { | |||
idle: [[-3, -3]], | idle: [[-3, -3]], | ||
alert: [[-7, -3]], | alert: [[-7, -3]], | ||
scratchSelf: [ | scratchSelf: [[-5, 0], [-6, 0], [-7, 0]], | ||
scratchWallN: [[0, 0], [0, -1]], | |||
scratchWallS: [[-7, -1], [-6, -2]], | |||
scratchWallE: [[-2, -2], [-2, -3]], | |||
scratchWallW: [[-4, 0], [-4, -1]], | |||
scratchWallN: [ | |||
scratchWallS: [ | |||
scratchWallE: [ | |||
scratchWallW: [ | |||
tired: [[-3, -2]], | tired: [[-3, -2]], | ||
sleeping: [ | sleeping: [[-2, 0], [-2, -1]], | ||
N: [[-1, -2], [-1, -3]], | |||
NE: [[0, -2], [0, -3]], | |||
E: [[-3, 0], [-3, -1]], | |||
N: [ | SE: [[-5, -1], [-5, -2]], | ||
S: [[-6, -3], [-7, -2]], | |||
SW: [[-5, -3], [-6, -1]], | |||
W: [[-4, -2], [-4, -3]], | |||
NE: [ | NW: [[-1, 0], [-1, -1]] | ||
E: [ | |||
SE: [ | |||
S: [ | |||
SW: [ | |||
W: [ | |||
NW: [ | |||
}; | }; | ||
function init() { | function init() { | ||
nekoEl.id = "oneko"; | nekoEl.id = "oneko"; | ||
Line 96: | Line 46: | ||
nekoEl.style.pointerEvents = "none"; | nekoEl.style.pointerEvents = "none"; | ||
nekoEl.style.imageRendering = "pixelated"; | nekoEl.style.imageRendering = "pixelated"; | ||
nekoEl.style.left = | nekoEl.style.left = "".concat(nekoPosX - 16, "px"); | ||
nekoEl.style.top = | nekoEl.style.top = "".concat(nekoPosY - 16, "px"); | ||
nekoEl.style.zIndex = Number.MAX_VALUE; | nekoEl.style.zIndex = Number.MAX_VALUE; | ||
var nekoFile = "https://wiki.vencord.dev/images/8/8c/Oneko.gif"; | |||
var curScript = document.currentScript; | |||
if (curScript && curScript.dataset.cat) { | if (curScript && curScript.dataset.cat) { | ||
nekoFile = curScript.dataset.cat; | nekoFile = curScript.dataset.cat; | ||
} | } | ||
nekoEl.style.backgroundImage = | nekoEl.style.backgroundImage = "url(".concat(nekoFile, ")"); | ||
document.body.appendChild(nekoEl); | document.body.appendChild(nekoEl); | ||
document.addEventListener("mousemove", function (event) { | document.addEventListener("mousemove", function (event) { | ||
mousePosX = event.clientX; | mousePosX = event.clientX; | ||
mousePosY = event.clientY; | mousePosY = event.clientY; | ||
}); | }); | ||
window.requestAnimationFrame(onAnimationFrame); | window.requestAnimationFrame(onAnimationFrame); | ||
} | } | ||
var lastFrameTimestamp; | |||
function onAnimationFrame(timestamp) { | function onAnimationFrame(timestamp) { | ||
// Stops execution if the neko element is removed from DOM | // Stops execution if the neko element is removed from DOM | ||
Line 133: | Line 77: | ||
window.requestAnimationFrame(onAnimationFrame); | window.requestAnimationFrame(onAnimationFrame); | ||
} | } | ||
function setSprite(name, frame) { | function setSprite(name, frame) { | ||
var sprite = spriteSets[name][frame % spriteSets[name].length]; | |||
nekoEl.style.backgroundPosition = | nekoEl.style.backgroundPosition = "".concat(sprite[0] * 32, "px ").concat(sprite[1] * 32, "px"); | ||
} | } | ||
function resetIdleAnimation() { | function resetIdleAnimation() { | ||
idleAnimation = null; | idleAnimation = null; | ||
idleAnimationFrame = 0; | idleAnimationFrame = 0; | ||
} | } | ||
function idle() { | function idle() { | ||
idleTime += 1; | idleTime += 1; | ||
// every ~ 20 seconds | // every ~ 20 seconds | ||
if ( | if (idleTime > 10 && Math.floor(Math.random() * 200) == 0 && idleAnimation == null) { | ||
var avalibleIdleAnimations = ["sleeping", "scratchSelf"]; | |||
if (nekoPosX < 32) { | if (nekoPosX < 32) { | ||
avalibleIdleAnimations.push("scratchWallW"); | avalibleIdleAnimations.push("scratchWallW"); | ||
Line 166: | Line 103: | ||
avalibleIdleAnimations.push("scratchWallS"); | avalibleIdleAnimations.push("scratchWallS"); | ||
} | } | ||
idleAnimation = | idleAnimation = avalibleIdleAnimations[Math.floor(Math.random() * avalibleIdleAnimations.length)]; | ||
} | } | ||
switch (idleAnimation) { | switch (idleAnimation) { | ||
case "sleeping": | case "sleeping": | ||
Line 199: | Line 132: | ||
idleAnimationFrame += 1; | idleAnimationFrame += 1; | ||
} | } | ||
function frame() { | function frame() { | ||
frameCount += 1; | frameCount += 1; | ||
var diffX = nekoPosX - mousePosX; | |||
var diffY = nekoPosY - mousePosY; | |||
var distance = Math.sqrt(Math.pow(diffX, 2) + Math.pow(diffY, 2)); | |||
if (distance < nekoSpeed || distance < 48) { | if (distance < nekoSpeed || distance < 48) { | ||
idle(); | idle(); | ||
return; | return; | ||
} | } | ||
idleAnimation = null; | idleAnimation = null; | ||
idleAnimationFrame = 0; | idleAnimationFrame = 0; | ||
if (idleTime > 1) { | if (idleTime > 1) { | ||
setSprite("alert", 0); | setSprite("alert", 0); | ||
Line 221: | Line 150: | ||
return; | return; | ||
} | } | ||
var direction; | |||
direction = diffY / distance > 0.5 ? "N" : ""; | direction = diffY / distance > 0.5 ? "N" : ""; | ||
direction += diffY / distance < -0.5 ? "S" : ""; | direction += diffY / distance < -0.5 ? "S" : ""; | ||
Line 228: | Line 156: | ||
direction += diffX / distance < -0.5 ? "E" : ""; | direction += diffX / distance < -0.5 ? "E" : ""; | ||
setSprite(direction, frameCount); | setSprite(direction, frameCount); | ||
nekoPosX -= diffX / distance * nekoSpeed; | |||
nekoPosX -= | nekoPosY -= diffY / distance * nekoSpeed; | ||
nekoPosY -= | |||
nekoPosX = Math.min(Math.max(16, nekoPosX), window.innerWidth - 16); | nekoPosX = Math.min(Math.max(16, nekoPosX), window.innerWidth - 16); | ||
nekoPosY = Math.min(Math.max(16, nekoPosY), window.innerHeight - 16); | nekoPosY = Math.min(Math.max(16, nekoPosY), window.innerHeight - 16); | ||
nekoEl.style.left = "".concat(nekoPosX - 16, "px"); | |||
nekoEl.style.left = | nekoEl.style.top = "".concat(nekoPosY - 16, "px"); | ||
nekoEl.style.top = | |||
} | } | ||
init(); | init(); | ||
})(); | })(); |
Latest revision as of 20:09, 7 March 2024
// oneko.js
// Copyright © 2022 adryd
// SPDX-License-Identifier: MIT
// https://github.com/adryd325/oneko.js
// compiled to es5 with babel because mediawiki is insane and needs es5
(function oneko() {
var isReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)") === true || window.matchMedia("(prefers-reduced-motion: reduce)").matches === true;
if (isReducedMotion) return;
var nekoEl = document.createElement("div");
var nekoPosX = 32;
var nekoPosY = 32;
var mousePosX = 0;
var mousePosY = 0;
var frameCount = 0;
var idleTime = 0;
var idleAnimation = null;
var idleAnimationFrame = 0;
var nekoSpeed = 10;
var spriteSets = {
idle: [[-3, -3]],
alert: [[-7, -3]],
scratchSelf: [[-5, 0], [-6, 0], [-7, 0]],
scratchWallN: [[0, 0], [0, -1]],
scratchWallS: [[-7, -1], [-6, -2]],
scratchWallE: [[-2, -2], [-2, -3]],
scratchWallW: [[-4, 0], [-4, -1]],
tired: [[-3, -2]],
sleeping: [[-2, 0], [-2, -1]],
N: [[-1, -2], [-1, -3]],
NE: [[0, -2], [0, -3]],
E: [[-3, 0], [-3, -1]],
SE: [[-5, -1], [-5, -2]],
S: [[-6, -3], [-7, -2]],
SW: [[-5, -3], [-6, -1]],
W: [[-4, -2], [-4, -3]],
NW: [[-1, 0], [-1, -1]]
};
function init() {
nekoEl.id = "oneko";
nekoEl.ariaHidden = true;
nekoEl.style.width = "32px";
nekoEl.style.height = "32px";
nekoEl.style.position = "fixed";
nekoEl.style.pointerEvents = "none";
nekoEl.style.imageRendering = "pixelated";
nekoEl.style.left = "".concat(nekoPosX - 16, "px");
nekoEl.style.top = "".concat(nekoPosY - 16, "px");
nekoEl.style.zIndex = Number.MAX_VALUE;
var nekoFile = "https://wiki.vencord.dev/images/8/8c/Oneko.gif";
var curScript = document.currentScript;
if (curScript && curScript.dataset.cat) {
nekoFile = curScript.dataset.cat;
}
nekoEl.style.backgroundImage = "url(".concat(nekoFile, ")");
document.body.appendChild(nekoEl);
document.addEventListener("mousemove", function (event) {
mousePosX = event.clientX;
mousePosY = event.clientY;
});
window.requestAnimationFrame(onAnimationFrame);
}
var lastFrameTimestamp;
function onAnimationFrame(timestamp) {
// Stops execution if the neko element is removed from DOM
if (!nekoEl.isConnected) {
return;
}
if (!lastFrameTimestamp) {
lastFrameTimestamp = timestamp;
}
if (timestamp - lastFrameTimestamp > 100) {
lastFrameTimestamp = timestamp;
frame();
}
window.requestAnimationFrame(onAnimationFrame);
}
function setSprite(name, frame) {
var sprite = spriteSets[name][frame % spriteSets[name].length];
nekoEl.style.backgroundPosition = "".concat(sprite[0] * 32, "px ").concat(sprite[1] * 32, "px");
}
function resetIdleAnimation() {
idleAnimation = null;
idleAnimationFrame = 0;
}
function idle() {
idleTime += 1;
// every ~ 20 seconds
if (idleTime > 10 && Math.floor(Math.random() * 200) == 0 && idleAnimation == null) {
var avalibleIdleAnimations = ["sleeping", "scratchSelf"];
if (nekoPosX < 32) {
avalibleIdleAnimations.push("scratchWallW");
}
if (nekoPosY < 32) {
avalibleIdleAnimations.push("scratchWallN");
}
if (nekoPosX > window.innerWidth - 32) {
avalibleIdleAnimations.push("scratchWallE");
}
if (nekoPosY > window.innerHeight - 32) {
avalibleIdleAnimations.push("scratchWallS");
}
idleAnimation = avalibleIdleAnimations[Math.floor(Math.random() * avalibleIdleAnimations.length)];
}
switch (idleAnimation) {
case "sleeping":
if (idleAnimationFrame < 8) {
setSprite("tired", 0);
break;
}
setSprite("sleeping", Math.floor(idleAnimationFrame / 4));
if (idleAnimationFrame > 192) {
resetIdleAnimation();
}
break;
case "scratchWallN":
case "scratchWallS":
case "scratchWallE":
case "scratchWallW":
case "scratchSelf":
setSprite(idleAnimation, idleAnimationFrame);
if (idleAnimationFrame > 9) {
resetIdleAnimation();
}
break;
default:
setSprite("idle", 0);
return;
}
idleAnimationFrame += 1;
}
function frame() {
frameCount += 1;
var diffX = nekoPosX - mousePosX;
var diffY = nekoPosY - mousePosY;
var distance = Math.sqrt(Math.pow(diffX, 2) + Math.pow(diffY, 2));
if (distance < nekoSpeed || distance < 48) {
idle();
return;
}
idleAnimation = null;
idleAnimationFrame = 0;
if (idleTime > 1) {
setSprite("alert", 0);
// count down after being alerted before moving
idleTime = Math.min(idleTime, 7);
idleTime -= 1;
return;
}
var direction;
direction = diffY / distance > 0.5 ? "N" : "";
direction += diffY / distance < -0.5 ? "S" : "";
direction += diffX / distance > 0.5 ? "W" : "";
direction += diffX / distance < -0.5 ? "E" : "";
setSprite(direction, frameCount);
nekoPosX -= diffX / distance * nekoSpeed;
nekoPosY -= diffY / distance * nekoSpeed;
nekoPosX = Math.min(Math.max(16, nekoPosX), window.innerWidth - 16);
nekoPosY = Math.min(Math.max(16, nekoPosY), window.innerHeight - 16);
nekoEl.style.left = "".concat(nekoPosX - 16, "px");
nekoEl.style.top = "".concat(nekoPosY - 16, "px");
}
init();
})();