From 260bb91fd688e7ed06caba1247b1b5d9c7d25ac8 Mon Sep 17 00:00:00 2001 From: sophie Date: Wed, 24 Jul 2024 19:21:07 +0300 Subject: [PATCH] oneko.js TSified --- website/assets/oneko.webp | Bin 0 -> 2894 bytes website/assets/style.css | 3 + website/index.html | 5 +- website/scripts/oneko.ts | 243 ++++++++++++++++++++++++++++++++++++ website/templates/head.html | 2 +- 5 files changed, 250 insertions(+), 3 deletions(-) create mode 100644 website/assets/oneko.webp create mode 100644 website/scripts/oneko.ts diff --git a/website/assets/oneko.webp b/website/assets/oneko.webp new file mode 100644 index 0000000000000000000000000000000000000000..7724bd481129246cbbce3ac2553c8d944300e1e1 GIT binary patch literal 2894 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

☹️☹️☹️.ovh

I'm Latvian, 17. My name's Sophie meows a lot. I love listening to music.

- +

I love to play games with peeps! My favorite games recently have been Minecraft and Stardew Valley! DM me - if you wanna play with me ^w^

+ if you wanna play with me ^w^ +

DNI: diff --git a/website/scripts/oneko.ts b/website/scripts/oneko.ts new file mode 100644 index 0000000..3883950 --- /dev/null +++ b/website/scripts/oneko.ts @@ -0,0 +1,243 @@ +class Neko { + element: HTMLDivElement; + + readonly nekoSpeed = 10; + readonly spriteSheets: Record = { + 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], + ], + }; + private nekoPosX = 32; + private nekoPosY = 32; + + private mousePosX = 0; + private mousePosY = 0; + + private frameCount = 0; + private idleTime = 0; + private idleAnimation?: string; + private idleAnimationFrame = 0; + private lastFrameTimestamp?: number; + private file: string; + + constructor(file: string = "assets/oneko.webp") { + this.file = file; + + this.element = document.createElement("div"); + + this.element.id = "oneko"; + this.element.ariaHidden = "true"; + this.element.style.width = "32px"; + this.element.style.height = "32px"; + this.element.style.position = "fixed"; + this.element.style.pointerEvents = "none"; + this.element.style.imageRendering = "pixelated"; + this.element.style.left = `${this.nekoPosX - 16}px`; + this.element.style.top = `${this.nekoPosY - 16}px`; + this.element.style.zIndex = "2147483647"; + + this.element.style.backgroundImage = `url(${this.file})`; + let element = document.body; + + if (!element) { + throw new Error("no body exists"); + } + + element.appendChild(this.element); + document.addEventListener("mousemove", (event) => { + this.mousePosX = event.clientX; + this.mousePosY = event.clientY; + }); + + window.requestAnimationFrame((t) => this.onAnimationFrame(t)); + } + private onAnimationFrame(timestamp: number) { + // Stops execution if the neko element is removed from DOM + if (!this.element.isConnected) { + return; + } + if (!this.lastFrameTimestamp) { + this.lastFrameTimestamp = timestamp; + } + if (timestamp - this.lastFrameTimestamp > 100) { + this.lastFrameTimestamp = timestamp; + this.frame(); + } + window.requestAnimationFrame((t) => this.onAnimationFrame(t)); + } + + setSprite(name: string, frame: number) { + const sprite = + this.spriteSheets[name][frame % this.spriteSheets[name].length]; + this.element.style.backgroundPosition = `${sprite[0] * 32}px ${ + sprite[1] * 32 + }px`; + } + + resetidleAnimation() { + this.idleAnimation = undefined; + this.idleAnimationFrame = 0; + } + + idle() { + this.idleTime += 1; + + // every ~ 20 seconds + if ( + this.idleTime > 10 && + Math.floor(Math.random() * 200) == 0 && + this.idleAnimation == null + ) { + let availableidleAnimations = ["sleeping", "scratchSelf"]; + if (this.nekoPosX < 32) { + availableidleAnimations.push("scratchWallW"); + } + if (this.nekoPosY < 32) { + availableidleAnimations.push("scratchWallN"); + } + if (this.nekoPosX > window.innerWidth - 32) { + availableidleAnimations.push("scratchWallE"); + } + if (this.nekoPosY > window.innerHeight - 32) { + availableidleAnimations.push("scratchWallS"); + } + this.idleAnimation = + availableidleAnimations[ + Math.floor(Math.random() * availableidleAnimations.length) + ]; + } + + switch (this.idleAnimation) { + case "sleeping": + if (this.idleAnimationFrame < 8) { + this.setSprite("tired", 0); + break; + } + this.setSprite("sleeping", Math.floor(this.idleAnimationFrame / 4)); + if (this.idleAnimationFrame > 192) { + this.resetidleAnimation(); + } + break; + case "scratchWallN": + case "scratchWallS": + case "scratchWallE": + case "scratchWallW": + case "scratchSelf": + this.setSprite(this.idleAnimation, this.idleAnimationFrame); + if (this.idleAnimationFrame > 9) { + this.resetidleAnimation(); + } + break; + default: + this.setSprite("idle", 0); + return; + } + this.idleAnimationFrame += 1; + } + + frame() { + this.frameCount += 1; + const diffX = this.nekoPosX - this.mousePosX; + const diffY = this.nekoPosY - this.mousePosY; + const distance = Math.sqrt(diffX ** 2 + diffY ** 2); + + if (distance < this.nekoSpeed || distance < 48) { + this.idle(); + return; + } + + this.idleAnimation = undefined; + this.idleAnimationFrame = 0; + + if (this.idleTime > 1) { + this.setSprite("alert", 0); + // count down after being alerted before moving + this.idleTime = Math.min(this.idleTime, 7); + this.idleTime -= 1; + return; + } + + let direction: string; + direction = diffY / distance > 0.5 ? "N" : ""; + direction += diffY / distance < -0.5 ? "S" : ""; + direction += diffX / distance > 0.5 ? "W" : ""; + direction += diffX / distance < -0.5 ? "E" : ""; + this.setSprite(direction, this.frameCount); + + this.nekoPosX -= (diffX / distance) * this.nekoSpeed; + this.nekoPosY -= (diffY / distance) * this.nekoSpeed; + + this.nekoPosX = Math.min( + Math.max(16, this.nekoPosX), + window.innerWidth - 16 + ); + this.nekoPosY = Math.min( + Math.max(16, this.nekoPosY), + window.innerHeight - 16 + ); + + this.element.style.left = `${this.nekoPosX - 16}px`; + this.element.style.top = `${this.nekoPosY - 16}px`; + } +} + +window.addEventListener("load", () => { + new Neko("/assets/oneko.webp"); +}); diff --git a/website/templates/head.html b/website/templates/head.html index 71b262d..6c57871 100644 --- a/website/templates/head.html +++ b/website/templates/head.html @@ -23,4 +23,4 @@ - \ No newline at end of file + \ No newline at end of file