Greasy Fork is available in English.

TagPro RL Chat

Enhances the chat by mimicking Rocket League

// ==UserScript==
// @name         TagPro RL Chat
// @description  Enhances the chat by mimicking Rocket League
// @author       Ko
// @version      3.5
// @match        *://*.koalabeast.com/*
// @match        *://*.jukejuice.com/*
// @match        *://*.newcompte.fr/*
// @icon         https://github.com/wilcooo/TagPro-RL/raw/master/tprlc-256.png
// @supportURL   https://www.reddit.com/message/compose/?to=Wilcooo
// @website      https://redd.it/9le791
// @require      https://cdnjs.cloudflare.com/ajax/libs/autolinker/1.6.2/Autolinker.min.js
// @license      MIT
// @require      https://greasyfork.org/scripts/371240/code/TagPro%20Userscript%20Library.js
// @grant        GM_setValue
// @grant        GM_getValue
// @namespace https://greasyfork.org/users/152992
// ==/UserScript==



////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//     ### --- OPTIONS --- ###                                                                                            //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////  //
                                                                                                                      //  //
// Looking for options? They are on the homepage! Look for a green 'RL-chat' button.                                  //  //
// The only thing you can change here are what system messages will be replaced.                                      //  //
// null means the system message won't be shown at all, any other string will replace                                 //  //
// the message. The main goal is to reduce the space these messages take up, so that everything looks a bit neater.   //  //
                                                                                                                      //  //
const system_messages = {                                                                                             //  //
    "Since you refreshed, you will have to wait 10 seconds to respawn.": "Wait 10 seconds after a refresh",

    "Since there aren't many players in this game yet, you'll get a bonus": "There aren't many players in this game",
    "5 rank points if you stick around and make it a real match.": null,
    "10 rank points if you stick around and make it a real match.": null,
    "15 rank points if you stick around and make it a real match.": null,
    "20 rank points if you stick around and make it a real match.": null,

    "Thanks, you're getting 5 bonus rank points for that.": null,
    "Thanks, you're getting 10 bonus rank points for that.": null,
    "Thanks, you're getting 15 bonus rank points for that.": null,
    "Thanks, you're getting 20 bonus rank points for that.": null,

    "THROWBACK MAP!": "This is a throwback map",

    "TagPro Neomacro Plus Loaded!": null,

    "You can't switch teams right now.": "You can't switch teams right now.",

    "You've joined a game as a spectator. Once enough players come online, you'll": "You are spectating",
    "be auto-joined to a game. Q/W=Rotate through players. A=Red's flag carrier.": null,
    "S=Blue's flag carrier. +/- for zooming. C=Center map view. SPACE=Toggle auto-join.": null,
    "S=Blue's flag carrier. +/- for zooming. C=Center map view.": null,

    "Hi! You’re currently playing unregistered which means you can’t pick a": "You are not logged in",
    "custom name and your chat is limited. To register, click the Log In": null,
    "button on the homepage. Have fun!": null,

    "Sorry, as an unregistered player, you’ve reached your chat limit. To": "Chat limit reached, log in",
    "remove this limit, click the Log In button on the homepage and chat as": null,
    "much as you like!": null,

    "Thank you for testing! Winning 50 games while testing the world joiner": "Testing the SWJ. Win 50 to get the Compass",
    "will result in bonus flair!": null,

    "We are testing some adjustments to TagPro: Games are now 6 minutes long": "Test: 6 minute game, no limit, mercy and overtime",
    "with no cap limit but there is a 3 cap mercy rule. There is also an": null,
    "over time mode. Enjoy!": null,

    "OVERTIME! Next cap wins. Respawn penalty increases. Grabbing the flag":"OVERTIME!",
    "awards Juke Juice.":null,

    "During over time, each death is +3 seconds of respawn. Currently: 3": "Current respawn time: 3",
    "During over time, each death is +3 seconds of respawn. Currently: 6": "Current respawn time: 6",
    "During over time, each death is +3 seconds of respawn. Currently: 9": "Current respawn time: 9",
    "During over time, each death is +3 seconds of respawn. Currently: 12": "Current respawn time: 12",
    "During over time, each death is +3 seconds of respawn. Currently: 15": "Current respawn time: 15",
    "During over time, each death is +3 seconds of respawn. Currently: 18": "Current respawn time: 18",
    "During over time, each death is +3 seconds of respawn. Currently: 21": "Current respawn time: 21",
    "During over time, each death is +3 seconds of respawn. Currently: 24": "Current respawn time: 24",
    "During over time, each death is +3 seconds of respawn. Currently: 27": "Current respawn time: 27",
    "During over time, each death is +3 seconds of respawn. Currently: 30": "Current respawn time: 30",
};                                                                                                                    //  //
                                                                                                                      //  //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////  //
