Wobbly chat

Makes chat in OWoT wobbly.

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Wobbly chat
// @namespace    owot-wobbly-chat
// @version      1.1.1
// @description  Makes chat in OWoT wobbly.
// @author       Helloim0_0
// @match        https://*.ourworldoftext.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=ourworldoftext.com
// @grant        none
// ==/UserScript==

// inspired by kat, https://discord.com/channels/366609898164715520/373803780505862147/1449425422029688954

// config
var bounce = true; // default: true; recommended to set elementSnapApprox at 10 if disabled
var bounceX = 1; // default: 1; how fast chat goes after hitting left or right wall
var bounceY = 1; // default: 1; how fast chat goes after hitting ceiling or floor
var chatFriction = 0.02; // default: 0.02; how fast chat stops moving after being released
var chatSpeed = 0.3; // default: 0.3; how fast chat goes after being released
var speedMomentum = 2; // default: 2; how long chat takes to get to mouse speed or lose speed while holding
var speedDeadPoint = 0.25; // default: 0.25; maximum speed for chat to stop moving
var chatMinSize = 0.5; // default: 0.5; how compressed chat can get when moving
var scaleIntensity = 0.01; // default: 0.01; very sensitive value, be careful
var wobbleSpeed = 0.75; // default: 0.75; messed up with too low and too high values
var wobbleIntensity = 0.4; // default: 0.4; 1 = infinite wobbling
var wobbleStretch = 0.1; // default: 0.1; how far stretching goes compared to moved distance
var wobbleLimit = 30; // default: 30; the skewing hard limit in degrees
var wobbleDeadPoint = 0.1; // default: 0.1; maximum skew for chat to stop wobbling

// other config
var hitboxBgColor = "#00000000"; // default: #00000000

// owot config
window.elementSnapApprox = 0; // default: 0; owot default: 10; how many pixels from border needed to snap to it

/* examples of other values
bounce            | true:      files.catbox.moe/w2kbzv.mp4 | false:     files.catbox.moe/7nrpzx.mp4
bounceX           | 0.3:       files.catbox.moe/s7qtm3.mp4 | 3:         files.catbox.moe/mhyp37.mp4
bounceY           | 0.3:       files.catbox.moe/h08kge.mp4 | 3:         files.catbox.moe/7bb9lp.mp4
chatFriction      | 0.01:      files.catbox.moe/jdf2xb.mp4 | 0.04:      files.catbox.moe/0v0bqe.mp4
chatSpeed         | 0.1:       files.catbox.moe/ym90xg.mp4 | 1:         files.catbox.moe/3menfm.mp4
speedMomentum     | 1:         files.catbox.moe/vl2qm5.mp4 | 5:         files.catbox.moe/hhbgaa.mp4
speedDeadPoint    | 0.1:       files.catbox.moe/5fg9k0.mp4 | 1:         files.catbox.moe/a2k8qb.mp4
chatMinSize       | 0.2:       files.catbox.moe/r70jbt.mp4 | 0.8:       files.catbox.moe/s05k2u.mp4
scaleIntensity    | 0.05:      files.catbox.moe/tpsxrm.mp4 | 0.25:      files.catbox.moe/l836ut.mp4 | note: 0.01 is too low for the recording's width so higher values were used
wobbleSpeed       | 0.25:      files.catbox.moe/4z1co7.mp4 | 2:         files.catbox.moe/yainv1.mp4
wobbleIntensity   | 0.1:       files.catbox.moe/sqf76x.mp4 | 0.99:      files.catbox.moe/to9aoq.mp4
wobbleStretch     | 0.05:      files.catbox.moe/l8x135.mp4 | 0.5:       files.catbox.moe/6pr8s5.mp4
wobbleLimit       | 10:        files.catbox.moe/9p0dhb.mp4 | 60:        files.catbox.moe/nuvsld.mp4
wobbleDeadPoint   | 0.05:      files.catbox.moe/xcjbkm.mp4 | 0.5:       files.catbox.moe/9a4m24.mp4
hitboxBgColor     | #00000000: files.catbox.moe/e66u91.mp4 | #80008020: files.catbox.moe/yiij28.mp4
elementSnapApprox | 0:         files.catbox.moe/x484fz.mp4 | 25:        files.catbox.moe/4ps5vm.mp4
*/

// script variables
var previousX;
var previousY;
var speedX = 0;
var speedY = 0;
var scaleX = 1;
var scaleY = 1;
var skewX = 0;
var skewY = 0;
var skewSpeedX = 0;
var skewSpeedY = 0;
var stopTurnX = 0.1;
var stopTurnY = 0.1;
var directionX = false; // false = left
var directionY = false; // false = up
var lastDiffX = 0;
var lastDiffY = 0;
var draggingChat = false;

