IdleScape - Lootify

Addons for the IdleScape Fighting System

目前為 2021-01-27 提交的版本,檢視 最新版本

// ==UserScript==
// @name         IdleScape - Lootify
// @namespace    D4IS
// @version      1.3.1
// @description  Addons for the IdleScape Fighting System
// @author       D4M4G3X
// @match        http*://*idlescape.com/game
// @match        https://idlescape.com/game
// @match        https://www.idlescape.com/game
// @grant        none
// @require http://code.jquery.com/jquery-3.4.1.min.js
// @run-at document-start
// ==/UserScript==

(function() {
    'use strict';
    let ver = '1.3.1';
    let runs = [{}];
    let total = {};
    let totalLoot = {};
    let isSetup = false;
    let time = 0;
    let prices = getMarketPrices();
    let data = {};
    let user = {};
    let app = {};
    let status;
    let location;
    let headers = {};
    let timer = {
        'running': false,
        'active': false,
        'status': 'idling',
        'starts': [],
        'stops': [],
        'reset': function() {
            this.active = false;
            this.running = false;
            this.starts = [];
            this.stops = [];
        },
        'go': function() {
            this.active = true;
            this.update();
            this.interval();
        },
        'interval': function() {
            let e = setInterval(()=> {
                if (timer.running) {
                    if($('.time-stats').length) {
                        $('.time-stats').find('span').text('Elapsed: ' + getTime());
                    }
                }
            }, 100);
        },
        'update': function() {
            let e = setInterval(()=> {
                if (timer.active) {
                    if($('.status-bar').length) {
                        if (status === timer.status) {
                            if (!timer.running) {
                                timer.running = true;
                                timer.starts.unshift(new Date());
                            }
                        } else {
                            if(timer.running) {
                                timer.running = false;
                                timer.stops.unshift(new Date());
                            }
                        }
                    }
                }
            }, 500);
        },
    }
    init();
    /* ############## INTERVALS ############## */
    let mainInterval = setInterval(()=> {
        if ($('.status-bar').length) {
            setupSettings();
            checkUpdates();
            if(!isSetup) {
                isSetup = true;
                getData();
                updateUser();
                setupLogs();
            }
            renderStatus();
            status == 'fighting' ? saveLogs() : 1;
            mergeLogs();
            renderLogs();
            renderTimer();
            renderKPH();
            !isIron() ? renderGPH() : 1;
            renderXPH();

            addConfirmations();
            cleanChat();
        }
    }, 1000);

    let pasteInterval = setInterval(()=> {
        if(window.localStorage.getItem(getUsername() + '-AutoPaster') == 'true') {
            reset('single');
        }
    }, getPasteInterval() * 60 * 1000);
    let adInterval = setInterval(()=> {
        chat({
            'msg': 'Lootify - Please report bugs to D4M4G3X#6263 on Discord!',
            'color': '#00a0fd',
        });
    }, 20 * 60 * 1000);
    let updateInterval = setInterval(()=> {
        prices = getMarketPrices();
        getNewVer();
        getAppEnabled();
        getData();
        updateUser();
    }, 15 * 60 * 1000);
    let quickInterval = setInterval(()=> {
        updateStatus();
        addResourceButtons();
        checkRemovals();
        addUserIcons();
    }, 100);
    /* ############## SETUP AND RENDER ############## */
    function init() {
        getNewVer();
        getAppEnabled();
        $('head').append('<link rel="stylesheet" href="https://digimol.net/lootify/css/game.css" type="text/css" />');
    }
    function addUserIcons() {
        $('.message-username').each(function() {
            if(data.testers) {
                if(data.testers.includes($(this).text())) {
                    if (!$(this).find('ltf-user-icon').length) {
                        $(this).prepend($('<img>', {
                            'class': 'message-username-icon ltf-user-icon'
                        }).attr({
                            'src': 'https://digimol.net/lootify/img/lootify_logo.png'
                        }));
                    }
                }
            }
        });
    }
    function addConfirmations() {
        if ($('.combat-zone-clone').length) {
            $('.combat-zone-clone').unbind().click(function(e) {
                let $parent = $(this).parents('.combat-zones');
                let $that = $(this);
                let includes = false;
                if(getLogMobs()) {
                    $.each(getLogMobs(), function(key, mob) {
                        if (getZoneMobs($that.text()).includes(mob)) {
                            includes = true;
                            return false;
                        }
                    });

                    if(isStatus('idling') && Object.keys(runs[0]).length === 0) {
                        $parent.find('.combat-zone').each(function() {
                            if($(this).text() === $that.text()) {
                                $(this).click();
                                reset();
                            }
                        });
                        return false;
                    }

                    if(!includes) {
                        confirmDialog({
                            'title': 'Lootify warning!',
                            'text': 'Going to a different zone will paste and reset your lootify statistics, are you sure you want to do this?',
                            'img': true,
                            'cbyes': function() {
                                $parent.find('.combat-zone').each(function() {
                                    if($(this).text() === $that.text()) {
                                        $(this).click();
                                        reset();
                                    }
                                });
                            },
                        });
                    } else {
                        $parent.find('.combat-zone').each(function() {
                            if($(this).text() === $that.text()) {
                                $(this).click();
                            }
                        });
                    }
                }
            });
        }

        if ($('.resource-button-overlay').length) {
            $('.resource-button-overlay').unbind().click(function(e) {
                let $parent = $(this).parents('.resource-container');
                let includes = false;
                if(getLogMobs()) {
                    $.each(getLogMobs(), function(key, mob) {
                        if ($parent.find('.resource-container-title').text() == mob) {
                            includes = true;
                            return false;
                        }
                    });
                }
                if(isStatus('idling') && Object.keys(runs[0]).length === 0) {
                    $parent.find('.resource-button:not(.resource-button-overlay)').click();
                    reset();
                    return false;
                }
                if(isFighting() || !includes) {
                    confirmDialog({
                        'title': 'Lootify warning!',
                        'text': 'Going to a different location will paste and reset your lootify statistics, are you sure you want to do this?',
                        'img': true,
                        'cbyes': function() {
                            $parent.find('.resource-button:not(.resource-button-overlay)').click();
                            reset();
                        }
                    });
                } else {
                    $parent.find('.resource-button:not(.resource-button-overlay)').click();
                }
            });
        }
    }
    function addResourceButtons() {
        $('.resource-container-button').each(function() {
            if($(this).parents('.theme-mining').length || $(this).parents('.theme-foraging').length) {
                if( !$(this).next('.resource-button-overlay').length && !~$(this).text().indexOf('Stop')) {
                    let $btn = $(this).find('.resource-button').hide();
                    $('<div>', {
                        'class': 'resource-button-overlay resource-button'
                    }).text($btn.text()).insertAfter($(this));
                } else {
                    if (~$(this).text().indexOf('Stop')) {
                        $(this).next('.resource-button-overlay').remove();
                        $(this).find('.resource-button').show();
                    }
                }
            }
        });
        if ($('.combat-zones').length) {
            if(!$('.zone-button-overlay').length) {
                let $overlay = $('<div>', {
                    'class': 'zone-button-overlay'
                }).appendTo($('.combat-zones'));
                $('.combat-zone').each(function() {
                    let $zoneBtn = $('<div>', {
                        'class':'combat-zone-clone'
                    }).text($(this).text()).appendTo($overlay);
                });
                $(window).resize(function() {
                    $overlay.width($('.combat-zones').width());
                }).resize();
            }
        }
    }
    function checkRemovals() {
        if ($('.recipe-item').length) {
            $('.recipe-item').each(function() {
                if($(this).find('img').attr('src') === '/images/foraging/log.png') {
                    $('<div>', {
                        'class': 'ltf-item-remove'
                    }).text($(this).find('.recipe-item-text').text() + ' temporarily disabled by Lootify').appendTo($(this).prev('.category-item'));
                    $(this).remove();
                }
            });
        }
    }
    function setupSettings() {
        let $lootBtn;
        if(!getMenuItem('Lootify', 'category')) {
            let $ltfHeader = setMenuItem({
                'text': 'Lootify',
                'clone': 'Gathering',
                'class': 'hdr-lootify',
                'before': getMenuItem('Gathering', 'category'),
            }, 'category');
            $ltfHeader.append($('<i/>').text(' (v'+ver+')').insertAfter($ltfHeader));
        }
        if (!app.market) {
            $('.lootify-error.error-paster').remove();
        }
        if (!$('.lootify-error.error-paster').length && app.market) {
            $('<div/>', {
                'class': 'lootify-error error-paster'
            }).css({
                'color': 'red',
                'width': '80%',
                'margin': '0 auto',
            }).html('Paster is disabled!<br>Reset log to Enable again.').insertBefore(getMenuItem('Lootify Settings'));
        }
        if (!$('.lootify-error.error-disabled').length && (app.enabled && app.enabled === 'false')) {
            $('<div/>', {
                'class': 'lootify-error error-disabled'
            }).css({
                'color': 'red',
                'width': '80%',
                'margin': '0 auto',
            }).html('Paster is globally disabled!').insertBefore(getMenuItem('Lootify Settings'));
        }
        if(!getMenuItem('Lootify')) {
            $lootBtn = setMenuItem({
                'icon': '/images/money_icon.png',
                'text': 'Lootify Settings',
                'class': 'btn-lootify',
                'after': getMenuItem('Lootify', 'category'),
                'click': function() {
                    $('.log-paster-settings').toggle();
                },
            });
        } else {
            $lootBtn = $('.btn-lootify');
        }
        if (!$('.log-paster-settings').length) {
            let $wrap = $('<div/>', {
                'class': 'log-paster-settings'
            }).hide();
            $lootBtn.after($wrap);

            addSetting({
                'text': 'Allow Lootify to track your ID and username for your personal experience',
                'name': 'TrackUser6',
                'type': 'checkbox',
                'default': 'false',
                'change': function() {
                    window.localStorage.setItem(getUsername() + '-TrackUser6', $(this).prop('checked'));
                }
            }).appendTo($wrap);
            if (!window.localStorage.getItem(getUsername() + '-TrackUser6')) {
                confirmDialog({
                    'title': 'Requesting permission',
                    'text': 'Please notice that Lootify tracks user data such as user ID, username, mob kill statistics/loot data. The purpose of this data is purely for your personal experience. This data can later be used by to lookup your personal Lootify statistics. Do you agree?',
                    'img': true,
                    'cbyes': function() {
                        $('.setting-checkbox.TrackUser6').prop('checked', true);
                        window.localStorage.setItem(getUsername() + '-TrackUser6', 'true');
                    },
                    'cbno': function() {
                        window.localStorage.setItem(getUsername() + '-TrackUser6', 'false');
                    },
                });
            }

            addSetting({
                'text': 'Enable Auto Paster',
                'name': 'AutoPaster',
                'type': 'checkbox',
                'default': 'false',
                'change': function() {
                    window.localStorage.setItem(getUsername() + '-AutoPaster', $(this).prop('checked'));
                }
            }).appendTo($wrap);
            addSetting({
                'text': 'Paste Interval (minutes)',
                'name': 'AutoPasterInterval',
                'type': 'number',
                'min': 15,
                'max': 60,
                'default': 30,
                'change': function() {
                    if($(this).val() >= $(this).attr('min') && $(this).val() <= $(this).attr('max')) {
                        window.localStorage.setItem(getUsername() + '-AutoPasterInterval', $(this).val());
                    }
                }
            }).appendTo($wrap);
        }
    }
    function renderStatus() {
        if (!isFighting() && !isActiveSkill()) {
            $('.ltf-status').remove();
            return false;
        }
        if (!getMenuItem('Status:')) {
            let $kphStats = setMenuItem({
                'class': 'ltf-status',
                'text': 'Status: ' + status,
                'icon': '/images/combat/combat_level.png',
                'after': $('.log-paster-settings'),
                'click': function() {
                    $('[data-for="'+status+'Header"]').click();
                }
            });
        }
        $('.ltf-status').find('span').text('Status: ' + ucfirst(status));
    }
    function setupLogs() {
        if ($('.btn-loot-log').length) {
            $('.btn-loot-log').remove();
        }
        let $logBtn = getMenuItem('Loot Log');
        if (($logBtn && $logBtn.text() === 'Loot Log') && !$logBtn.hasClass('btn-loot-log')) {
            editMenuItem('Loot Log');
        }
        if(!getMenuItem('Loot Log')) {
            setMenuItem({
                'icon': '/images/ui/inventory_icon.png',
                'text': 'Loot Log',
                'class': 'btn-loot-log',
                'before': getMenuItem('Gathering', 'category'),
                'click': function() {
                    $('.item-log-clone').toggle();
                },
            });
        }
        let $logwrap = $('.item-log-window');
        $logwrap.hide();
        let $clone = $logwrap.clone();
        $clone.addClass('item-log-clone ltf-submenu').removeClass('hidden');
        $clone.find('.item-log-timer').remove();
        $clone.find('.item-log-info').remove();
        $clone.find('.drawer-setting-large').addClass('clone').unbind().click(function() {
            reset()
        }).text('Paste and Reset Log');
        if (app.market) {
            $clone.text('Reset Log');
        }
        getMenuItem('Loot Log').after($clone);
    }
    function renderTimer() {
        if (!isFighting() && !isActiveSkill()) {
            $('.time-stats').remove();
            return false;
        }
        if (!getMenuItem('Elapsed:')) {
            let $timeStats = setMenuItem({
                'class': 'time-stats',
                'text': 'Elapsed: 0S',
                'icon': '/images/clock.png',
                'after': $('.ltf-status')
            });
        }
        let activeTimers = ['fighting', 'mining', 'foraging'];
        setTimeout(()=> {
            if (!timer.active) {
                timer.go();
            }
            if (activeTimers.includes(status)) {
                timer.status = status;
            }
        }, 1000);
    }
    function renderKPH() {
        if (!isFighting()) {
            $('.kph-stats').remove();
            $('.kph-wrap').remove();
            return false;
        }
        if (!getMenuItem('Kills:')) {
            let $kphStats = setMenuItem({
                'class': 'kph-stats',
                'text': 'Kills: 0 p/h',
                'icon': '/images/combat/combat_level.png',
                'after': $('.time-stats'),
                'click': function() {
                    $('.kph-wrap').toggle();
                }
            });
            $kphStats.after($('<div/>', {
                'class': 'kph-wrap ltf-submenu'
            }).hide());
        }
        let mobkills = {};
        $.each(total, function(mob, mobinfo) {
            mobkills[mob] = mobinfo.count;
        });
        let totalkills = function() {
            let c = 0;
            $.each(mobkills, function(mob, kills) {
                c += kills;
            });
            return c;
        };
        $('.kph-stats').find('span').text('Kills: ' + addCommas(Math.floor((totalkills()/time)*3600)) + ' p/h');
        $('.kph-wrap').empty();
        $.each(sortObject(mobkills), function(mob, count) {
            let $mobwrap = $('<div/>', {
                'class': 'ltf-header mob-header'
            }).appendTo($('.kph-wrap'));
            $mobwrap.append($('<span/>').css('display','block').text(mob + ': ' + Math.floor((count/time)*3600) + ' p/h'));
        });
    }
    function renderGPH() {
        if(!isActiveSkill() && !isFighting()) {
            $('.gph-stats').remove();
            $('.gph-wrap').remove();
            return false;
        }
        if (!getMenuItem('Gold:')) {
            let $gphStats = setMenuItem({
                'class': 'gph-stats',
                'text': 'Loot value: 0 p/h',
                'icon': '/images/ui/shop_icon.png',
                'after': $('.time-stats'),
                'click': function() {
                    $('.gph-wrap').toggle();
                }
            });
            $gphStats.after($('<div/>', {
                'class': 'gph-wrap ltf-submenu'
            }).hide());
        }

        let totalGold = 0;
        $.each(totalLoot, function(item, count) {
            if (item === 'Gold') {
                totalGold += count;
            } else {
                totalGold += prices[item] * count;
            }
        });
        $('.gph-stats').find('span').text('Gold: ' + addCommas(Math.floor((totalGold/time)*3600)) + ' p/h');
        $('.gph-wrap').empty();
        let $lootwrap = $('<div/>', {
            'class': 'ltf-header mob-header'
        }).appendTo($('.gph-wrap'));
        $lootwrap.append($('<span/>').css('display','block').text('Loot value: ' + addCommas(Math.floor(totalGold))));
    }
    function renderXPH() {
        if(!isActiveSkill() && !isFighting()) {
            $('.xph-stats').remove();
            $('.xph-wrap').remove();
            return false;
        }
        if (!getMenuItem('Experience:')) {
            let $xphStats = setMenuItem({
                'class': 'xph-stats',
                'text': 'Experience: 0 p/h',
                'icon': '/images/total_level.png',
                'after': $('.time-stats'),
                'click': function() {
                    $('.xph-wrap').toggle();
                }
            });
            $xphStats.after($('<div/>', {
                'class': 'xph-wrap ltf-submenu'
            }).hide());
        }
        let attExp, defExp, strExp, totalExp;

        totalExp = 0;
        if (app.exp) {
            if (isFighting()) {
                attExp = app.exp.attack.current - app.exp.attack.init;
                defExp = app.exp.defense.current - app.exp.defense.init;
                strExp = app.exp.strength.current - app.exp.strength.init;
                totalExp = attExp+defExp+strExp;
            } else if(isStatus('mining')) {
                totalExp = app.exp.mining.current - app.exp.mining.init;
            } else if(isStatus('foraging')) {
                totalExp = app.exp.foraging.current - app.exp.foraging.init;
            }
        }
        $('.xph-stats').find('span').text('Experience: ' + addCommas(Math.floor((totalExp/time)*3600)) + ' p/h');
        $('.xph-wrap').empty();
        let $expwrap = $('<div/>', {
            'class': 'ltf-header mob-header'
        }).appendTo($('.xph-wrap'));
        $expwrap.append($('<span/>').css('display','block').text('Exp earned: ' + totalExp));
    }
    function saveLogs() {
        let $logcats = $('.item-log-window:not(.item-log-clone)').find('.item-log-cateogry');
        if ($logcats.length) {
            $logcats.each(function() {
                if( $(this).find('.item-log-category-closed').length ) {
                    $(this).find('.item-log-category-closed').click();
                }
                let mobs = $(this).find('.item-log-category-open').text().split(" x ");
                !runs[0][mobs[0]] ? runs[0][mobs[0]] = {} : 1;
                runs[0][mobs[0]].count = parseInt(mobs[1]);
                $(this).find('.item-log-item').each(function() {
                    if( $(this).text() !== "None" ) {
                        let loot = $(this).text().split(" x ");
                        !runs[0][mobs[0]].loot ? runs[0][mobs[0]].loot = {} : 1;
                        !runs[0][mobs[0]].loot[loot[0]] ? runs[0][mobs[0]].loot[loot[0]] = {} : 1;
                        runs[0][mobs[0]].loot[loot[0]].count = parseInt(loot[1]);
                    }
                });
            });
        }
    }
    function mergeLogs() {
        if(runs) {
            total = {};
            totalLoot = {};
            $.each(runs, function(num, run) {
                $.each(run, function(mobname, mobinfo) {
                    !total[mobname] ? total[mobname] = {} : 1;
                    !total[mobname].count ? total[mobname].count = 0 : 1;
                    total[mobname].count += mobinfo.count;
                    $.each(mobinfo.loot, function(lootname, lootinfo) {
                        /* SET TOTAL LOOT PER MOB */
                        !total[mobname].loot ? total[mobname].loot = {} : 1;
                        !total[mobname].loot[lootname] ? total[mobname].loot[lootname] = {} : 1;
                        !total[mobname].loot[lootname].count ? total[mobname].loot[lootname].count = 0 : 1;
                        total[mobname].loot[lootname].count += lootinfo.count;
                        /* SET TOTAL LOOT OVERALL */
                        !totalLoot[lootname] ? totalLoot[lootname] = 0 : 1;
                        totalLoot[lootname] += lootinfo.count;
                    });
                });
            });
            delete total[''];
        }
    }
    function renderLogs() {
        if(!runs) { return; }
        let $cat, $mobwrap, $mobheader, $lootentry;
        $('.item-log-clone').find('.item-log-cateogry').remove();
        $.each(sortObject(total), function(mob, mobinfo) {
            $cat = $('<div/>', {
                'class':'item-log-cateogry noselect'
            });
            $mobwrap = $('<div/>', {
                'class': 'ltf-header loot-header'
            }).appendTo($cat);
            $cat.click(function() {
                headers[mob] = headers[mob] == false ? true : false;
                $mobwrap.find('.loot-entry').toggle();
            });
            $mobwrap.append($('<div/>').text(mob + ' x ' + mobinfo.count));
            let icon = (status != 'fighting') ? getLocationIcon(getLocationID(mob)) : getMobIcon(mob);
            $mobwrap.append($('<img/>').addClass('drawer-item-icon').attr('src', icon));
            if(mobinfo.loot) {
                $.each(sortObject(mobinfo.loot), function(loot, lootinfo) {
                    $lootentry = $('<div/>', {
                        'class': 'loot-entry'
                    }).text(loot + ' x ' + lootinfo.count).appendTo($cat);
                    if(isRare(loot)) {
                        $lootentry.addClass('loot-rare');
                    } else if(isEvent(loot)) {
                        $lootentry.addClass('loot-event');
                    } else if(isUniversal(loot)) {
                        $lootentry.addClass('loot-universal');
                    }
                    headers[mob] = headers[mob] ? headers[mob] : true;
                    if (!headers[mob]) {
                        $lootentry.hide();
                    }
                });
            } else {
                $lootentry = $('<div/>', {
                    'class': 'loot-entry'
                }).html('<i>None</i>').appendTo($cat);
                headers[mob] = headers[mob] ? headers[mob] : false;
                if (!headers[mob]) {
                    $lootentry.hide();
                }
            }
            $('.drawer-setting-large.clone').before($cat);
        });
    }
    /* ############## ACTIONS ############## */
    function getLogs() {
        let logs = [];
        let $logcats = $('.item-log-window:not(.item-log-clone)').find('.item-log-cateogry');
        if ($logcats.length) {
            $logcats.each(function(k,v) {
                if( $(this).find('.item-log-category-closed').length ) {
                    $(this).find('.item-log-category-closed').click();
                }
                if( $(this).find('.item-log-category-open').length ) {
                    logs.push($(this).find('.item-log-category-open').text());
                    $(this).find('.item-log-item').each(function() {
                        if( $(this).text() !== "None" ) {
                            logs.push($(this).text());
                        }
                    });
                }
            });
        }
        return logs;
    }
    function clearLogs(m = 'all') {
        if (m == 'single') {
            runs.unshift({});
        } else if (m == 'all') {
            if (app.exp) {
                app.exp.attack.init = app.exp.attack.current;
                app.exp.defense.init = app.exp.defense.current;
                app.exp.strength.init = app.exp.strength.current;
                app.exp.mining.init = app.exp.mining.current;
                app.exp.foraging.init = app.exp.foraging.current;
            }
            timer.reset();
            runs = [{}];
            total = {};
            app.market = false;
            isSetup = false;
            $('.item-log-clone').remove();
        }
        $('.item-log-window').find('.drawer-setting-large.active:not(.clone)').click();
    }
    function reset(m = 'all') {
        submitStats(getLogs(), getTH());
        clearLogs(m);
    }
    /* ############## GAME LIBRARY ############## */
    function isIron() {
        return $('.header-league-icon').attr('src') === '/images/leagues/ironman_league_icon_v5.png';
    }
    function isStatus(txt) {
        return status === txt;
    }
    function isActiveSkill() {
        return isStatus('mining') || isStatus('foraging');
    }
    function isFighting() {
        return isStatus('fighting');
    }
    function isUniversal(txt) {
        return data.item ? data.item.universal.includes(txt) : '';
    }
    function isRare(txt) {
        return data.item ? data.item.rare.includes(txt) : '';
    }
    function isEvent(txt) {
        return data.item ? data.item.event.includes(txt) : '';
    }
    function getZoneMobs(zone) {
        return data.zone ? data.zone[zone] : '';
    }
    function getLocation(loc) {
        return data.location ? data.location.name[loc] : '';
    }
    function getLocationID(loc) {
        let id = 0;
        if (data.location) {
            $.each(data.location.name, function(k,v) {
                if (v === loc) {
                    id = k;
                }
            });
        }
        return id;
    }
    function getLocationIcon(loc) {
        return data.location ? data.location.icon[loc] : '';
    }
    function getMobIcon(name) {
        return data.icon ? data.icon.mob[name] : '';
    }
    function getUsername() {
        return $('.navbar1-box').text().split(' ')[1];
    }
    function getTH() {
        return getEnchantment('32');
    }
    function getEnchantment(id) {
        let TH = 0;
        if(user.enchantments) {
            $.each(user.enchantments, function(k, v) {
                if(v.enchantmentID == id) {
                    TH = v.enchantmentStrength;
                }
            });
        }
        return TH;
    }
    function getLogMobs() {
        let mobs = [];
        $.each(total, function(mob, info) {
            mobs.push(mob);
        });
        return mobs;
    }
    function getPasteInterval() {
        let interval = parseInt($('.AutoPasterInterval').val());
        interval = interval ? interval : 30;
        interval = interval < 15 ? 15 : interval;
        interval = interval > 60 ? 60 : interval;
        return interval;
    }
    function getMarketPrices() {
        let p = {};
        $.getJSON( "https://api.idlescape.xyz/prices", function( d ) {
            if(d) {
                $.each(d.items, function(k, v) {
                    p[v.name] = v.price;
                });
            } else {
                p = false;
            }
        });
        return p;
    }
    /* === ACTIONS === */
    function chat(args) {
        let e = setInterval(()=> {
            let $chat = $('.chat-message-container > .chat-message-list > div');
            if( $chat.length ) {
                clearInterval(e);
                $chat.each(function() {
                    if (!$(this).find('.activity-log').length) {
                        let $msg = $('<div/>', {
                            'class': 'chat-message msg-lootify'
                        }).clone().appendTo($(this));
                        if (args['date'] !== false) {
                            let $date = $('<span/>', {
                                'class': 'message-time-stamp',
                            }).text('['+getDate(new Date)+']').appendTo($msg);
                        }
                        args['color'] = args['color'] ? args['color'] : '#00A0FD';
                        args['glow'] = args['glow'] ? '0 0 3px '+args['glow'] : 'none';
                        let $txt = $('<span/>', {
                            'class':'chat-message-system'
                        }).css({
                            'font-size': '14px',
                            'color': args['color'],
                            'text-shadow': args['glow'],
                        }).text(args['msg']).appendTo($msg);

                        args['ttl'] = args['ttl'] ? args['ttl'] : 5;
                        setTimeout(()=> {
                            $msg.remove();
                        }, args['ttl'] * 60 * 1000);
                    }
                });
            }
        }, 1000);
    }
    function cleanChat() {
        let $chat = $('.chat-message-container');
        if( $chat.length ) {
            $chat.each(function() {
                if($(this).find('.activity-log').length) {
                    $(this).find('.msg-lootify').remove();
                }
            });
        }
    }
    function checkUpdates() {
        if (ver < app.newver) {
            if (!$('.lootify-update').length) {
                if(!~newver.toLowerCase().indexOf('error')) {
                    $('<div/>', {
                        'class': 'lootify-update'
                    }).css({
                        'color': 'red',
                        'width': '80%',
                        'margin': '0 auto',
                    }).text('Update: v'+newver+' available!').insertBefore(getMenuItem('Lootify Settings'));
                }
            }
        }
    }
    /* ############## API INTERACTION ############## */
    function getAppEnabled() {
        getRequest('https://digimol.net/lootify/const.php?t=enabled', function(d) {
            app.enabled = d ? d.toString() : 0;
        });
    }
    function getNewVer() {
        getRequest('https://digimol.net/lootify/const.php?t=ver&m=current', function(d) {
            app.newver = d ? d.toString() : 0;
        });
    }
    function getData() {
        getRequest('https://digimol.net/lootify/json.php?d=data', function(d) {
            data = d ? JSON.parse(d) : {};
        });
    }
    function updateUser() {
        getRequest('https://digimol.net/lootify/api.php?a=updateuser&id='+user.id+'&name='+user.name);
    }
    function getRequest(url, cb = function(){}) {
        $.get('https://digimol.net/lootify/api.php?a=getauth').done(function(d1) {
            $.get(url+'&auth='+d1+'&ver='+ver).done(function(d2) {
                cb(d2);
            });
        });
    }
    function submitStats(logs, th) {
        if ((isFighting() && logs.length < 1) || Object.keys(runs[0]).length === 0 || app.market || (!app.enabled || app.enabled === 'false')) {
            return false;
        }
        updateStatus();
        let targetUrl = 'https://docs.google.com/forms/u/0/d/e/1FAIpQLSch3eG9Tqts0tIvnkk-C5JZeTwfbkWXhxkIpFnxyyaNO26h4Q/formResponse';
        let logEntryId = 'entry.558332813';
        let thEntryId = 'entry.22586929';
        let noteEntryId = 'entry.1726819066';
        let finalTH = (parseInt(th) > 0) ? 'TH '+th : 'None';
        let fullUrl = targetUrl + '?'+ noteEntryId + '=Lootify-' + ver + '&' + thEntryId + '=' + encodeURIComponent(finalTH) + '&' + logEntryId + '=' + encodeURIComponent(logs.join('\n'));
        if (!isFighting()) {
            th = 0;
        } else {
            $.get(fullUrl);
        }

        getRequest('https://digimol.net/lootify/api.php?a=paste&th='+th+'&user='+user.id+'&log='+encodeURIComponent(JSON.stringify(runs[0])));
        chat({
            'msg': 'Lootify - Loot log pasted!',
            'color': '#00a0fd',
        });
    }
    /* ############## NAVIGATION ############## */
    function setMenuItem(args, type = 'item') {
        let $item, $img, e;
        args['text'] = args['text'] ? args['text'] : 'Menu item';
        args['clone'] = args['clone'] ? args['clone'] : 'Shops';
        if(!getMenuItem(args['clone'], type)) { return $('<div>'); }
        $item = getMenuItem(args['clone'], type).clone();
        if(args['class']) {
            $item.addClass(args['class']);
        }
        if(args['css']) {
            $item.css(args['css']);
        }
        $img = $item.find('img').clone();
        $item.unbind().empty();
        if(type === 'item') {
            $item.append($('<span/>').text(args['text']));
        } else if (type === 'category') {
            $item.append($('<b/>').text(args['text']));
        }
        if (args['icon']) {
            $img.attr('src', args['icon']).prependTo($item);
        }
        args['before'] ? args['before'].before($item) : 1;
        args['after'] ? args['after'].after($item) : 1;
        if (typeof args['click'] === 'function') {
            $item.click(args['click']);
        }
        return $item;
    }
    function getMenuItem(txt, type='item') {
        let $item;
        $.each($('.drawer-' + type), function() {
            if (~$(this).text().indexOf(txt)) {
                $item = $(this);
            }
        });
        return $item;
    }
    function editMenuItem(txt) {
        getMenuItem(txt).text('Hidden').hide();
    }
    function removeMenuItem(txt) {
        getMenuItem(txt).remove();
    }
    function addSetting(args) {
        args['text'] = args['text'] ? args['text'] : 'New setting';
        args['name'] = args['name'] ? args['name'] : args['text'].replace(' ', '-').toLowerCase();
        args['type'] = args['type'] ? args['type'] : 'text';
        args['default'] = args['default'] ? args['default'] : 0;
        let $setting = $('<div/>', {
            'class': 'ltf-setting'
        }).append($('<span/>')).append($('<input/>').addClass('setting-'+args['type']));
        $setting.find('span').text(args['text']);
        $setting.find('input').addClass(args['name']).attr('type', args['type']);
        if (args['min']) {
            $setting.find('input').attr('min', args['min']);
        }
        if (args['max']) {
            $setting.find('input').attr('max', args['max']);
        }
        let val = window.localStorage.getItem(getUsername() + '-' + args['name']);
        if (args['type'] === 'checkbox') {
            val = val ? val : args['default'];
            val = val == 'true' ? true : false;
            $setting.find('input').prop('checked', val);
        }
        if (val) {
            $setting.find('input').val(val)
        } else {
            $setting.find('input').val(args['default']);
        }
        if (args['change']) {
            $setting.find('input').change(args['change']);
        }
        return $setting;
    }
    function confirmDialog(args) {
        let $root = $('<div>', {
            'class': 'MuiDialog-root'
        }).attr({
            'role': 'presentation'
        }).css({
            'position': 'fixed',
            'z-index': '1300',
            'inset': '0px'
        }).appendTo($('body'));

        let $backdrop = $('<div>', {
            'class': 'MuiBackdrop-root'
        }).attr({
            'aria-hidden': 'true'
        }).css({
            'opacity': '1',
            'transition': 'opacity 225ms cubic-bezier(0.4, 0, 0.2, 1) 0ms',
            'z-index': '100'
        }).appendTo($root);

        let $container = $('<div>', {
            'class': 'MuiDialog-container MuiDialog-scrollPaper',
        }).attr({
            'role': 'none presentation'
        }).css({
            'opacity': '1',
            'transition': 'opacity 225ms cubic-bezier(0.4, 0, 0.2, 1) 0ms'
        }).appendTo($root);

        let $paper = $('<div>', {
            'class': 'MuiPaper-root MuiDialog-paper MuiDialog-paperScrollPaper MuiDialog-paperWidthSm MuiPaper-elevation24 MuiPaper-rounded'
        }).attr({
            'role': 'dialog'
        }).css({
            'z-index': '200'
        }).appendTo($container);

        if (args['img']) {
            let $image = $('<img>', {
                'class': 'ltf-dialog-image'
            }).attr({
                'src': 'https://digimol.net/lootify/img/lootify_logo.png'
            }).appendTo($paper);
        }

        let $title = $('<div>', {
            'class': 'MuiDialogTitle-root'
        }).appendTo($paper);

        $('<h2>', {
            'class': 'MuiTypography-root MuiTypography-h6'
        }).text(args['title']).appendTo($title);

        $('<p>', {
            'class': 'MuiTypography-root MuiDialogContentText-root MuiTypography-body1 MuiTypography-colorTextSecondary'
        }).text(args['text']).appendTo($paper);

        let $actions = $('<div>', {
            'class':  'MuiDialogActions-root MuiDialogActions-spacing'
        }).appendTo($paper);

        if(!args['cbyes']) {
            args['cbyes'] = function(){};
        }

        if(!args['cbno']) {
            args['cbno'] = function(){};
        }

        $backdrop.click(function() {
            $root.remove();
        });

        let $button = [];
        $button[0] = $('<div>', {
            'class': 'item-dialogue-button idlescape-button idlescape-button-green'
        }).attr({
            'variant': 'contained',
            'color': 'secondary'
        }).text('Yes').appendTo($actions);

        $button[0].click(function() {
            $root.remove();
            args['cbyes']();
        });

        $button[1] = $('<div>', {
            'class': 'item-dialogue-button idlescape-button idlescape-button-red'
        }).attr({
            'variant': 'contained',
            'color': 'secondary'
        }).text('No').appendTo($actions);

        $button[1].click(function() {
            $root.remove();
            args['cbno']();
        });
    }
    /* ############## SOCKET SETUP ############## */
    const sockets = [];
    const nativeWebSocket = window.WebSocket;
    window.WebSocket = function(...args){
        const socket = new nativeWebSocket(...args);
        sockets.push(socket);
        return socket;
    };
    let setupSocket = setInterval(()=> {
        if(sockets.length != 0){
            clearInterval(setupSocket);
            sockets[0].addEventListener('message', (e) => messageHandler(e));
        }
    }, 1000);
    function messageHandler(e) {
        let msg = e.data;
        msg = (msg.match(/^[0-9]+(\[.+)$/) || [])[1];
        if(msg && ~msg.indexOf('"update player"')) {
            let d = JSON.parse(msg.split('Socket'))[1];
            if(~msg.indexOf('activeEnchantments')) {
                if(~msg.indexOf('"portion":"all"')) {
                    user.enchantments = d.value.activeEnchantments;
                    if(window.localStorage.getItem(getUsername() + '-TrackUser')) {
                        user.id = d.value.id;
                        user.name = d.value.username;
                    } else {
                        user.id = '0';
                        user.name = 'Unknown';
                    }
                    !app.exp ? app.exp = {} : 1;
                    !app.exp.attack ? app.exp.attack = {} : 1;
                    !app.exp.defense ? app.exp.defense = {} : 1;
                    !app.exp.strength ? app.exp.strength = {} : 1;
                    !app.exp.mining ? app.exp.mining = {} : 1;
                    !app.exp.foraging ? app.exp.foraging = {} : 1;
                    app.exp.attack.init = parseInt(d.value.skills.attack.experience);
                    app.exp.defense.init = parseInt(d.value.skills.defense.experience);
                    app.exp.strength.init = parseInt(d.value.skills.strength.experience);
                    app.exp.mining.init = parseInt(d.value.skills.mining.experience);
                    app.exp.foraging.init = parseInt(d.value.skills.foraging.experience);
                    app.exp.attack.current = parseInt(d.value.skills.attack.experience);
                    app.exp.defense.current = parseInt(d.value.skills.defense.experience);
                    app.exp.strength.current = parseInt(d.value.skills.strength.experience);
                    app.exp.mining.current = parseInt(d.value.skills.mining.experience);
                    app.exp.foraging.current = parseInt(d.value.skills.foraging.experience);
                } else {
                    user.enchantments = d.value[0];
                }
            }
        }
        // GET MINING AND FORAGING ITEMS
        if (isActiveSkill()) {
            if (msg && ~msg.indexOf('"update inventory"') && !~msg.indexOf('Essence')) {
                let d = JSON.parse(msg)[1];
                if( data.skill && data.skill[status].allowed.includes(d.item.name) ) {
                    let loc = getLocation(location);
                    !runs[0][loc] ? runs[0][loc] = {} : 1;
                    !runs[0][loc].count ? runs[0][loc].count = 0 : 1;
                    runs[0][loc].count++;
                    !runs[0][loc].loot ? runs[0][loc].loot = {} : 1;
                    !runs[0][loc].loot[d.item.name] ? runs[0][loc].loot[d.item.name] = {} : 1;
                    !runs[0][loc].loot[d.item.name].count ? runs[0][loc].loot[d.item.name].count = 0 : 1;
                    runs[0][loc].loot[d.item.name].count++;
                }
            }
        }
        // GET COMBAT EXP
        if(msg && ~msg.indexOf('"update player"') && ~msg.indexOf('"skills"')) {
            !app.exp ? app.exp = {} : 1;
            !app.exp.attack ? app.exp.attack = {} : 1;
            !app.exp.defense ? app.exp.defense = {} : 1;
            !app.exp.strength ? app.exp.strength = {} : 1;
            !app.exp.mining ? app.exp.mining = {} : 1;
            !app.exp.foraging ? app.exp.foraging = {} : 1;
            let d = JSON.parse(msg)[1];
            if (~msg.indexOf('"attack"')) {
                app.exp.attack.current = parseInt(d.value.experience);
            } else if (~msg.indexOf('"defense"')) {
                app.exp.defense.current = parseInt(d.value.experience);
            } else if (~msg.indexOf('"strength"')) {
                app.exp.strength.current = parseInt(d.value.experience);
            } else if (~msg.indexOf('"mining"')) {
                app.exp.mining.current = parseInt(d.value.experience);
            } else if (~msg.indexOf('"foraging"')) {
                app.exp.foraging.current = parseInt(d.value.experience);
            }
        }
        // UPDATE STATUS
        if(msg && ~msg.indexOf('"start animation"')) {
            let d = JSON.parse(msg)[1];
            status = d.action;
            location = d.location;
        }
        // CHECK IF PLAYER WENT TO MARKET
        if(msg && ~msg.indexOf('"get player marketplace items"')) {
            let d = JSON.parse(msg)[1];
            if (!app.market) {
                app.market = true;
                $('.drawer-setting-large.clone').text('Reset Log');
                chat({
                    'msg': "Lootify - Paster has been disabled to prevent skewed statistics. You can still use Lootify, but won't be able to paste your data to our server. Please reset your log to enable the paster again!",
                    'color': 'red',
                    'date': false
                });
            }
        }
    }
    function updateStatus() {
        if ($('.status-bar').length) {
            if(~$('.status-bar').text().indexOf('Fighting')) {
                status = 'fighting';
            } else if(~$('.status-bar').text().indexOf('Idling')) {
                status = 'idling';
            }
        }
    }
    /* ############## GENERAL LIBRARY ############## */
    function addCommas(nStr) {
        nStr += '';
        let x = nStr.split('.');
        let x1 = x[0];
        let x2 = x.length > 1 ? '.' + x[1] : '';
        let rgx = /(\d+)(\d{3})/;
        while (rgx.test(x1)) {
            x1 = x1.replace(rgx, '$1' + ',' + '$2');
        }
        return x1 + x2;
    }
    function getDate(date) {
        let h = date.getHours();
        let m = date.getMinutes();
        let s = date.getSeconds()
        m = m < 10 ? '0'+m : m;
        s = s < 10 ? '0'+s : s;
        h = h < 10 ? '0'+h : h;
        let strTime = h + ':' + m + ':' + s;
        if(~$('.chat-message .message-time-stamp').text().indexOf(' AM]') || ~$('.chat-message .message-time-stamp').text().indexOf(' PM]')) {
            let ampm = parseInt(h) >= 12 ? 'PM' : 'AM';
            h = h % 12;
            h = h ? h : 12;
            strTime = h + ':' + m + ':' + s + ' '  + ampm
        }
        return strTime;
    }
    function getTime() {
        let count = 0;
        $.each(timer.starts, function(k, start) {
            let stop = !timer.stops[k] ? new Date() : timer.stops[k];
            count += +stop - +start;
        });
        let s = Math.floor((count /  1000)) % 60;
        let m = Math.floor((count / 60000)) % 60;
        let h = Math.floor((count / 3600000)) % 24;
        let d = Math.floor((count / 86400000)) % 7;
        let w = Math.floor((count / 604800000)) % 52;
        let y = Math.floor((count / 31557600000));
        let timeStr = '';
        timeStr += (y>0) ? y+'Y ' : '';
        timeStr += (w>0) ? w+'W ' : '';
        timeStr += (d>0) ? d+'D ' : '';
        timeStr += (h>0) ? h+'H ' : '';
        timeStr += (m>0) ? m+'M ' : '';
        timeStr += (s>0) ? s+'S ' : '';
        time = (count /  1000);
        return timeStr;
    }
    function sortObject(o) {
        var sorted = {},
            key, a = [];
        for (key in o) {
            if (o.hasOwnProperty(key)) {
                a.push(key);
            }
        }
        a.sort();
        for (key = 0; key < a.length; key++) {
            sorted[a[key]] = o[a[key]];
        }
        return sorted;
    }
    function ucfirst(string) {
        return string.charAt(0).toUpperCase() + string.slice(1);
    }

})();