//                                                                                     ### --- END OF OPTIONS --- ###     //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////




////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//     ### --- SOUNDS --- ###                                                                                             //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////  //
                                                                                                                      //  //
var chat_sound = new Audio('https://raw.githubusercontent.com/wilcooo/TagPro-GroPro/master/audio/chat.wav');          //  //
var left_sound = new Audio('https://raw.githubusercontent.com/wilcooo/TagPro-GroPro/master/audio/left.mp3');          //  //
var join_sound = new Audio('https://raw.githubusercontent.com/wilcooo/TagPro-GroPro/master/audio/joined.mp3');        //  //
                                                                                                                      //  //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////  //
//                                                                                                                        //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////






//////////////////////////////////////
// SCROLL FURTHER AT YOUR OWN RISK! //
//////////////////////////////////////






/* Structure: (for reference)

div#RLC-box

    div.chats-wrapper          // Necessary to define the exact height of the next div
                               // by placing n empty lines in it.

        div.chats              // inherits the height of the wrapper

            div
                span.name
                span.message

            div
                span.name
                span.message

            //etc...

    label                      // can show 'team', 'group', 'mod' or nothing

        input#chat             // Note: this is the original TP chatbox!

*/




// =====CONFIG SECTION=====


// DON'T CHANGE THE OPTIONS HERE, AS YOU CAN CHANGE THEM ON THE TAGPRO HOME PAGE!

var settings = tpul.settings.addSettings({
    id: 'RL-Chat',
    title: 'TagPro RL Chat Configuration',
    tooltipText: 'RL Chat',
    icon: 'https://github.com/wilcooo/TagPro-RL/raw/master/tprlc-256.png',

    tabs: true,

    fields:
    {
        position:
        {
            label: 'Position',
            section: 'Appearance',
            type: 'select',
            options: ['top-left', 'top-right', 'bottom-left', 'bottom-right'],
            default: 'top-left',
        },
        font_size:
        {
            label: 'Font size (pixels)',
            type: 'int',
            default: 12,
            min: 0,
        },
        lines:
        {
            label: 'Height of the box (amount of lines)',
            type: 'int',
            default: 8,
            min:0,
        },
        box_width:
        {
            label: 'Width of the box (pixels)',
            type: 'int',
            default: 300,
            min: 0,
        },
        scrolling:
        {
            label: 'Enable scrolling to see older messages',
            type: 'checkbox',
            default: false,
        },
        hide_system:
        {
            label: 'Hide and/or shorten the most annoying system messages ("Since there aren\'t many players...")',
            type: 'checkbox',
            default: true,
        },
        bind_to_game:
        {
            label: 'Keep the chatbox inside the game viewport.',
            type: 'checkbox',
            default: true,
        },
        sound_on_chat: {
            label: '"Bwep" whenever someone sends a message',
            section: 'Sounds',
            type: 'checkbox',
            default: true,
        },
        sound_on_join: {
            label: '"Dink" whenever someone joins',
            type: 'checkbox',
            default: true,
        },
        sound_on_left: {
            label: '"Donk" whenever someone leaves',
            type: 'checkbox',
            default: true,
        },
        show_time:
        {
            label: 'Time to show RL-chat after recieving a message (seconds)',
            section: ['Showing/Hiding the chat',
                        'By default, the chat is hidden. Once a message arrives, the chat (including past messages) will be shown for a few seconds.'],
            type: 'int',
            default: 6,
            min:0,
        },
        permanent_end:
        {
            label: 'Permanently show RL-chat after the game has ended',
            type: 'checkbox',
            default: true,
        },
        permanent_always:
        {
            label: 'Permanently show RL-chat. Always',
            type: 'checkbox',
            default: false,
        },
        rollingchat:
        {
            label: 'Use the arrow keys to roll while typing',
            type: 'checkbox',
            default: true,
        },
        hide_default_chat:
        {
            label: 'Hide the default chat (Recommended)',
            type: 'checkbox',
            default: true,
        },
    },

    events: { save: update_settings }
});