// moving chat as a separated function, accounting for transformations
function moveElm(elm, elmWidth, elmHeight, newX, newY) {
    elm.style.top = newY + "px";
    elm.style.left = newX + "px";
    if(newX <= elementSnapApprox) {
        if (bounce && !draggingChat) {
            newX = Math.abs(newX);
            elm.style.left = newX + "px";
            speedX = Math.abs(speedX) * bounceX;
        } else {
            elm.style.left = "0px";
            newX = 0;
            scaleX = 1;
        }
    }
    if(newX + elmWidth >= getWndWidth() - elementSnapApprox) {
        if (bounce && !draggingChat) {
            newX = newX - Math.abs(newX - getWndWidth() + elmWidth + elementSnapApprox / 2) + elementSnapApprox / 2;
            elm.style.left = newX + "px";
            speedX = -Math.abs(speedX) * bounceX;
        } else {
            elm.style.left = "";
            elm.style.right = "0px";
            newX = getWndWidth() - elmWidth;
            scaleX = 1;
        }
    }
    if(newY <= elementSnapApprox) {
        if (bounce && !draggingChat) {
            newY = Math.abs(newY);
            elm.style.top = newY + "px";
            speedY = Math.abs(speedY) * bounceY;
        } else {
            elm.style.top = "0px";
            newY = 0;
            scaleY = 1;
        }
    }
    if(newY + elmHeight >= getWndHeight() - elementSnapApprox) {
        if (bounce && !draggingChat) {
            newY = newY - Math.abs(newY - getWndHeight() + elmHeight + elementSnapApprox / 2) + elementSnapApprox / 2;
            elm.style.top = newY + "px";
            speedY = -Math.abs(speedY) * bounceY;
        } else {
            elm.style.top = "";
            elm.style.bottom = "0px";
            newY = getWndHeight() - elmHeight;
            scaleY = 1;
        }
    }
    return [newX, newY];
}

// used for when dragging chat window
function updateVars(newX, newY) {
    lastDiffX = newX - previousX;
    lastDiffY = newY - previousY;
    previousX = newX;
    previousY = newY;

    speedX = (speedX * (speedMomentum - 1) + lastDiffX) / speedMomentum;
    speedY = (speedY * (speedMomentum - 1) + lastDiffY) / speedMomentum;

    directionX = speedX != 0 ? speedX > 0 : directionX;
    stopTurnX = 0.1;
    directionY = speedY != 0 ? speedY > 0 : directionY;
    stopTurnY = 0.1;

    if (speedX != 0 || skewX != 0) {
        skewSpeedX = speedX * wobbleStretch;
        if (Math.abs(skewSpeedX) < 0.1) skewSpeedX = 0.1 * (directionX ? 1 : -1);
    }
    if (speedY != 0 || skewY != 0) {
        skewSpeedY = speedY * wobbleStretch;
        if (Math.abs(skewSpeedY) < 0.1) skewSpeedY = 0.1 * (directionY ? 1 : -1);
    }
    updateHitbox();
}

// main dragging function, comes from draggable_element in owot
function draggable_element_chat(dragger, dragged, exclusions, onDrag) {
    if(!dragged) {
        dragged = dragger;
    }
    var elmX = 0;
    var elmY = 0;
    var elmHeight = 0;
    var elmWidth = 0;
    draggingChat = false;

    var clickX = 0;
    var clickY = 0;
    dragger.addEventListener("mousedown", function(e) {
        if(exclusions) {
            for(var i = 0; i < exclusions.length; i++) {
                if(closest(e.target, exclusions[i])) {
                    return;
                }
            }
        }
        if(!closest(e.target, dragger)) return;
        elmX = dragged.offsetLeft;
        elmY = dragged.offsetTop;
        elmWidth = dragged.offsetWidth;
        elmHeight = dragged.offsetHeight;
        clickX = e.pageX;
        clickY = e.pageY;
        draggingChat = true;
        if (!chatResizing) {
            speedX = 0;
            speedY = 0;
        }
    });
    // when the element is being dragged (owot comment)
    draggable_element_mousemove.push(function(e, arg_pageX, arg_pageY) {
        if(!draggingChat) return;
        if(onDrag) {
            if(onDrag() == -1) return;
        }
        dragged.style.top = "";
        dragged.style.bottom = "";
        dragged.style.left = "";
        dragged.style.right = "";

        if (previousX === undefined) previousX = elmX;
        if (previousY === undefined) previousY = elmY;
        var diffX = arg_pageX - clickX;
        var diffY = arg_pageY - clickY;

        var newY = elmY + diffY;
        var newX = elmX + diffX;

        [newX, newY] = moveElm(dragged, elmWidth, elmHeight, newX, newY);
        updateVars(newX, newY);
    });
    // when the element is released (owot comment)
    draggable_element_mouseup.push(function() {
        draggingChat = false;
    });
}

