Wobbly chat

Makes chat in OWoT wobbly.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

You will need to install an extension such as Tampermonkey to install this script.

이 스크립트를 설치하려면 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);