var position = settings.get('position'),
    show_time = settings.get('show_time'),
    box_width = settings.get('box_width'),
    font_size = settings.get('font_size'),
    lines = settings.get('lines'),
    rollingchat = settings.get('rollingchat'),
    permanent_end = settings.get('permanent_end'),
    permanent_always = settings.get('permanent_always'),
    hide_system = settings.get('hide_system'),
    hide_default_chat = settings.get('hide_default_chat'),
    scrolling = settings.get('scrolling'),
    sound_on_chat = settings.get("sound_on_chat"),
    sound_on_join = settings.get("sound_on_join"),
    sound_on_left = settings.get("sound_on_left"),
    bind_to_game = settings.get("bind_to_game");

function update_settings() {

    if (tpul.playerLocation == 'game') {

        position = settings.get('position');
        show_time = settings.get('show_time');
        box_width = settings.get('box_width');
        font_size = settings.get('font_size');
        lines = settings.get('lines');
        //rollingchat = settings.get('rollingchat');
        permanent_end = settings.get('permanent_end');
        permanent_always = settings.get('permanent_always');
        hide_system = settings.get('hide_system');
        hide_default_chat = settings.get('hide_default_chat');
        scrolling = settings.get('scrolling');
        sound_on_chat = settings.get("sound_on_chat");
        sound_on_join = settings.get("sound_on_join");
        sound_on_left = settings.get("sound_on_left");
        bind_to_game = settings.get("bind_to_game");

        // Update the position
        if (!tpul.noscript) tagpro.chat.resize();

        // Update the width
        box.style.width = box_width + 'px';

        // Update the font size
        box.style.fontSize = font_size + 'px';

        // Update the amount of lines
        spacer.innerHTML = Array(lines+1).join('<br>');

        // Warn if rollingchat has been updated
        if (rollingchat != settings.get('rollingchat')) {
            setTimeout(tpul.notify, 1500,
                       'Rolling chat ' +
                       (settings.get('rollingchat') ? 'will be enabled after a refresh' :
                        'won\'t be enabled after a refresh / next game. (unless you have another script that enables it)'), 'warning');
        }

        // permanent
        if (permanent_always) box.classList.add('permanent');
        else if (permanent_end && !tpul.noscript && tagpro.state == 2) box.classList.add('permanent');
        else box.classList.remove('permanent');

        // (un)hide default chat
        if (default_chat) default_chat.style.display = hide_default_chat ? 'none' : '';

        // scrolling
        if (scrolling) box.classList.add('scroll');
        else box.classList.remove('scroll');

    }
}







// =====NOITCES GIFNOC=====











// =====CSS SECTION=====



// Create our own stylesheet to define the styles in:

var style = document.createElement('style');
document.head.appendChild(style);
var styleSheet = style.sheet;

/* Remove the style rule for the input box from TagPro's stylesheet.
Doesn't work in firefox, lets override all css rules later on.
t:for (let sheet of document.styleSheets) if (sheet.href && sheet.href.endsWith('/stylesheets/style.css')) {
    for (let r in sheet.rules) if (sheet.rules[r].selectorText == '.game input#chat') {
        sheet.removeRule(r);
        break t;
    }
}*/