// apply chat transformation
function transformChat() {
    requestAnimationFrame(transformChat);
    elm.chat_window.style.transform = `skew(${-skewX}deg, ${skewY}deg) scale(${scaleX}, ${scaleY})`;
}

// run chat functions
draggable_element_chat(elm.chat_window, null, [
    elm.chatbar, elm.chatsend, elm.chat_close, elm.chat_page_tab, elm.chat_global_tab, elm.page_chatfield, elm.global_chatfield
], function() {
    if(chatResizing) {
        return -1;
    }
});
transformChat();

// prevent canvas from being clicked due to chat being out of mouse when releasing
var invisibleHitbox = document.createElement("span");
invisibleHitbox.style.backgroundColor = hitboxBgColor;
invisibleHitbox.style.position = "absolute";
document.body.appendChild(invisibleHitbox);
elm.chat_window.style.zIndex = 1;
function updateHitbox() {
    invisibleHitbox.style.width = elm.chat_window.offsetWidth + "px";
    invisibleHitbox.style.height = elm.chat_window.offsetHeight + "px";
    invisibleHitbox.style.left = elm.chat_window.offsetLeft + "px";
    invisibleHitbox.style.top = elm.chat_window.offsetTop + "px";
}

// variables interval
setInterval(function() {
    if (isNaN(speedX)) speedX = 0;
    if (isNaN(speedY)) speedY = 0;
    speedX /= 1 + chatFriction;
    speedY /= 1 + chatFriction;
    if (Math.abs(speedX) < speedDeadPoint) speedX = 0;
    if (Math.abs(speedY) < speedDeadPoint) speedY = 0;
    scaleX = Math.max(1 - Math.abs(speedX) * scaleIntensity, chatMinSize);
    scaleY = Math.max(1 - Math.abs(speedY) * scaleIntensity, chatMinSize);
    if (chatResizing) {
        previousX = elm.chat_window.offsetLeft;
        previousY = elm.chat_window.offsetTop;
        speedX = 0;
        speedY = 0;
    }
    updateHitbox();
    if ((draggingChat && !chatResizing) || previousX === undefined || previousY === undefined) return;
    var elmWidth = elm.chat_window.offsetWidth;
    var elmHeight = elm.chat_window.offsetHeight;
    var moveX = previousX + speedX * chatSpeed;
    var moveY = previousY + speedY * chatSpeed;
    var elmX, elmY;
    [elmX, elmY] = moveElm(elm.chat_window, elmWidth, elmHeight, moveX, moveY);
    previousX = elmX;
    previousY = elmY;
});

// wobble interval
setInterval(function() {
    if (directionX ^ (skewX < 0)) {
        skewSpeedX /= 1 + wobbleSpeed;
    } else {
        skewSpeedX *= 1 + (wobbleSpeed * wobbleIntensity);
    }
    if (Math.abs(skewSpeedX) <= stopTurnX && Math.abs(skewSpeedX) != 0) {
        directionX = !directionX;
        skewSpeedX = -skewSpeedX;
        stopTurnX = Math.abs(skewSpeedX);
        if (stopTurnX < wobbleDeadPoint / 100) stopTurnX = wobbleDeadPoint / 100;
        if (Math.abs(skewX) < wobbleDeadPoint) {
            skewSpeedX = 0;
            skewX = 0;
        }
    }
    skewX += skewSpeedX;
    if (skewX > wobbleLimit) {
        skewX = wobbleLimit;
        skewSpeedX = -0.1;
        stopTurnX = 0.1;
    }
    if (skewX < -wobbleLimit) {
        skewX = -wobbleLimit;
        skewSpeedX = 0.1;
        stopTurnX = 0.1;
    }

    if (directionY ^ (skewY < 0)) {
        skewSpeedY /= 1 + wobbleSpeed;
    } else {
        skewSpeedY *= 1 + (wobbleSpeed * wobbleIntensity);
    }
    if (Math.abs(skewSpeedY) <= stopTurnY && Math.abs(skewSpeedY) != 0) {
        directionY = !directionY;
        skewSpeedY = -skewSpeedY;
        stopTurnY = Math.abs(skewSpeedY);
        if (stopTurnY < wobbleDeadPoint / 100) stopTurnY = wobbleDeadPoint / 100;
        if (Math.abs(skewY) < wobbleDeadPoint) {
            skewSpeedY = 0;
            skewY = 0;
        }
    }
    skewY += skewSpeedY;
    if (skewY > wobbleLimit) {
        skewY = wobbleLimit;
        skewSpeedY = -0.1;
        stopTurnY = 0.1;
    }
    if (skewY < -wobbleLimit) {
        skewY = -wobbleLimit;
        skewSpeedY = 0.1;
        stopTurnY = 0.1;
    }
}, 15);