// The outer container (containing the chat history & text field)
// Define font-size here, to give the chat-wrapper as well as the chat
styleSheet.insertRule(` #RLC-box {
position:absolute;
transition: background 500ms;
width:`+box_width+`px;
border-radius: 10px;
font-size:`+font_size+`px;
margin: 10px;
}`);



// The container when it's shown (while composing a message)
styleSheet.insertRule(`#RLC-box:focus-within, #RLC-box.permanent {
background: rgba(0,0,0,.8);
}`);


// The wrapper around the .chats div
styleSheet.insertRule(`#RLC-box .chats-wrapper {
margin: 5px 0;
position:relative;
overflow: hidden;
}`);

// Add 8 empty lines in it to set the wanted height.
// Using em, or any other method, will result in non-pixel perfect
// heights while zooming (depending on the browser).
/* DEPRECATED: WE NOW USE A REAL SPACER DIV
styleSheet.insertRule(` #RLC-box .chats-wrapper:after {
content: "`+ '\\a'.repeat(lines) +`";
white-space: pre;
}`);*/



// The chat history, which will always contain ALL chats.
// Older chats will just be scrolled out of view
// Opacity set to 0 by default.
styleSheet.insertRule(`#RLC-box .chats {
opacity:0;
overflow:hidden;
transition: opacity 500ms;
transition-delay: 500ms;
position: absolute;
height: 100%;
width: 100%;
padding: 0 5px;
top: 0;
left: 0;
}`);


// SCROLLING
styleSheet.insertRule(`#RLC-box.scroll:focus-within .chats {
overflow-y: auto;
width: calc(100% + 17px);
padding-right: calc(5px + 17px);
scroll-snap-type: mandatory;

/* older spec implementation */
scroll-snap-destination: 0 100%;
}`);

styleSheet.insertRule(`#RLC-box .scrollbar {
position: absolute;
background: rgba(255,255,255,.4);
width: 7px;
border-radius: 5px;
right: 4px;
opacity: 0;
transition: opacity 500ms;
}`);

styleSheet.insertRule(`#RLC-box.scroll:hover:focus-within .scrollbar {
opacity: 1;
}`);



/*styleSheet.insertRule(`#RLC-box:focus-within .chats.scroll::-webkit-scrollbar { width: 17px; }`);
styleSheet.insertRule(`#RLC-box:focus-within:hover .chats.scroll::-webkit-scrollbar-thumb {
border-radius: 10px;
color: rgba(255,255,255,.5);
box-shadow: -17px 0 0 -5px;
}`);*/


// This same box, but when it's .shown
// Using opacity instead of display:none or visibility allows us to use CSS transistion
styleSheet.insertRule(`#RLC-box:focus-within .chats, #RLC-box.permanent .chats, #RLC-box .chats.shown {
opacity:1;
transition-delay: 0s;
}`);

// A single message. Combining these multiple shadows creates a hard 1px line around the text,
// combined with a softer 5px shadow.
// display:inline; to make sure the height doesn't deviate from the set line-height
//  (we need this to make sure that exactly 8 lines fit in the box)
styleSheet.insertRule(` #RLC-box .chats div {
text-shadow: -1px -1px 5px #000, 1px -1px 5px #000, -1px 1px 5px #000, 1px 1px 5px #000, -1px -1px 1px #000, 1px -1px 1px #000, -1px 1px 1px #000, 1px 1px 1px #000;
width:inherit;
display: block;
text-overflow: ellipsis;
overflow: hidden;

scroll-snap-align: start;

/* older spec implementation */
scroll-snap-coordinate: 0 100%;
}`);

// The name of a chat message
styleSheet.insertRule(` #RLC-box .chats div .name {
text-transform: uppercase;
font-weight: bold;
margin-right: 3px;
}`);


// Authenticated name: add a green ✔
styleSheet.insertRule(` #RLC-box .chats div.auth .name::before {
content: "✔";
color: #BFFF00;
font-size: 8px;
}`);

// Changing the style of the .name, .message or full chat div depending on what kind of message it is.
styleSheet.insertRule(` #RLC-box .chats div.red       .name    { color:#FFB5BD; }`); // Red name
styleSheet.insertRule(` #RLC-box .chats div.red.team  .message { color:#FFB5BD; }`); // Red team message
styleSheet.insertRule(` #RLC-box .chats div.blue      .name    { color:#CFCFFF; }`); // Blue name
styleSheet.insertRule(` #RLC-box .chats div.blue.team .message { color:#CFCFFF; }`); // Blue team message
styleSheet.insertRule(` #RLC-box .chats div.group              { color:#E7E700; }`); // Group (name&message)
styleSheet.insertRule(` #RLC-box .chats div.mod       .name    { color:#00B900; }`); // Mod name
styleSheet.insertRule(` #RLC-box .chats div.system             { color:#F0E68C; }`); // system message
styleSheet.insertRule(` #RLC-box .chats div.announcement       { color:#FF88FF; }`); // announcement (server shutting down or something)

// Links. Same color as the message, but underlined.
styleSheet.insertRule(` #RLC-box a {
color: #8BC34A;
text-decoration: underline double;
}`);


// The label around the input field.
// This thing has the borders, because we want the
// label text to be inside the border too. ('team', 'group', 'mod'...)
// display:table; will make the input behave like a table-cell, which extends it to the end of the line.
styleSheet.insertRule(` #RLC-box label {
border-radius: 8px;
margin: 3px;
width: calc(100% - 6px);
padding: 3px;
border: 2px inset Gray;
transition: opacity 500ms;
opacity: 0;
display: table;
font-weight: normal;
background: rgba(255,255,255,.15);
cursor: text;
}`);

styleSheet.insertRule(` #RLC-box:focus-within label {
border: 2px outset CadetBlue;
}`);

// Same trick as before to hide/show it.
styleSheet.insertRule(` #RLC-box:focus-within label, #RLC-box.permanent label {
opacity: 1;
}`);

// The style of the label text ('team', 'group', 'mod')
// We have to add content:​ , because otherwise an empty
// input-box would have no height.
styleSheet.insertRule(` #RLC-box label:before {
display: table-cell;
font-weight: bold;
color: LightGrey;
content: '​';
}`);

// Add text to the label, depending on the type of chat you're composing.
styleSheet.insertRule(` #RLC-box label.team:before  {width:1px; padding-right:3px; content: 'team';}`);
styleSheet.insertRule(` #RLC-box label.group:before {width:1px; padding-right:3px; content: 'group';}`);
styleSheet.insertRule(` #RLC-box label.mod:before   {width:1px; padding-right:3px; content: 'mod';}`);

// The input field
// note: this is the original TagPro element, it'll just be moved to the new location
// This way we don't have to bother with the logic behind opening the box, and sending a chat
// We are mainly overriding default parameters.
styleSheet.insertRule(` #RLC-box input#chat {
padding: 0;
position: static;
border: none;
background: none;
outline: none;
transition: all 500ms;
display: none;
width: 100%;
font-size: inherit;
}`);

// Change the text-color of the chat you're composing, based on the type
styleSheet.insertRule(` #RLC-box label.team.red  input {color:#FFB5BD;}`); // Red text
styleSheet.insertRule(` #RLC-box label.team.blue input {color:#CFCFFF;}`); // Blue text
styleSheet.insertRule(` #RLC-box label.group     input {color:#E7E700;}`); // Yellow text
styleSheet.insertRule(` #RLC-box label.mod       input {color:#00B900;}`); // Green text





// =====NOITCES SSC=====



if (tpul.playerLocation == 'game' && !tpul.noscript) {


    // =====DOM SECTION=====



    // Some default DOM elements TagPro
    var canvas = document.getElementById('viewport');
    var game = document.getElementsByClassName('game')[0];
    var default_chat = document.getElementById('chatHistory');
    var input = document.getElementById('chat');



    // Hide the default chat history
    if (hide_default_chat && default_chat) default_chat.style.display = 'none';



    // Create the RLC container

    var box = document.createElement('div');
    box.id = 'RLC-box';
    game.appendChild( box );
    if (permanent_always) box.classList.add('permanent');

    // Add a wrapper for around the chats

    var wrapper = document.createElement('div');
    wrapper.className = 'chats-wrapper';
    box.appendChild(wrapper);

    // Add a "spacer" div to define the height of the wrapper based on some amount of lines
    var spacer = document.createElement('div');
    spacer.className = 'chats-spacer';
    wrapper.appendChild( spacer );
    spacer.innerHTML = Array(lines+1).join('<br>');

    // Add the chat-log to that wrapper

    var chats = document.createElement('div');
    chats.className = 'chats';
    wrapper.appendChild( chats );

    // scrolling
    if (scrolling) box.classList.add('scroll');
    else box.classList.remove('scroll');

    var scrollbar = document.createElement('div');
    scrollbar.className = 'scrollbar';
    wrapper.appendChild(scrollbar);


    // Add a label around the input, shown in case of team or group chat
    var label = document.createElement('label');
    box.appendChild(label);

    // Move the input inside RL chat
    label.appendChild(input);



    // =====NOITCES MOD=====





    // =====LOGIC SECTION=====




    /* global tagpro, $, Autolinker */


    var autolinker = new Autolinker( {
        urls : {
            schemeMatches : true,
            wwwMatches    : true,
            tldMatches    : true
        },
        email       : true,
        phone       : false,
        mention     : false,
        hashtag     : false,

        stripPrefix : true,
        stripTrailingSlash : true,
        newWindow   : true,

        truncate : { length:25, location:'end' }
    } );


    tagpro.ready(function() {

        if (rollingchat) enableRollingChat();

        var timeout;



        //Listen for messages, and add them to RLC:

        function handleChat (chat) {

            // Return disabled chat types
            if (tagpro.settings.ui) {
                if ( !tagpro.settings.ui.allChat && chat.to == "all" && chat.from ) return;
                if ( !tagpro.settings.ui.teamChat && chat.to == "team" ) return;
                if ( !tagpro.settings.ui.groupChat && chat.to == "group" ) return;
                if ( chat.to == "all" && !chat.from ) {
                    if (!tagpro.settings.ui.systemChat) return;
                    if (hide_system && system_messages[chat.message] === null) return;
                }
            }


            // Create the message div
            var message = document.createElement('div');

            var message_span = document.createElement('span');
            message_span.className = 'message';

            if (chat.from) {

                var name_span = document.createElement('span');
                name_span.className = 'name';

                if ( typeof chat.from == "number" ) {
                    var player = tagpro.players[chat.from];

                    if (player.auth) message.classList.add('auth');

                    if (player.team == 1) message.classList.add('red');
                    else if (player.team == 2) message.classList.add('blue');

                    name_span.innerText = player.name;
                } else name_span.innerText = chat.from;

                message.appendChild(name_span);

            } else {
                message.classList.add('system');
            }

            if ( chat.to == "group" ) {
                name_span.innerText = chat.from;
                message.classList.add('group');
            }

            if ( chat.from == "ADMIN_GLOBAL_BROADCAST" ) {
                message.classList.add('announcement');
                name_span.innerText = "ANNOUNCEMENT";
            }

            if ( chat.mod ) {
                message.classList.add('mod');
            }


            if( chat.to == "team") {
                message.classList.add("team");
            }

            message_span.innerText = system_messages[chat.message] || chat.message;
            message_span.innerHTML = autolinker.link( message_span.innerHTML );

            // Linkify the message, and append it
            message.appendChild(message_span);

            if ( chat.c ) message_span.style.color = chat.c;

            // Append the message and scroll the chat
            chats.appendChild(message);
            chats.scrollTop = chats.scrollHeight;

            chats.classList.add('shown');

            clearTimeout(timeout);    // Remove any existing timeout

            timeout = setTimeout(()=>chats.classList.remove('shown'),show_time*1e3);   // Set a timeout to hide the chats

            // Set volume according to the awesome Volume Slider®
            chat_sound.volume = left_sound.volume = join_sound.volume = 1 * tagpro.volumeCoefficient || 1;

            // Play a sound
            if (chat.from && sound_on_chat) chat_sound.play();
            else if (chat.message.includes(' has left ') && sound_on_left)
                left_sound.play();
            else if (chat.message.includes(' has joined ') && sound_on_join)
                join_sound.play();
            else if (sound_on_chat) chat_sound.play();
        }

        tagpro.socket.on('chat', handleChat);
        if (tagpro.group.socket) tagpro.group.socket.on('chat', handleChat);

        // Scrolling:
        chats.onscroll = function(scroll){

            // Update scrollbar
            scrollbar.style.height = 100 * chats.offsetHeight / chats.scrollHeight + '%';
            scrollbar.style.top = 100 * chats.scrollTop / chats.scrollHeight + '%';
        };

        // TODO: click on label opens all-chat (if not yet opened)


        // Change the built-in jQuery function .show()
        //  to make it trigger an event.
        // This function is used by TagPro to show the input-box.
        $.fn.org_show = $.fn.show;
        $.fn.show = function(speed, easing, callback) {
            $(this).trigger('show');
            return $(this).org_show(...arguments);
        };
        // Same for .hide()
        $.fn.org_hide = $.fn.hide;
        $.fn.hide = function(e, r, i) {
            $(this).trigger('hide');
            return $(this).org_hide(...arguments);
        };

        // Keep track of what is the last pressed key,
        // so that we know what label to add.
        var last_keyCode = null;
        document.addEventListener('keydown', event => last_keyCode = event.keyCode);

        // When the input is shown, add a label
        $(input).on('show',function(){

            // Scroll to bottom on opening chat
            chats.scrollTop = chats.scrollHeight;

            setTimeout(function(){
                if ( tagpro.keys.chatToAll.indexOf(last_keyCode) > -1 )
                    label.classList.add('all');
                else label.classList.remove('all');

                if ( tagpro.keys.chatToTeam.indexOf(last_keyCode) > -1 ) {
                    label.classList.add('team');
                    label.classList.add(tagpro.players[tagpro.playerId].team == 1 ? 'red' : 'blue');
                } else label.classList.remove('team');

                if ( tagpro.keys.chatToGroup.indexOf(last_keyCode) > -1 )
                    label.classList.add('group');
                else label.classList.remove('group');

                if ( tagpro.keys.chatAsMod.indexOf(last_keyCode) > -1 )
                    label.classList.add('mod');
                else label.classList.remove('mod');
            });
        });

        // When the input is hidden, hide the label
        $(input).on('hide',function(){

            // Blur the input (not automatically done on firefox)
            this.blur();

            // hide chat on closing it
            clearTimeout(timeout);    // Remove any existing timeout
            chats.classList.remove('shown');  // hide the chats

            label.classList.remove('all');
            label.classList.remove('team');
            label.classList.remove('group');
            label.classList.remove('mod');
        });

        // Permanently show the chat box after a game ends
        if (permanent_end)
            tagpro.socket.on('end', function(end) {
                // Show the box
                box.classList.add('permanent');
            });

        // When clicking the label, open the chat
        // (this will only be used once a game has ended)
        label.addEventListener('click', function(){
            if (input.style.display == 'none') {
                // Open the chat box:
                var e = new Event("keydown");
                e.keyCode = 'RL-Chat';
                tagpro.keys.chatToAll.push('RL-Chat');
                document.dispatchEvent(e);
                tagpro.keys.chatToAll.pop();
            }
        });

        // When the input looses focus, hide it
        // (so that it can be reopened with Enter or T or whatever)
        input.addEventListener('blur', function(){
            if (input.style.display == 'inline-block') {
                // Close the chat box:
                var e = new Event("keydown");
                e.keyCode = 'RL-Chat';
                tagpro.keys.cancelChat.push('RL-Chat');
                input.dispatchEvent(e);
                tagpro.keys.cancelChat.pop();
            }
        });

        // Modify TagPro's resize function, which is called whenever
        // your window size changes. (going fullscreen, zooming, etc.)
        tagpro.chat.org_resize = tagpro.chat.resize;
        tagpro.chat.resize = function() {

            canvas = document.getElementById('viewport');

            switch (position) {

                default: case 'top-left':
                    box.style.top = bind_to_game ? canvas.offsetTop + 'px' : 0;
                    box.style.left = bind_to_game ? canvas.offsetLeft + 'px' : 0;
                    box.style.bottom = '';
                    box.style.right = '';

                    // move 30 pixels down if you have FPS & ping enabled.
                    if (bind_to_game && tagpro.settings.ui.performanceInfo) box.style.top = canvas.offsetTop + 30 + 'px';
                    break;

                case 'top-right':
                    box.style.top = bind_to_game ? canvas.offsetTop + 'px' : 0;
                    box.style.right = bind_to_game ? game.offsetWidth - canvas.offsetLeft - canvas.offsetWidth + 'px' : 0;
                    box.style.bottom = '';
                    box.style.left = '';
                    break;

                case 'bottom-right':
                    box.style.bottom = bind_to_game ? window.innerHeight - canvas.offsetTop - canvas.offsetHeight + 'px' : 0;
                    box.style.right = bind_to_game ? game.offsetWidth - canvas.offsetLeft - canvas.offsetWidth + 'px' : 0;
                    box.style.top = '';
                    box.style.left = '';
                    break;

                case 'bottom-left':
                    box.style.bottom = bind_to_game ? window.innerHeight - canvas.offsetTop - canvas.offsetHeight + 'px' : 0;
                    box.style.left = bind_to_game ? canvas.offsetLeft + 'px' : 0;
                    box.style.top = '';
                    box.style.right = '';
                    break;
            }


            chats.scrollTop = chats.scrollHeight;

            if (!hide_default_chat) {
                default_chat.style.left = canvas.offsetLeft + 10 + 'px';
                default_chat.style.top = canvas.offsetTop + canvas.height - default_chat.offsetHeight - 50 + 'px';
            }

            return tagpro.chat.org_resize(...arguments);

        };

        // Call it once to make sure everyting is positioned correctly at the start.
        tagpro.chat.resize();

    });


    function enableRollingChat(){

        // It is perfectly fine to add this function (unchanged) to your own script.
        // Multiple instances of "rollingchat" can run simultaniously without problems.

        // intercept all key presses and releases:
        document.addEventListener('keydown', keyUpOrDown);
        document.addEventListener('keyup', keyUpOrDown);

        function keyUpOrDown( event ) {

            // The key that is pressed/released (undefined when it is any other key)
            var arrow = ['left','up','right','down'][[37,38,39,40].indexOf(event.keyCode)];

            // Only if the controls are disabled (usually while composing a message)
            // AND the key is indeed an arrow (not undefined)
            if (tagpro.disableControls && arrow) {

                // Whether you are releasing instead of pressing the key:
                var releasing = event.type == 'keyup';

                // Prevent the 'default' thing to happen, which is the cursor moving through the message you are typing
                event.preventDefault();

                // Return if already pressed/released
                if (tagpro.players[tagpro.playerId].pressing[arrow] != releasing) return;

                // Send the key press/release to the server!
                tagpro.sendKeyPress(arrow, releasing);

                // Not necesarry, but useful for other scripts to 'hook onto'
                if (!releasing && tagpro.events.keyDown) tagpro.events.keyDown.forEach(f => f.keyDown(arrow));
                if (releasing && tagpro.events.keyUp) tagpro.events.keyUp.forEach(f => f.keyUp(arrow));
                tagpro.ping.avg&&setTimeout(()=>(tagpro.players[tagpro.playerId][arrow]=!releasing),tagpro.ping.avg/2);
            }
        }
    }
    // =====NOITCES CIGOL=====
}