Anastasia

Dobby

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

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

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

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

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

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

Advertisement:

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

Advertisement:

// ==UserScript==
// @name         Anastasia
// @namespace    http://tampermonkey.net/
// @version      1.62
// @description  Dobby
// @author       xxxx
// @match        https://*.the-west.*/game.php*
// @match        https://*.the-west.*/*
// @include      https://*.the-west.*/game.php*
// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant        none
// @run-at       document-idle
// @license MIT 
// ==/UserScript==

(function() {

    function JobPrototype(x,y,id) {
        this.x = x;
        this.y = y;
        this.id = id;
        this.silver = false;
        this.distance = 0;
        this.experience = 0;
        this.money = 0;
        this.motivation = 0;
        this.stopMotivation = 75;
        this.set = -1;
    };
    JobPrototype.prototype = {
        setSilver: function(isSilver) {
            this.silver = isSilver;
        },
        calculateDistance:function() {
            this.distance = GameMap.calcWayTime({x:this.x,y:this.y},Character.position);
        },
        setExperience:function(xp) {
            this.experience = xp;
        },
        setMoney:function(money) {
            this.money = money;
        },
        setMotivation:function(motivation) {
            this.motivation = motivation;
        },
        setStopMotivation:function(stopMotivation) {
            this.stopMotivation = stopMotivation;
        },
        setSet:function(setIndex) {
            this.set = setIndex;
        }
    };
    function ConsumablePrototype(id,image,name) {
        this.id = id;
        this.energy = 0;
        this.motivation = 0;
        this.health = 0;
        this.selected = true;
        this.image = image;
        this.count = 0;
        this.name = name;
    };
    ConsumablePrototype.prototype = {
        setEnergy:function(energy) {
            this.energy = energy;
        },
        setMotivation:function(motivation) {
            this.motivation = motivation;
        },
        setHealth:function(health) {
            this.health = health;
        },
        setSelected:function(select) {
            this.selected = select;
        },
        setCount:function(count) {
            this.count = count;
        }
    };

     Dobby = {
         window:null,
         jobsLoaded:false,
         allJobs:[],
         allConsumables:[],
         consumableUsed:[],
         addedJobs:[],
         forts:[],
         homeTown:null,
         jobFilter:{filterOnlySilver:false,filterNoSilver:false,filterCenterJobs:false,filterJob:""},
         sortJobTableXp:0,
         sortJobTableDistance:0,
         sortJobTableMoney:0,
         statusText:"Inactiv",
         motivationCache:{},
         POLL_MS:50,
         _wakeups:[],
         _keepAlive:null,
         tabHidden:false,
         lastActivityTime:0,
         _watchdogTimer:null,
         _watchdogState:{queueLen:-1,queueSince:0,xp:-1,lastPos:null,lastMoveTime:0},
         STALL_TIMEOUT_MS:60000,
         jobTablePosition:{content:"0px",scrollbar:"0px"},
         addedJobTablePosition:{content:"0px",scrollbar:"0px"},
         consumableTablePosition:{content:"0px",scrollbar:"0px"},
         currentState:0,
         states:["idle","running","waiting for a consumable cooldown"],
         sets:null,
         selectedSet:0,
         selectedSleepPlace:-2,
         travelSet:-1,
         jobSet:-1,
         healthSet:-1,
         regenerationSet:-1,
         language:"",
         searchKeys:{
             "en_DK":{
                 energy:"Energy",
                 energyText:"Energy increase:",
                 motivation:"Work motivation",
                 motivationText:"Work motivation increase:",
                 health: "Health point bonus",
                 healthText:"Health point bonus:"
             },
             "sk_SK":{
                 energy:"Energia",
                 energyText:"Zvýšenie energie:",
                 motivation:"Pracovnej motivácie",
                 motivationText:"Zvýšenie pracovnej motivácie:",
                 health: "Bonus bodov zdravia",
                 healthText:"Bonus bodov zdravia:"
             },
             "cs_CZ":{
                 energy:"Energie",
                 energyText:"Zvýšení energie:",
                 motivation:"Pracovní motivace",
                 motivationText:"Zvýšení pracovní motivace:",
                 health: "Bonus zdraví",
                 healthText:"Bonus zdraví:"
             },
             "hu_HU":{
                 energy:"Energia növekedése:",
                 energyText:"Energia növekedése:",
                 motivation:"Munka motiváció növelése:",
                 motivationText:"Munka motiváció növelése:",
                 health: "Életerő bónusz",
                 healthText:"Életerő bónusz:"
             },
             "pl_PL":{
                 energy:"Wzrost energii:",
                 energyText:"Wzrost energii:",
                 motivation:"Zwiększenie motywacji do pracy:",
                 motivationText:"Zwiększenie motywacji do pracy:",
                 health: "Bonus Punktów życia:",
                 healthText:"Bonus Punktów życia:"
             },
             "ro_RO":{
                 energy:"Energie",
                 energyAlt:"energie",
                 energyText:"Energie mărită:",
                 motivation:"motiva",
                 motivationAlt:"motivatie",
                 motivationText:"Creştere a motivaţiei de muncă:",
                 health: "viaţă",
                 healthAlt:"viata",
                 healthText:"Puncte de viaţă:"
             },


         },
         consumableSelection:{energy:false,motivation:false,health:false},
         isRunning:false,
         currentJob:{job:0,direction:true},
         jobRunning:false,
         settings:{
             addEnergy:false,
             addMotivation:false,
             addHealth:false,
             healthStop:10,
             setWearDelay:5,
             jobDelayMin:0,
             jobDelayMax:0,
             enableRegeneration:false,
             keepAliveBackground:true
         },
         statistics:{
             jobsInSession:0,
             xpInSession:0,
             totalJobs:0,
             totalXp:0,
         }

    };
    Dobby.isNumber = function(potentialNumber) {
        return Number.isInteger(parseInt(potentialNumber));
    };

    Dobby.generateRandomNumber = function(min,max) {
        var minN = Math.min(min,max);
        var maxN = Math.max(min,max);

        return Math.floor((minN + Math.random() * (maxN-minN+1)));
    };

    Dobby.wake = function() {
        var pending = Dobby._wakeups.splice(0, Dobby._wakeups.length);
        for(var i = 0; i < pending.length; i++) {
            try { pending[i](); } catch(e) {}
        }
        if(Dobby.updateLiveStats) Dobby.updateLiveStats();
        if(Dobby.isRunning) Dobby.touchActivity();
    };

    Dobby.getBackgroundPollMs = function() {
        return Dobby.settings.keepAliveBackground ? Dobby.POLL_MS : 1000;
    };

    Dobby.tick = function() {
        return new Promise(function(resolve) {
            Dobby._wakeups.push(resolve);
            if(!document.hidden) {
                requestAnimationFrame(function() {
                    setTimeout(resolve, Dobby.POLL_MS);
                });
            } else {
                setTimeout(resolve, Dobby.getBackgroundPollMs());
            }
        });
    };

    Dobby.waitUntil = async function(conditionFn, shouldContinue, timeoutMs) {
        if(!shouldContinue) {
            shouldContinue = function() { return true; };
        }
        var start = Date.now();
        while(shouldContinue()) {
            try {
                if(conditionFn()) return true;
            } catch(e) {}
            if(timeoutMs && (Date.now() - start) >= timeoutMs) return false;
            await Dobby.tick();
        }
        return false;
    };

    Dobby.touchActivity = function() {
        Dobby.lastActivityTime = Date.now();
    };

    Dobby.isInTransit = function() {
        if(typeof Character === 'undefined' || !Character.position) return false;
        if(Dobby._watchdogState.lastMoveTime && (Date.now() - Dobby._watchdogState.lastMoveTime) < 45000) {
            return true;
        }
        if(Dobby.addedJobs.length && typeof GameMap !== 'undefined') {
            var idx = Dobby.currentJob.job;
            if(idx >= 0 && idx < Dobby.addedJobs.length) {
                var job = Dobby.addedJobs[idx];
                var wayTime = GameMap.calcWayTime(Character.position, {x: job.x, y: job.y});
                if(wayTime > 0 && Dobby._watchdogState.lastMoveTime && (Date.now() - Dobby._watchdogState.lastMoveTime) < 60000) {
                    return true;
                }
            }
        }
        if(typeof TaskQueue !== 'undefined' && TaskQueue.queue) {
            for(var i = 0; i < TaskQueue.queue.length; i++) {
                var task = TaskQueue.queue[i];
                if(task && task.type === 'walk') return true;
                if(task && task.constructor && task.constructor.name === 'TaskWalk') return true;
            }
        }
        return false;
    };

    Dobby.updateWatchdogPosition = function() {
        if(typeof Character === 'undefined' || !Character.position) return;
        var pos = Character.position;
        var state = Dobby._watchdogState;
        if(!state.lastPos) {
            state.lastPos = { x: pos.x, y: pos.y };
            return;
        }
        if(state.lastPos.x !== pos.x || state.lastPos.y !== pos.y) {
            state.lastPos = { x: pos.x, y: pos.y };
            state.lastMoveTime = Date.now();
            Dobby.touchActivity();
        }
    };

    Dobby.startWatchdog = function() {
        if(Dobby._watchdogTimer) return;
        Dobby.touchActivity();
        Dobby._watchdogState = { queueLen: -1, queueSince: Date.now(), xp: -1, lastPos: null, lastMoveTime: 0 };
        Dobby._watchdogTimer = setInterval(function() {
            if(!Dobby.isRunning) return;
            var now = Date.now();
            Dobby.updateWatchdogPosition();
            var qLen = (typeof TaskQueue !== 'undefined' && TaskQueue.queue) ? TaskQueue.queue.length : 0;
            var xp = (typeof Character !== 'undefined') ? Character.experience : -1;
            if(qLen !== Dobby._watchdogState.queueLen) {
                Dobby._watchdogState.queueLen = qLen;
                Dobby._watchdogState.queueSince = now;
                Dobby.touchActivity();
            }
            if(xp !== Dobby._watchdogState.xp) {
                Dobby._watchdogState.xp = xp;
                Dobby.touchActivity();
            }
            if(Dobby.isInTransit()) return;
            var queueStuck = qLen > 0 && (now - Dobby._watchdogState.queueSince) >= Dobby.STALL_TIMEOUT_MS;
            var idleStuck = (now - Dobby.lastActivityTime) >= Dobby.STALL_TIMEOUT_MS;
            if(!queueStuck && !idleStuck) return;
            Dobby.updateRunStatus("Blocaj detectat — repornire automată...");
            Dobby.cancelJobs();
            Dobby.wake();
            Dobby.touchActivity();
            Dobby._watchdogState.queueSince = now;
            Dobby.clearMotivationCache();
            Dobby.run();
        }, 15000);
    };

    Dobby.stopWatchdog = function() {
        if(Dobby._watchdogTimer) {
            clearInterval(Dobby._watchdogTimer);
            Dobby._watchdogTimer = null;
        }
    };

    Dobby.getJobWaitTimeout = function(jobCount) {
        return Math.max(180000, jobCount * 90000 + 60000);
    };

    Dobby.sleepMs = async function(ms) {
        var end = Date.now() + ms;
        while(Date.now() < end) {
            await Dobby.tick();
        }
    };

    Dobby.startKeepAlive = function() {
        if(!Dobby.settings.keepAliveBackground || Dobby._keepAlive) return;
        try {
            var ctx = new (window.AudioContext || window.webkitAudioContext)();
            var osc = ctx.createOscillator();
            var gain = ctx.createGain();
            gain.gain.value = 0.0001;
            osc.connect(gain);
            gain.connect(ctx.destination);
            osc.start();
            Dobby._keepAlive = { ctx: ctx, osc: osc };
        } catch(e) {}
    };

    Dobby.stopKeepAlive = function() {
        if(!Dobby._keepAlive) return;
        try {
            Dobby._keepAlive.osc.stop();
            Dobby._keepAlive.ctx.close();
        } catch(e) {}
        Dobby._keepAlive = null;
    };

    Dobby.hookAjax = function() {
        if(Dobby._ajaxHooked || typeof Ajax === 'undefined') return;
        Dobby._ajaxHooked = true;
        var wrap = function(original) {
            return function() {
                var args = Array.prototype.slice.call(arguments);
                var callback = args[args.length - 1];
                if(typeof callback === 'function') {
                    args[args.length - 1] = function() {
                        Dobby.wake();
                        return callback.apply(this, arguments);
                    };
                }
                var result = original.apply(Ajax, args);
                Dobby.wake();
                return result;
            };
        };
        if(Ajax.get) Ajax.get = wrap(Ajax.get);
        if(Ajax.remoteCall) Ajax.remoteCall = wrap(Ajax.remoteCall);
        if(Ajax.remoteCallMode) Ajax.remoteCallMode = wrap(Ajax.remoteCallMode);
    };

    Dobby.onVisibilityChange = function() {
        Dobby.tabHidden = document.hidden;
        if(!Dobby.isRunning) return;
        if(document.hidden) {
            Dobby.startKeepAlive();
            Dobby.updateRunStatus("Merge în fundal (keep-alive activ)");
        } else {
            Dobby.wake();
            Dobby.updateRunStatus("Merge — tab activ");
        }
    };

    Dobby.loadJobs = function() {
        if(!Dobby.jobsLoaded) {
        new UserMessage("Loading...", UserMessage.TYPE_HINT).show();
        var tiles = [];
        var index = 0;
        var currentLength = 0;
        var maxLength = 299;
        Ajax.get('map','get_minimap',{},function(r){
            var tiles = [];
            var jobs = [];

            for(var townNumber in r.towns) {
                if(r.towns[townNumber].town_id == Character.homeTown.town_id) {
                    Dobby.homeTown = r.towns[townNumber];
                    break;
                }
            }

            /*for(var fortNumber in r.forts) {
                for(var fortNumber2 in r.forts[fortNumber]) {
                    var fort = r.forts[fortNumber][fortNumber2];
                    if(fort['fort']['alliance_id'] == Character.homeTown.alliance_id) {
                       Dobby.forts.push(fort['fort']);
                    }
                }
            }*/

            for(var jobGroup in r.job_groups) {
                var group = r.job_groups[jobGroup];
                var jobsGroup = JobList.getJobsByGroupId(parseInt(jobGroup));
                for(var tilecoord = 0; tilecoord < group.length;tilecoord++) {
                    var xCoord = Math.floor(group[tilecoord][0]/GameMap.tileSize);
                    var yCoord = Math.floor(group[tilecoord][1]/GameMap.tileSize);
                    if(currentLength == 0) {
                        tiles[index] = [];
                    }
                    tiles[index].push([xCoord,yCoord]);
                    currentLength++;
                    if(currentLength == maxLength) {
                        currentLength = 0;
                        index++;
                    }
                    for(var i = 0 ; i < jobsGroup.length;i++) {
                    jobs.push(new JobPrototype(group[tilecoord][0],group[tilecoord][1],jobsGroup[i].id));
                    }
                }
            }
            var toLoad = tiles.length;
            var loaded = 0;
                for(var blocks = 0; blocks < tiles.length;blocks++) {
                    GameMap.Data.Loader.load(tiles[blocks],function(){
                        loaded++;
                        if(loaded == toLoad) {
                            Dobby.jobsLoaded = true;
                            Dobby.allJobs = jobs;
                            Dobby.findAllConsumables();
                            Dobby.createWindow();
                        }
                    });
                }
        });
      }else {
          Dobby.findAllConsumables();
          Dobby.createWindow();
      }
    };
    Dobby.loadJobData = function(callback) {
        Ajax.get('work','index',{},function(r) {
            if(r.error) {
                console.log(r.error);
                return;
            }
            JobsModel.initJobs(r.jobs);
            callback();
        });
    };
    Dobby.loadSets = function(callback) {
        Ajax.remoteCallMode('inventory', 'show_equip', {}, function(r) {
            Dobby.sets = r.data;
            callback();
        });
    };
    Dobby.loadLanguage = function(callback) {
        Ajax.remoteCall("settings", "settings", {}, function(resp) {
             Dobby.language = resp.lang.account.key;
             if(callback) callback();
         });
    };
    Dobby.getSearchKeys = function() {
        if(Dobby.language && Dobby.searchKeys[Dobby.language]) {
            return Dobby.searchKeys[Dobby.language];
        }
        if(Dobby.searchKeys.en_DK) return Dobby.searchKeys.en_DK;
        for(var lang in Dobby.searchKeys) {
            return Dobby.searchKeys[lang];
        }
        return null;
    };
    Dobby.normalizeBonusText = function(text) {
        if(!text) return '';
        return text.toLowerCase()
            .replace(/ș/g, 's').replace(/ş/g, 's')
            .replace(/ț/g, 't').replace(/ţ/g, 't')
            .replace(/ă/g, 'a').replace(/â/g, 'a').replace(/î/g, 'i');
    };
    Dobby.getBagItems = function() {
        var items = [];
        var seen = {};
        var push = function(item) {
            if(!item || !item.obj || !item.obj.item_id || seen[item.obj.item_id]) return;
            seen[item.obj.item_id] = true;
            items.push(item);
        };
        if(typeof Bag === 'undefined') return items;
        if(Bag.items) {
            for(var key in Bag.items) {
                if(Bag.items.hasOwnProperty(key)) push(Bag.items[key]);
            }
        }
        if(Bag.content && Bag.content.items) {
            for(var key2 in Bag.content.items) {
                if(Bag.content.items.hasOwnProperty(key2)) push(Bag.content.items[key2]);
            }
        }
        if(typeof Bag.getItems === 'function') {
            try {
                var list = Bag.getItems();
                if(list && list.length) {
                    for(var i = 0; i < list.length; i++) push(list[i]);
                }
            } catch(e) {}
        }
        return items;
    };
    Dobby.loadJobMotivation = function(index,callback) {
        var job = Dobby.addedJobs[index];
        var key = job.x + "-" + job.y + "-" + job.id;
        var cached = Dobby.motivationCache[key];
        if(cached && (Date.now() - cached.time < 4000)) {
            callback(cached.value);
            return;
        }
        Ajax.get('job','job',{jobId:job.id,x:job.x,y:job.y},function(r){
            var value = r.motivation*100;
            Dobby.motivationCache[key] = {value:value,time:Date.now()};
            callback(value);
        });
    };
    Dobby.invalidateJobMotivation = function(index) {
        var job = Dobby.addedJobs[index];
        if(!job) return;
        delete Dobby.motivationCache[job.x + "-" + job.y + "-" + job.id];
    };
    Dobby.clearMotivationCache = function() {
        Dobby.motivationCache = {};
    };
    Dobby.ensureStatusOverlay = function() {
        if($('#dobby2-status-overlay').length) return;
        $('body').append('<div id="dobby2-status-overlay" style="position:fixed;top:calc(6px + 2cm);left:50%;transform:translateX(-50%);z-index:99999;background:rgba(20,20,20,0.88);color:#f5f5f5;padding:7px 16px;border-radius:8px;font:13px Arial,sans-serif;pointer-events:none;display:none;box-shadow:0 2px 8px rgba(0,0,0,0.4);"></div>');
    };
    Dobby.updateRunStatus = function(message) {
        Dobby.statusText = message;
        Dobby.touchActivity();
        Dobby.ensureStatusOverlay();
        var $overlay = $('#dobby2-status-overlay');
        if(Dobby.isRunning) {
            $overlay.text(message).show();
        } else {
            $overlay.hide();
        }
        $('.dobby2window .dobby_status').text(message);
    };
    Dobby.getJobName = function(id) {
        return JobList.getJobById(id).name;
    };
    Dobby.getJobIcon = function(silver,id,x,y) {
        var html ='<div class="centermap" onclick="GameMap.center(' + x + ',' + y + ');"style="position: absolute;background-image: url(\'../images/map/icons/instantwork.png\');width: 20px;height: 20px;top: 0;right: 3px;cursor: pointer;"></div>';
        var silverHtml = "";
        if(silver) {
             silverHtml = '<div class="featured silver"></div>';
         }
        return'<div class="job" style="left: 0; top: 0; position: relative;"><div  onclick="" class="featured"></div>' + silverHtml + html + '<img src="../images/jobs/' + JobList.getJobById(id).shortname + '.png" class="job_icon"></div>';
    };
    Dobby.getConsumableIcon = function(src) {
        return "<div><img src ="+ src + "></div>";
    };
    Dobby.checkIfSilver = function(x,y,id) {
        var key = x + "-" + y;
        var jobData = GameMap.JobHandler.Featured[key];
        if(jobData == undefined || jobData[id] == undefined) {
            return false;
        }else {
            return jobData[id].silver;
        }
    };
    Dobby.compareUniqueJobs = function(job,jobs){
        for(var i = 0 ; i < jobs.length;i++) {
            if(jobs[i].id == job.id) {
                if(job.silver && !jobs[i].silver || (job.silver == jobs[i].silver &&job.distance < jobs[i].distance)) {
                    jobs.splice(i,1);
                    jobs.push(job);
                }
                return;
            }
        }
        jobs.push(job);
    };
    Dobby.findJobData = function(job) {
        for(var i = 0 ; i < JobsModel.Jobs.length;i++) {
            if(JobsModel.Jobs[i].id == job.id) {
                return JobsModel.Jobs[i];
            }
        }
    };
    Dobby.parseJobData = function(jobs) {
        for(var job = 0 ; job < jobs.length;job++) {
            var currentJob = jobs[job];
            var data = Dobby.findJobData(currentJob);
            var xp = data.basis.short.experience;
            var money = data.basis.short.money;
            if(currentJob.silver) {
                xp = Math.ceil(1.5*xp);
                money = Math.ceil(1.5*money);
            }
            currentJob.setExperience(xp);
            currentJob.setMoney(money);
        }
    };
    Dobby.updateJobDistances = function() {
        for(var i = 0; i < Dobby.allJobs.length;i++) {
            Dobby.allJobs[i].calculateDistance();
        }
    };
    Dobby.getAllUniqueJobs = function() {
        Dobby.updateJobDistances();
        var jobs = [];
        for(var i = 0 ; i < Dobby.allJobs.length;i++) {
            var currentJob = Dobby.allJobs[i];
            if(Dobby.jobFilter.filterJob != "") {
                if(!Dobby.getJobName(currentJob.id).toLowerCase().includes(Dobby.jobFilter.filterJob)) {
                    continue;
                }
            }
            if(!JobList.getJobById(currentJob.id).canDo()) {
                continue;
            }
            if(Dobby.checkIfJobAdded(currentJob.id)) {
                continue;
            }
            var isSilver = Dobby.checkIfSilver(currentJob.x,currentJob.y,currentJob.id);
            currentJob.silver = isSilver;
            if(isSilver && Dobby.jobFilter.filterNoSilver) {
                continue;
            }
            if(!isSilver && Dobby.jobFilter.filterOnlySilver) {
                continue;
            }
            if(Dobby.jobFilter.filterCenterJobs && currentJob.id < 131 ) {
                continue;
            }
            Dobby.compareUniqueJobs(currentJob,jobs);
        }
        Dobby.parseJobData(jobs);

        var experienceSort = function(a,b) {
              if(a == null && b == null) {
                  return 0;
              }
              if(a == null && b != null) {
                  return 1;
              }
              if(a != null && b == null) {
                  return -1;
              }
              var a1 = a.experience;
              var b1 = b.experience;
              return (a1 > b1) ? -1 :(a1 < b1) ? 1 :0;
          };
          var reverseExperienceSort = function(a,b) {
              if(a == null && b == null) {
                  return 0;
                 }
              if(a == null && b != null) {
                  return -1;
              }
              if(a != null && b == null) {
                  return 1;
              }
              var a1 = a.experience;
              var b1 = b.experience;
              return (a1 > b1) ? 1 :(a1 < b1) ? -1 :0;
          };
        var distanceSort = function(a,b) {
              if(a == null && b == null) {
                  return 0;
              }
              if(a == null && b != null) {
                  return 1;
              }
              if(a != null && b == null) {
                  return -1;
              }
              var a1 = a.distance;
              var b1 = b.distance;
              return (a1 > b1) ? -1 :(a1 < b1) ? 1 :0;
          };
           var reverseDistanceSort = function(a,b) {
             if(a == null && b == null) {
                  return 0;
                 }
              if(a == null && b != null) {
                  return -1;
              }
              if(a != null && b == null) {
                  return 1;
              }
              var a1 = a.distance;
              var b1 = b.distance;
              return (a1 > b1) ? 1 :(a1 < b1) ? -1 :0;
          };
        if(Dobby.sortJobTableXp == 1) {
            jobs.sort(experienceSort);
        }
        if(Dobby.sortJobTableXp == -1) {
            jobs.sort(reverseExperienceSort);
        }
        if(Dobby.sortJobTableDistance == 1) {
            jobs.sort(distanceSort);
        }
        if(Dobby.sortJobTableDistance == -1) {
            jobs.sort(reverseDistanceSort);
        }
        return jobs;
    };
    Dobby.findJob = function(x,y,id) {
        for(var i = 0 ; i < Dobby.allJobs.length;i++) {
            if(Dobby.allJobs[i].id == id && Dobby.allJobs[i].x == x && Dobby.allJobs[i].y == y) {
                return Dobby.allJobs[i];
            }
        }
    };
    Dobby.addJob = function(x,y,id) {
        if(!Dobby.checkIfJobAdded(id)) {
            var job = Dobby.findJob(x,y,id);
            Dobby.addedJobs.push(job);
            if(Dobby.jobSet != -1) {
                job.setSet(Dobby.jobSet);
            }
        }
    };
    Dobby.removeJob = function(x,y,id) {
        for(var i = 0; i < Dobby.addedJobs.length;i++) {
            if(Dobby.addedJobs[i].id == id && Dobby.addedJobs[i].x == x && Dobby.addedJobs[i].y == y) {
                Dobby.addedJobs.splice(i,1);
                Dobby.consolidePosition(i);
                break;
            }
        }
    };
    Dobby.checkIfJobAdded = function(id) {
        for(var i = 0; i < Dobby.addedJobs.length;i++) {
            if(Dobby.addedJobs[i].id == id ) {
                return true;
            }
        }
        return false;
    };
    Dobby.findAddedJob = function(x,y,id) {
        for(var i = 0 ; i < Dobby.addedJobs.length;i++) {
            if(Dobby.addedJobs[i].x == x && Dobby.addedJobs[i].y == y && Dobby.addedJobs[i].id == id ) {
                return Dobby.addedJobs[i];
            }
        }
        return null;
    };
    Dobby.getJobSet = function(x,y,id) {
        var job = Dobby.findAddedJob(x,y,id);
        if(job != null)
            return job.set;
    };
    Dobby.setJobSet = function(x,y,id,set) {
        var job = Dobby.findAddedJob(x,y,id);
        if(job != null)
            return job.setSet(set);
    };
    Dobby.setSetForAllJobs = function(forceAll) {
        for(var i = 0 ;i < Dobby.addedJobs.length;i++) {
            if(forceAll || Dobby.addedJobs[i].set == -1) {
                Dobby.addedJobs[i].setSet(Dobby.jobSet);
            }
        }
    };
    Dobby.consolidePosition = function(removeIndex) {
        if(removeIndex <= Dobby.currentJob.job && Dobby.currentJob.job > 0) {
            Dobby.currentJob.job--;
        }
        if(Dobby.addedJobs.length == 1) {
            Dobby.currentJob.direction = true;
        }
    }
    Dobby.parseStopMotivation = function() {
        for(var i = 0 ; i < Dobby.addedJobs.length;i++) {
            var stopMotivation = $(".dobby2window #x-" + Dobby.addedJobs[i].x + "y-" + Dobby.addedJobs[i].y + "id-" + Dobby.addedJobs[i].id).prop("value");
            if(Dobby.isNumber(stopMotivation)) {
                Dobby.addedJobs[i].setStopMotivation(parseInt(stopMotivation));
            }else {
                return false;
            }
        }
        return true;
    };
    Dobby.getItemImage = function(id) {
      return ItemManager.get(id).wear_image;
    };
    Dobby.findAllConsumables = function() {
        var keys = Dobby.getSearchKeys();
        if(keys == null) return;
        var previousSelected = {};
        for(var s = 0; s < Dobby.allConsumables.length; s++) {
            previousSelected[Dobby.allConsumables[s].id] = Dobby.allConsumables[s].selected;
        }
        Dobby.allConsumables = [];
        var found = {};
        var markFound = function(item) {
            if(item && item.obj && item.obj.item_id) {
                found[item.obj.item_id] = item;
            }
        };
        var searchTerms = [keys.energy, keys.energyAlt, keys.motivation, keys.motivationAlt, keys.health, keys.healthAlt];
        for(var t = 0; t < searchTerms.length; t++) {
            if(!searchTerms[t] || typeof Bag === 'undefined') continue;
            var results = Bag.search(searchTerms[t]);
            for(var i = 0; i < results.length; i++) {
                markFound(results[i]);
            }
        }
        var bagItems = Dobby.getBagItems();
        for(var b = 0; b < bagItems.length; b++) {
            var bagItem = bagItems[b];
            if(!bagItem.obj || !bagItem.obj.usebonus || !bagItem.obj.usebonus.length) continue;
            var parsed = Dobby.parseConsumableBonuses(bagItem.obj.usebonus);
            if(parsed[0] > 0 || parsed[1] > 0 || parsed[2] > 0) {
                markFound(bagItem);
            }
        }
        for(var id in found) {
            Dobby.addConsumable(found[id]);
        }
        for(var j = 0; j < Dobby.allConsumables.length; j++) {
            if(previousSelected[Dobby.allConsumables[j].id] !== undefined) {
                Dobby.allConsumables[j].setSelected(previousSelected[Dobby.allConsumables[j].id]);
            }
        }
    };
    Dobby.CheckIfConsumableAdded = function(item) {
        if(item == undefined)
            return true;
        for(var i = 0 ; i < Dobby.allConsumables.length;i++) {
            if(Dobby.allConsumables[i].id == item.obj.item_id) {
                return true;
            }
        }
        return false;
    };
    Dobby.addConsumable = function(item) {
        if(item == undefined || !item.obj) return;
        for(var i = 0; i < Dobby.allConsumables.length; i++) {
            if(Dobby.allConsumables[i].id == item.obj.item_id) {
                Dobby.allConsumables[i].setCount(item.count);
                return;
            }
        }
        var consumable = new ConsumablePrototype(item.obj.item_id,item.obj.image,item.obj.name);
        var bonuses = Dobby.parseConsumableBonuses(item.obj.usebonus);
        if(bonuses[0] == 0 && bonuses[1] == 0 && bonuses[2] == 0) return;
        consumable.setEnergy(bonuses[0]);
        consumable.setMotivation(bonuses[1]);
        consumable.setHealth(bonuses[2]);
        consumable.setCount(item.count);
        Dobby.allConsumables.push(consumable);
    };
    Dobby.removeConsumable = function(item) {
        for(var i = 0; i < Dobby.allConsumables.length; i++) {
            if(Dobby.allConsumables[i].id == item.id) {
                if(Dobby.allConsumables[i].count > 1) {
                    Dobby.allConsumables[i].count--;
                } else {
                    Dobby.allConsumables.splice(i, 1);
                }
                break;
            }
        }
    };
    Dobby.parseConsumableBonuses = function(bonuses) {
        var keys = Dobby.getSearchKeys();
        var extractNumber = function(text) {
            var match = text.match(/(\d+)/);
            return match ? parseInt(match[1], 10) : 0;
        };
        var classifyBonus = function(text) {
            var norm = Dobby.normalizeBonusText(text);
            if(norm.indexOf('motiv') !== -1) return 1;
            if(norm.indexOf('energ') !== -1 || norm.indexOf('energy') !== -1) return 0;
            if(norm.indexOf('viat') !== -1 || norm.indexOf('health') !== -1 || norm.indexOf('zdrav') !== -1 || norm.indexOf('puncte') !== -1 || norm.indexOf('zycia') !== -1) return 2;
            if(keys) {
                if(text.indexOf(keys.energyText) !== -1 || text.indexOf(keys.energy) !== -1) return 0;
                if(text.indexOf(keys.motivationText) !== -1 || text.indexOf(keys.motivation) !== -1) return 1;
                if(text.indexOf(keys.healthText) !== -1 || text.indexOf(keys.health) !== -1) return 2;
            }
            return -1;
        };
        var result = [0, 0, 0];
        for(var i = 0; i < bonuses.length; i++) {
            var type = classifyBonus(bonuses[i]);
            if(type === -1) continue;
            var value = extractNumber(bonuses[i]);
            if(value > result[type]) result[type] = value;
        }
        return result;
    };
    Dobby.filterConsumables = function(energy,motivation,health) {
        var result = [];
        for(var i = 0 ; i < Dobby.allConsumables.length;i++) {
            if(energy && Dobby.allConsumables[i].energy == 0) {
                continue;
            }
            if(motivation && Dobby.allConsumables[i].motivation == 0) {
                continue;
            }
            if(health && Dobby.allConsumables[i].health == 0) {
                continue;
            }
            result.push(Dobby.allConsumables[i]);
        }
        return result;
    };
    Dobby.changeConsumableSelection = function(id,selected) {
        for(var i = 0 ; i < Dobby.allConsumables.length;i++) {
            if(Dobby.allConsumables[i].id == id) {
                Dobby.allConsumables[i].setSelected(selected);
                break;
            }
        }
    };
    Dobby.changeSelectionAllConsumables = function(selected) {
        for(var i = 0 ; i < Dobby.allConsumables.length;i++) {
            Dobby.allConsumables[i].setSelected(selected);
        }
    };
    Dobby.canUseConsume = function(item) {
        if(BuffList.cooldowns[item.id] != undefined && BuffList.cooldowns[item.id].time > new ServerDate().getTime()) {
            return false;
        }
        return true;
    };
    Dobby.useConsumable = async function(itemToUse) {
        var item = Bag.getItemByItemId(itemToUse.id);
        item.showCooldown();
        Dobby.currentState = 2;
        Dobby.updateRunStatus("Așteaptă cooldown consumabil...");
        await Dobby.waitUntil(function() {
            return Dobby.canUseConsume(itemToUse);
        }, function() { return Dobby.isRunning; });
        if(!Dobby.isRunning) return;
        if(Dobby.healthSet != -1) {
            await Dobby.equipSet(Dobby.healthSet);
            if(Dobby.settings.setWearDelay > 0) {
                await Dobby.sleepMs(Dobby.settings.setWearDelay * 1000);
            }
        }
        Dobby.removeConsumable(itemToUse);
        Dobby.consumableUsed.push(itemToUse);
        ItemUse.doIt(itemToUse.id);
        await Dobby.waitUntil(function() {
            return !Dobby.canUseConsume(itemToUse);
        }, function() { return Dobby.isRunning; });
        $(".tw2gui_dialog_framefix").remove();
        Dobby.clearMotivationCache();
        Dobby.run();
    };
    Dobby.findProperConsumable = function(motivationMissing,energyMissing,healthMissing,averageMotivationMissing,consumables) {
        var betterEnergy = function(item1,item2) {
            var distanceItem1 = Math.abs(energyMissing - item1.energy);
            var distanceItem2 = Math.abs(energyMissing - item2.energy);
            return (distanceItem1 < distanceItem2 ) ? -1 :(distanceItem1 > distanceItem2) ? 1 : 0;
        };
        var betterMotivation = function(item1,item2) {
            var distanceItem1 = Math.abs(averageMotivationMissing - item1.motivation);
            var distanceItem2 = Math.abs(averageMotivationMissing - item2.motivation);
            return (distanceItem2 < distanceItem1) ? item2 : item1;
        };
        var findMotivationConsume = function(consumes) {
            var consumeToChoose = null;
            for(var i = 0 ; i < consumes.length;i++) {
                if(consumeToChoose == null && consumes[i].motivation != 0) {
                    consumeToChoose = consumes[i];
                    continue;
                }
                if(consumeToChoose != null && consumes[i].motivation != 0) {
                    consumeToChoose = betterMotivation(consumeToChoose,consumables[i]);
                }
            }
            return consumeToChoose;
        };
        var findHealthConsume = function(consumes) {
            for(var i = 0 ; i < consumes.length;i++) {
                if(consumes[i].health != 0) {
                    return consumes[i];
                }
            }
            return null;
        };
        if(consumables.length  == 0) return null;
        var consums = consumables;
        consums = consums.sort(betterEnergy);
        if(energyMissing == 100) {
            return consums[0];
        }
        if(motivationMissing == Dobby.addedJobs.length) {
            return findMotivationConsume(consums);
        }
        if(Dobby.isHealthBelowLimit()) {
            return findHealthConsume(consums);
        }
    };
    Dobby.tryUseConsumable = function(result) {
        var healthMissing = 100 - (Character.health/Character.maxHealth) * 100;
        var energyMissing = 100 - (Character.energy/Character.maxEnergy) * 100;
        var motivationMissing = Dobby.jobsBelowMotivation(result);
        var consumables = Dobby.allConsumables;
        var averageMotivationMissing = Dobby.averageMissingMotivation(result);
        var selectedConsumes = [];
        for(var i = 0 ; i < consumables.length;i++) {
            if(consumables[i].selected)
                selectedConsumes.push(consumables[i]);
        }
        var itemToUse = Dobby.findProperConsumable(motivationMissing,energyMissing,healthMissing,averageMotivationMissing,selectedConsumes);
        if(itemToUse == null) return false;
        Dobby.useConsumable(itemToUse);
        return true;
    };
    Dobby.calculateDistances = function() {
        for(var i = 0; i < Dobby.addedJobs.length;i++) {
            Dobby.addedJobs[i].calculateDistance();
        }
    };
    Dobby.createDistanceMatrix = function() {
        var distances = new Array(Dobby.addedJobs.length);
        for(var i = 0 ; i < distances.length;i++) {
            distances[i] = new Array(Dobby.addedJobs.length);
        }
        for(var i = 0 ; i < distances.length;i++) {
            for(var j = i; j < distances[i].length;j++) {
                if(i == j) {
                    distances[i][j] = distances[j][i] = Number.MAX_SAFE_INTEGER;
                    continue;
                }
                distances[i][j] = distances[j][i] = GameMap.calcWayTime({x:Dobby.addedJobs[i].x,y:Dobby.addedJobs[i].y},{x:Dobby.addedJobs[j].x,y:Dobby.addedJobs[j].y});
            }
        }
        return distances;
    };
    Dobby.createRoute = function() {
        Dobby.currentJob = {job:0,direction:true}
        Dobby.calculateDistances();
        var closestJobIndex = 0;
        var closestDistance = Dobby.addedJobs[0].distance;
        var route = [];
        var distances = Dobby.createDistanceMatrix();
        var getClosestJob = function(index,route,distances) {
            var closestDistance = Number.MAX_SAFE_INTEGER;
            var closestIndex = -1;
            for(var i = 0 ; i < distances.length;i++) {
                if(index == i || route.includes(i)) {
                    continue;
                }
                if(distances[i][index] < closestDistance) {
                    closestDistance = distances[i][index];
                    closestIndex = i;
                }
            }
            return closestIndex;
        };
        for(var i = 1; i < Dobby.addedJobs.length;i++) {
            if(Dobby.addedJobs[i].distance < closestDistance) {
                closestDistance = Dobby.addedJobs[i].distance;
                closestJobIndex = i;
            }
        }
        route.push(closestJobIndex);
        while(route.length < Dobby.addedJobs.length) {
            var closestJob = getClosestJob(route[route.length-1],route,distances);
            route.push(closestJob);
        }
        var addedJobsOrder = [];
        for(var i = 0 ; i < route.length;i++) {
            addedJobsOrder.push(Dobby.addedJobs[route[i]]);
        }
        Dobby.addedJobs = addedJobsOrder;
        if(Dobby.addedJobs.length > 0) {
            Dobby.updateRunStatus("Rută optimizată — începe de la: " + Dobby.getJobName(Dobby.addedJobs[0].id));
        }
    };
    Dobby.equipSet = async function(set) {
        if(set == -1) return true;
        EquipManager.switchEquip(Dobby.sets[set].equip_manager_id);
        await Dobby.waitUntil(function() {
            return Dobby.isGearEquipedSync(Dobby.getSetItemArray(Dobby.sets[set]));
        });
        return true;
    };
    Dobby.getSetItemArray = function(set) {
        var items = [];
        if(set.head != null)
            items.push(set.head);
        if(set.neck != null)
            items.push(set.neck);
        if(set.body != null)
            items.push(set.body);
        if(set.right_arm != null)
            items.push(set.right_arm);
        if(set.left_arm != null)
            items.push(set.left_arm);
        if(set.belt != null)
            items.push(set.belt);
        if(set.foot != null)
            items.push(set.foot);
        if(set.animal != null)
            items.push(set.animal);
        if(set.yield != null)
            items.push(set.yield);
        if(set.pants != null)
            items.push(set.pants);
        return items;
    };
    Dobby.isWearing = function(itemId) {
        if(Wear.wear[ItemManager.get(itemId).type] == undefined) return false;
        return Wear.wear[ItemManager.get(itemId).type].obj.item_id == itemId;
    };
    Dobby.isGearEquipedSync = function(items) {
        for(var i = 0 ; i < items.length;i++) {
            if(!Dobby.isWearing(items[i])) return false;
        }
        return true;
    };
    Dobby.isGearEquiped = async function(items) {
        return Dobby.isGearEquipedSync(items);
    }
    Dobby.getBestGear = function(jobid) {
        var modelId = function(jobid) {
            for(var i = 0 ; i < JobsModel.Jobs.length;i++) {
                if(JobsModel.Jobs[i].id == jobid)
                    return i;
            }
            return -1;
        }
         var result = west.item.Calculator.getBestSet(JobsModel.Jobs[modelId(jobid)].get('skills'), jobid);
         var bestItems = result && result.getItems();
         return bestItems;
    };
    Dobby.equipBestGear = async function(jobid) {
        var bestGear = Dobby.getBestGear(jobid);
        if(bestGear == undefined) {
            return Promise.resolve(true);;
        }
        for(var i = 0 ; i < bestGear.length;i++) {
            if(!Dobby.isWearing(bestGear[i]))
            Wear.carry(Bag.getItemByItemId(bestGear[i]));
        }
        await Dobby.waitUntil(function() {
            return Dobby.isGearEquipedSync(bestGear);
        });
        return true;
    };
    Dobby.checkMotivation = function(index,result,callback) {
        if(Dobby.addedJobs.length === 0) {
            callback([]);
            return;
        }
        var motivations = new Array(Dobby.addedJobs.length);
        var pending = Dobby.addedJobs.length;
        for(var i = 0; i < Dobby.addedJobs.length; i++) {
            (function(idx) {
                Dobby.loadJobMotivation(idx,function(motivation) {
                    motivations[idx] = motivation;
                    pending--;
                    if(pending === 0) {
                        callback(motivations);
                    }
                });
            })(i);
        }
    };
    Dobby.isMotivationAbove = function(result) {
        for(var i = 0 ; i < result.length;i++) {
            if(result[i] > Dobby.addedJobs[i].stopMotivation) {
                return true;
            }
        }
        return false;
    };
    Dobby.getBackToJobAfterMotivationStop = function() {

    };
    Dobby.jobsBelowMotivation = function(result) {
        var count = 0;
        for(var i = 0 ; i < result.length;i++) {
            if(result[i] <= Dobby.addedJobs[i].stopMotivation) {
                count++;
            }
        }
        return count;
    };
    Dobby.averageMissingMotivation = function(result) {
        var motivation = 0;
        for(var i = 0 ; i < result.length;i++) {
            motivation += (100-result[i]);
        }
        return motivation/result.length;
    };
    Dobby.isHealthBelowLimit = function() {
        if(Dobby.settings.healthStop >= ((Character.health/Character.maxHealth) * 100)) {
            return true;
        }
        return false;
    };
    Dobby.isStopMotivationZero = function() {
        for(var i = 0 ; i < Dobby.addedJobs.length;i++) {
            if(Dobby.addedJobs[i].stopMotivation == 0) {
                return true;
            }
        }
        return false;
    };
    Dobby.canAddMissing = function(result) {
        if(!Dobby.settings.addMotivation && Dobby.jobsBelowMotivation(result) && !Dobby.isStopMotivationZero()) {
            Dobby.updateRunStatus("Nu merge — motivație insuficientă (fără consumabile)");
            return false;
        }
        if(!Dobby.settings.addEnergy && Character.energy == 0) {
            Dobby.updateRunStatus("Nu merge — energie 0");
            return false;
        }
        if(!Dobby.settings.addHealth && Dobby.isHealthBelowLimit()) {
            Dobby.updateRunStatus("Nu merge — viață prea mică");
            return false;
        }
        return true;
    };
    Dobby.finishRun = function() {
        Dobby.currentState = 0;
        Dobby.isRunning = false;
        Dobby.stopWatchdog();
        Dobby.stopKeepAlive();
        Dobby.updateRunStatus("Oprit");
        $('#dobby2-status-overlay').hide();
    };
    Dobby.updateStatistics = function(oldXp) {
        var xpDifference = Character.experience - oldXp;
        Dobby.statistics.xpInSession += xpDifference;
        Dobby.statistics.totalXp += xpDifference;
    }
    Dobby.run = function() {
        if(!Dobby.isRunning) return;
        Dobby.updateRunStatus("Verifică motivația joburilor...");
        Dobby.checkMotivation(0,[],function(result) {
            if(!Dobby.isRunning) return;
            if((Dobby.isMotivationAbove(result) || Dobby.isStopMotivationZero()) && Character.energy > 0 && !Dobby.isHealthBelowLimit()){
                Dobby.currentState = 1;
                var job = Dobby.addedJobs[Dobby.currentJob.job];
                Dobby.updateRunStatus("Merge — pregătește: " + Dobby.getJobName(job.id));
                Dobby.prepareJobRun(Dobby.currentJob.job);
            }else {
                if(!Dobby.canAddMissing(result)) {
                    Dobby.finishRun();
                }else {
                    Dobby.updateRunStatus("Folosește consumabil (energie/motivație/viață)...");
                    var answer = Dobby.tryUseConsumable(result);
                    if(!answer) {
                        Dobby.updateRunStatus("Nu merge — lipsesc consumabilele potrivite");
                        Dobby.finishRun();
                    }
                }
            }
        });
    };
    Dobby.prepareJobRun = function(index) {
        setTimeout(function() {
            Dobby.loadJobMotivation(index,async function(motivation) {
                if(Character.energy == 0 || Dobby.isHealthBelowLimit()) {
                    Dobby.run();
                }
                else if(motivation <= Dobby.addedJobs[index].stopMotivation && Dobby.addedJobs[index].stopMotivation > 0) {
                    Dobby.checkMotivation(0,[],function(result) {
                        if(Dobby.isMotivationAbove(result)) {
                            Dobby.changeJob();
                        }else {
                            Dobby.run();
                        }
                    });
                }else
                if(GameMap.calcWayTime(Character.position,{x:Dobby.addedJobs[index].x,y:Dobby.addedJobs[index].y}) == 0) {
                    var maxJobs;
                    (Premium.hasBonus('automation')) ? maxJobs = 9 : maxJobs = 4;
                    if(Dobby.addedJobs[index].stopMotivation != 0){
                    var numberOfJobs = Math.min(Math.min(motivation - Dobby.addedJobs[index].stopMotivation,Character.energy),maxJobs);
                    }else {
                        var numberOfJobs = Math.min(Character.energy,maxJobs);
                    }
                    Dobby.updateRunStatus("Merge — lucrează: " + Dobby.getJobName(Dobby.addedJobs[index].id) + " (x" + numberOfJobs + ")");
                    Dobby.runJob(index,numberOfJobs);
                }else {
                    Dobby.updateRunStatus("Merge — merge spre: " + Dobby.getJobName(Dobby.addedJobs[index].id) + " (cel mai apropiat)");
                    await Dobby.equipSet(Dobby.travelSet);
                    Dobby.walkToJob(index);
                }
            });
        },Dobby.generateRandomNumber(Dobby.settings.jobDelayMin,Dobby.settings.jobDelayMax)*1000);
    };
    Dobby.walkToJob = async function(index) {
        JobWindow.startJob(Dobby.addedJobs[index].id,Dobby.addedJobs[index].x,Dobby.addedJobs[index].y,15);
        var arrived = await Dobby.waitUntil(function() {
            return GameMap.calcWayTime(Character.position,{x:Dobby.addedJobs[index].x,y:Dobby.addedJobs[index].y}) == 0;
        }, function() { return Dobby.isRunning; }, 900000);
        Dobby.cancelJobs();
        if(!Dobby.isRunning) return;
        if(!arrived) {
            Dobby.updateRunStatus("Drum blocat — reîncearcă...");
            Dobby.prepareJobRun(index);
            return;
        }
        Dobby.prepareJobRun(index);
    };
    Dobby.sleep = async function() {
        if(Dobby.settings.enableRegeneration && Dobby.selectedSleepPlace != -2) {
            //if sleep place is town
            if(Dobby.selectedSleepPlace == -1) {
                TaskQueue.add(new TaskWalk(Dobby.homeTown.town_id,'town'));
            }else {
                TaskQueue.add(new TaskWalk(Dobby.forts[Dobby.selectedSleepPlace].fort_id,'fort'));
            }

            while(true) {
            if(GameMap.calcWayTime(Character.position,{x:Dobby.addedJobs[index].x,y:Dobby.addedJobs[index].y}) == 0) {
                break;
            }
            if(!Dobby.isRunning) {
                break;
            }
            await Dobby.waitUntil(function() {
                return GameMap.calcWayTime(Character.position,{x:Dobby.addedJobs[index].x,y:Dobby.addedJobs[index].y}) == 0;
            }, function() { return Dobby.isRunning; });
        }
        }

    }
    Dobby.changeJob = function() {
        (Dobby.currentJob.direction) ? Dobby.currentJob.job++ : Dobby.currentJob.job--;
        if(Dobby.currentJob.job == Dobby.addedJobs.length) {
            Dobby.currentJob.job--;
            Dobby.currentJob.direction = false;
        } else if(Dobby.currentJob.job < 0) {
            Dobby.currentJob.job++;
            Dobby.currentJob.direction = true;
        }
        Dobby.updateRunStatus("Schimbă locul de muncă (motivație scăzută)...");
        Dobby.setCookies();
        Dobby.run();
    };
    Dobby.runJob = async function(jobIndex,jobCount) {
        Dobby.statistics.jobsInSession += jobCount;
        Dobby.statistics.totalJobs += jobCount;
        var oldXp = Character.experience;
        await Dobby.equipBestGear(Dobby.addedJobs[jobIndex].id);
        for(var i = 0; i < jobCount;i++) {
            JobWindow.startJob(Dobby.addedJobs[jobIndex].id,Dobby.addedJobs[jobIndex].x,Dobby.addedJobs[jobIndex].y,15);
        }
        if(Dobby.settings.setWearDelay > 0) {
            await Dobby.sleepMs(Dobby.settings.setWearDelay * 1000);
        }
        await Dobby.equipSet(Dobby.addedJobs[jobIndex].set);
        var waitTimeout = Dobby.getJobWaitTimeout(jobCount);
        var completed = await Dobby.waitUntil(function() {
            return TaskQueue.queue.length == 0;
        }, function() { return Dobby.isRunning && !Dobby.isHealthBelowLimit(); }, waitTimeout);
        if(completed) {
            Dobby.updateStatistics(oldXp);
            Dobby.invalidateJobMotivation(jobIndex);
            Dobby.setCookies();
            Dobby.prepareJobRun(jobIndex);
            return;
        }
        Dobby.statistics.jobsInSession -= TaskQueue.queue.length;
        Dobby.statistics.totalJobs -= TaskQueue.queue.length;
        Dobby.updateStatistics(oldXp);
        Dobby.setCookies();
        Dobby.cancelJobs();
        if(Dobby.isRunning) {
            Dobby.updateRunStatus("Joburi blocate — continuă...");
            Dobby.run();
        }
    };
    Dobby.cancelJobs = function() {
        if(TaskQueue.queue.length > 0)
            TaskQueue.cancelAll();
    };
    Dobby.setCookies = function() {
        var expiracyDateTemporary = new Date();
        var hour = expiracyDateTemporary.getHours();
        expiracyDateTemporary.setHours(2,0,0);
        if(hour > 2)
            expiracyDateTemporary.setDate(expiracyDateTemporary.getDate() + 1);
        var temporaryObject ={
            addedJobs:Dobby.addedJobs,
            travelSet:Dobby.travelSet,
            jobSet:Dobby.jobSet,
            healthSet:Dobby.healthSet,
            currentJob:Dobby.currentJob
        };
        var expiracyDatePernament = new Date();
        expiracyDatePernament.setDate(expiracyDatePernament.getDate() + 360000);
        var pernamentObject = {
            settings:Dobby.settings,
            totalJobs:Dobby.statistics.totalJobs,
            totalXp:Dobby.statistics.totalXp
        };
        var jsonTemporary = JSON.stringify(temporaryObject);
        var jsonPernament = JSON.stringify(pernamentObject);
        document.cookie = "dobby2temporary=" + jsonTemporary + ";expires=" + expiracyDateTemporary.toGMTString() + ";";
        document.cookie = "dobby2pernament=" + jsonPernament + ";expires=" + expiracyDatePernament.toGMTString() + ";";
    };
    Dobby.getCookies = function() {
        try {
            var cookie = document.cookie.split("=");
            for(var i = 0; i < cookie.length;i++) {
                if(cookie[i].includes("dobby2temporary")) {
                    var obj = cookie[i+1].split(";");
                    var tempObject = JSON.parse(obj[0]);
                    var tmpAddedJobs = tempObject.addedJobs;
                    for(var j = 0 ; j < tmpAddedJobs.length;j++) {
                        var jobP = new JobPrototype(tmpAddedJobs[j].x,tmpAddedJobs[j].y,tmpAddedJobs[j].id);
                        jobP.setSilver(tmpAddedJobs[j].silver);
                        jobP.distance = tmpAddedJobs[j].distance;
                        jobP.setExperience(tmpAddedJobs[j].experience);
                        jobP.setMoney(tmpAddedJobs[j].money);
                        jobP.setMotivation(tmpAddedJobs[j].motivation);
                        jobP.setStopMotivation(tmpAddedJobs[j].stopMotivation);
                        jobP.setSet(tmpAddedJobs[j].set);
                        Dobby.addedJobs.push(jobP);
                    }
                    Dobby.travelSet = tempObject.travelSet;
                    Dobby.jobSet = tempObject.jobSet;
                    Dobby.healthSet = tempObject.healthSet;
                    Dobby.currentJob = tempObject.currentJob;
                    Dobby.setSetForAllJobs();
                }
                if(cookie[i].includes("dobby2pernament")) {
                    var obj = cookie[i+1].split(";");
                    var pernamentObject = JSON.parse(obj[0]);
                    Dobby.settings = pernamentObject.settings;
                    Dobby.statistics.totalJobs = pernamentObject.totalJobs;
                    Dobby.statistics.totalXp = pernamentObject.totalXp;
                }
            }
        } catch(e) {
            console.log("Dobby2: cookie load failed", e);
        }
    };
    Dobby.createWindow = function() {
        var window = wman.open("dobby").setResizeable(false).setMinSize(650, 480).setSize(650, 480).setMiniTitle("Dobby2");
        var content = $('<div class=\'dobby2window\'/>');
        var tabs = {
            "jobs":"Jobs",
            "choosenJobs":"Choosen jobs",
            "sets":"Sets",
            "consumables":"Consumables",
            "stats":"Statistics",
            "settings":"Settings"
        };
        var tabLogic = function(win,id) {
            var content = $('<div class=\'dobby2window\'/>');
            switch(id) {
                case "jobs":
                    Dobby.loadJobData(function(){
                    Dobby.removeActiveTab(this);
                    Dobby.removeWindowContent();
                    Dobby.addActiveTab("jobs",this);
                    content.append(Dobby.createJobsTab());
                    Dobby.window.appendToContentPane(content);
                    Dobby.addJobTableCss();
                    $(".dobby2window .tw2gui_scrollpane_clipper_contentpane").css({"top":Dobby.jobTablePosition.content});
                    $(".dobby2window .tw2gui_scrollbar_pulley").css({"top":Dobby.jobTablePosition.scrollbar});
                    Dobby.addEventsHeader();
                    });
                    break;
                case "choosenJobs":
                    Dobby.removeActiveTab(this);
                    Dobby.removeWindowContent();
                    Dobby.addActiveTab("choosenJobs",this);
                    content.append(Dobby.createAddedJobsTab());
                    Dobby.window.appendToContentPane(content);
                    $(".dobby2window .tw2gui_scrollpane_clipper_contentpane").css({"top":Dobby.addedJobTablePosition.content});
                    $(".dobby2window .tw2gui_scrollbar_pulley").css({"top":Dobby.addedJobTablePosition.scrollbar});
                    Dobby.addAddedJobsTableCss();
                    break;
                case "consumables":
                    Dobby.removeActiveTab(this);
                    Dobby.removeWindowContent();
                    Dobby.addActiveTab("consumables",this);
                    var showConsumables = function() {
                        Dobby.findAllConsumables();
                        var content = $('<div class=\'dobby2window\'/>');
                        content.append(Dobby.createConsumablesTable());
                        Dobby.window.appendToContentPane(content);
                        $(".dobby2window .tw2gui_scrollpane_clipper_contentpane").css({"top":Dobby.consumableTablePosition.content});
                        $(".dobby2window .tw2gui_scrollbar_pulley").css({"top":Dobby.consumableTablePosition.scrollbar});
                        Dobby.addConsumableTableCss();
                    };
                    if(Dobby.language) {
                        showConsumables();
                    } else {
                        Dobby.loadLanguage(showConsumables);
                    }
                    break;
                case "sets":
                    Dobby.loadSets(function() {
                        Dobby.removeActiveTab(this);
                        Dobby.removeWindowContent();
                        Dobby.addActiveTab("sets",this);
                        content.append(Dobby.createSetGui())
                        Dobby.window.appendToContentPane(content);
                    });
                    break;
                case "stats":
                    Dobby.removeActiveTab(this);
                    Dobby.removeWindowContent();
                    Dobby.addActiveTab("stats",this);
                    content.append(Dobby.createStatisticsGui());
                    Dobby.window.appendToContentPane(content);
                    break;
                case "settings":
                    Dobby.removeActiveTab(this);
                    Dobby.removeWindowContent();
                    Dobby.addActiveTab("settings",this);
                    content.append(Dobby.createSettingsGui());
                    Dobby.window.appendToContentPane(content);
                    break;
            }
        }
        for(var tab in tabs) {
            window.addTab(tabs[tab],tab,tabLogic);
        }
        Dobby.window = window;
        Dobby.selectTab("jobs");
    };
    Dobby.selectTab = function(key) {
        Dobby.window.tabIds[key].f(Dobby.window,key);
    };
    Dobby.removeActiveTab = function(window) {
        $('div.tw2gui_window_tab', window.divMain).removeClass('tw2gui_window_tab_active');
    };
    Dobby.addActiveTab = function(key,window) {
        $('div._tab_id_' + key, window.divMain).addClass('tw2gui_window_tab_active');
    };
    Dobby.removeWindowContent = function() {
        $(".dobby2window").remove();
    };
    Dobby.addJobTableCss = function() {
        $(".dobby2window .jobIcon").css({"width":"80px"});
        $(".dobby2window .jobName").css({"width":"150px"});
        $(".dobby2window .jobXp").css({"width":"40px"});
        $(".dobby2window .jobMoney").css({"width":"40px"});
        $(".dobby2window .jobDistance").css({"width":"100px"});
        $(".dobby2window .row").css({"height":"60px"});
        $('.dobby2window').find('.tw2gui_scrollpane').css('height', '250px');
    };
    Dobby.addAddedJobsTableCss = function() {
        $(".dobby2window .jobIcon").css({"width":"80px"});
        $(".dobby2window .jobName").css({"width":"130px"});
        $(".dobby2window .jobStopMotivation").css({"width":"110px"});
        $(".dobby2window .jobRemove").css({"width":"105px"});
        $(".dobby2window .jobSet").css({"width":"100px"});
        $(".dobby2window .row").css({"height":"60px"});
        $('.dobby2window').find('.tw2gui_scrollpane').css('height', '250px');
    };
    Dobby.addConsumableTableCss = function() {
        $(".dobby2window .consumIcon").css({"width":"80px"});
        $(".dobby2window .consumName").css({"width":"120px"});
        $(".dobby2window .consumCount").css({"width":"70px"});
        $(".dobby2window .consumEnergy").css({"width":"70px"});
        $(".dobby2window .consumMotivation").css({"width":"70px"});
        $(".dobby2window .consumHealth").css({"width":"70px"});
        $(".dobby2window .row").css({"height":"80px"});
        $('.dobby2window').find('.tw2gui_scrollpane').css('height', '250px');
    };
    Dobby.addEventsHeader = function() {
        $(".dobby2window .jobXp").click(function() {
            if(Dobby.sortJobTableXp == 0) {
                Dobby.sortJobTableXp = 1;
            }else {
                (Dobby.sortJobTableXp == 1) ? Dobby.sortJobTableXp = -1 : Dobby.sortJobTableXp = 1;
            }
            Dobby.sortJobTableDistance = 0;
            Dobby.selectTab("jobs");
        });
        $(".dobby2window .jobDistance").click(function() {
            if(Dobby.sortJobTableDistance == 0) {
                Dobby.sortJobTableDistance = 1;
            }else {
                (Dobby.sortJobTableDistance == 1) ? Dobby.sortJobTableDistance = -1 : Dobby.sortJobTableDistance = 1;
            }
            Dobby.sortJobTableXp = 0;
            Dobby.selectTab("jobs");
        });
    };
    Dobby.createJobsTab = function() {
        var htmlSkel = $("<div id = \'jobs_overview'\></div>");
        var html = $("<div class = \'jobs_search'\ style=\'position:relative;'\><div id=\'jobFilter'\style=\'position:absolute;top:10px;left:15px'\></div><div id=\'job_only_silver'\style=\'position:absolute;top:10px;left:200px;'\></div><div id=\'job_no_silver'\style=\'position:absolute;top:10px;left:270px;'\></div><div id=\'job_center'\style=\'position:absolute;top:10px;left:350px;'\></div><div id=\'button_filter_jobs'\style=\'position:absolute;top:5px;left:450px;'\></div></div>");
        var table = new west.gui.Table();
        var xpIcon = '<img src="/images/icons/star.png">';
        var dollarIcon = '<img src="/images/icons/dollar.png">';
        var arrow_desc = '&nbsp;<img src="../images/window/jobs/sortarrow_desc.png"/>';
        var arrow_asc = '&nbsp;<img src="../images/window/jobs/sortarrow_asc.png"/>';
        var uniqueJobs = Dobby.getAllUniqueJobs();
        table.addColumn("jobIcon","jobIcon").addColumn("jobName","jobName").addColumn("jobXp","jobXp").addColumn("jobMoney","jobMoney").addColumn("jobDistance","jobDistance").addColumn("jobAdd","jobAdd");
        table.appendToCell("head","jobIcon","Job icon").appendToCell("head","jobName","Job name").appendToCell("head","jobXp",xpIcon + (Dobby.sortJobTableXp == 1 ? arrow_asc : Dobby.sortJobTableXp == -1 ? arrow_desc : "")).appendToCell("head","jobMoney",dollarIcon).appendToCell("head","jobDistance","Distance " + (Dobby.sortJobTableDistance == 1 ? arrow_asc : Dobby.sortJobTableDistance == -1 ? arrow_desc : "")).appendToCell("head","jobAdd","");
        for(var job = 0 ; job < uniqueJobs.length;job++) {
            table.appendRow().appendToCell(-1,"jobIcon",Dobby.getJobIcon(uniqueJobs[job].silver,uniqueJobs[job].id,uniqueJobs[job].x,uniqueJobs[job].y)).appendToCell(-1,"jobName",Dobby.getJobName(uniqueJobs[job].id)).appendToCell(-1,"jobXp",uniqueJobs[job].experience).appendToCell(-1,"jobMoney",uniqueJobs[job].money).appendToCell(-1,"jobDistance",uniqueJobs[job].distance.formatDuration()).appendToCell(-1,"jobAdd",Dobby.createAddJobButton(uniqueJobs[job].x,uniqueJobs[job].y,uniqueJobs[job].id));
        }
        var textfield = new west.gui.Textfield("jobsearch").setPlaceholder("Select job name");
        if(Dobby.jobFilter.filterJob != "") {
            textfield.setValue(Dobby.jobFilter.filterJob);
        }
        var checkboxOnlySilver = new west.gui.Checkbox();
        checkboxOnlySilver.setLabel("Silvers");
        checkboxOnlySilver.setSelected(Dobby.jobFilter.filterOnlySilver);
        checkboxOnlySilver.setCallback(function() {
            if(this.isSelected()) {
                Dobby.jobFilter.filterOnlySilver = true;
              }else {
                Dobby.jobFilter.filterOnlySilver = false;
              }
        });
        var checkboxNoSilver = new west.gui.Checkbox();
        checkboxNoSilver.setLabel("No silvers");
        checkboxNoSilver.setSelected(Dobby.jobFilter.filterNoSilver);
        checkboxNoSilver.setCallback(function() {
            if(this.isSelected()) {
                Dobby.jobFilter.filterNoSilver = true;
              }else {
                Dobby.jobFilter.filterNoSilver = false;
              }
        });
        var checkboxCenterJobs = new west.gui.Checkbox();
        checkboxCenterJobs.setLabel("Center jobs");
        checkboxCenterJobs.setSelected(Dobby.jobFilter.filterCenterJobs);
        checkboxCenterJobs.setCallback(function() {
            if(this.isSelected()) {
                Dobby.jobFilter.filterCenterJobs = true;
              }else {
                Dobby.jobFilter.filterCenterJobs = false;
              }
        });
        var buttonFilter = new west.gui.Button("Filter",function() {
            Dobby.jobFilter.filterJob = textfield.getValue();
            Dobby.jobTablePosition.content = "0px";
            Dobby.jobTablePosition.scrollbar = "0px";
            Dobby.selectTab("jobs");
        });
        htmlSkel.append(table.getMainDiv());
        $('#jobFilter', html).append(textfield.getMainDiv());
        $("#job_only_silver",html).append(checkboxOnlySilver.getMainDiv());
        $("#job_no_silver",html).append(checkboxNoSilver.getMainDiv());
        $("#job_center",html).append(checkboxCenterJobs.getMainDiv());
        $("#button_filter_jobs",html).append(buttonFilter.getMainDiv());
        htmlSkel.append(html);
        return htmlSkel;
    };
    Dobby.createAddJobButton = function(x,y,id) {
        var buttonAdd = new west.gui.Button("Add new job",function() {
            Dobby.addJob(x,y,id);
            Dobby.jobTablePosition.content = $(".dobby2window .tw2gui_scrollpane_clipper_contentpane").css("top");
            Dobby.jobTablePosition.scrollbar = $(".dobby2window .tw2gui_scrollbar_pulley").css("top");
            Dobby.selectTab("jobs");
        });
        buttonAdd.setWidth(100);
        return buttonAdd.getMainDiv();
    };
    Dobby.createAddedJobsTab = function() {
        var htmlSkel = $("<div id=\'added_jobs_overview'\></div>");
        var footerHtml = $("<div id=\'start_dobby2'\ style=\'position:relative;'\><span class =\'dobby_status'\ style=\'position:absolute;left:20px;top:10px;font-family:Arial,Helvetica,sans-serif;font-size:13px;font-weight:bold;max-width:320px;'\>"+ Dobby.statusText +"</span><div class = \'dobby_run'\ style = \'position:absolute; left:350px; top:20px;'\></div></div>");
        var table = new west.gui.Table();
        table.addColumn("jobIcon","jobIcon").addColumn("jobName","jobName").addColumn("jobStopMotivation","jobStopMotivation").addColumn("jobSet","jobSet").addColumn("jobRemove","jobRemove");
        table.appendToCell("head","jobIcon","Job icon").appendToCell("head","jobName","Job name").appendToCell("head","jobStopMotivation","Stop motivation").appendToCell("head","jobSet","Job set").appendToCell("head","jobRemove","");
        for(var job = 0; job < Dobby.addedJobs.length;job++) {
            table.appendRow().appendToCell(-1,"jobIcon",Dobby.getJobIcon(Dobby.addedJobs[job].silver,Dobby.addedJobs[job].id,Dobby.addedJobs[job].x,Dobby.addedJobs[job].y)).appendToCell(-1,"jobName",Dobby.getJobName(Dobby.addedJobs[job].id)).appendToCell(-1,"jobStopMotivation",Dobby.createMinMotivationTextfield(Dobby.addedJobs[job].x,Dobby.addedJobs[job].y,Dobby.addedJobs[job].id,Dobby.addedJobs[job].stopMotivation)).appendToCell(-1,"jobSet",Dobby.createComboxJobSets(Dobby.addedJobs[job].x,Dobby.addedJobs[job].y,Dobby.addedJobs[job].id)).appendToCell(-1,"jobRemove",Dobby.createRemoveJobButton(Dobby.addedJobs[job].x,Dobby.addedJobs[job].y,Dobby.addedJobs[job].id));
        }
        var buttonStart = new west.gui.Button("Start",function() {
            var parseSuccesfull = Dobby.parseStopMotivation();
            if(parseSuccesfull) {
                Dobby.createRoute();
                Dobby.isRunning = true;
                Dobby.setCookies();
                Dobby.startKeepAlive();
                Dobby.startWatchdog();
                Dobby.hookAjax();
                Dobby.updateRunStatus("Pornit — caută cel mai apropiat loc...");
                Dobby.run();
            }else {
                new UserMessage("Wrong format of set stop motivation", UserMessage.TYPE_ERROR).show();
            }
        });
        var buttonStop = new west.gui.Button("Stop",function() {
            Dobby.isRunning = false;
            Dobby.currentState = 0;
            Dobby.stopWatchdog();
            Dobby.stopKeepAlive();
            Dobby.updateRunStatus("Oprit manual");
            $('#dobby2-status-overlay').hide();
        });
        htmlSkel.append(table.getMainDiv());
        $(".dobby_run",footerHtml).append(buttonStart.getMainDiv());
        $(".dobby_run",footerHtml).append(buttonStop.getMainDiv());
        htmlSkel.append(footerHtml);
        return htmlSkel;
    };
    Dobby.createMinMotivationTextfield = function(x,y,id,placeholder) {
        var componentId = "x-" + x + "y-" +y + "id-" + id;
        var textfield = new west.gui.Textfield();
        textfield.setId(componentId);
        textfield.setWidth(40);
        textfield.setValue(placeholder);
        return textfield.getMainDiv();
    };
    Dobby.createRemoveJobButton = function(x,y,id) {
        var buttonRemove = new west.gui.Button("Remove job",function() {
            Dobby.removeJob(x,y,id);
            Dobby.addedJobTablePosition.content = $(".dobby2window .tw2gui_scrollpane_clipper_contentpane").css("top");
            Dobby.addedJobTablePosition.scrollbar = $(".dobby2window .tw2gui_scrollbar_pulley").css("top");
            Dobby.selectTab("choosenJobs");
        });
        buttonRemove.setWidth(100);
        return buttonRemove.getMainDiv();
    };
    Dobby.createComboxJobSets = function(x,y,id) {
        var combobox = new west.gui.Combobox();
        Dobby.addComboboxItems(combobox);
        combobox = combobox.select(Dobby.getJobSet(x,y,id));
        combobox.setWidth(60);
        combobox.addListener(function(value) {
            Dobby.setJobSet(x,y,id,value);;
            Dobby.selectTab("choosenJobs");
        });
        return combobox.getMainDiv();
    };
    Dobby.addComboboxItems = function(combobox) {
        combobox.addItem(-1,"None");
        for(var i = 0 ; i < Dobby.sets.length;i++) {
            combobox.addItem(i.toString(),Dobby.sets[i].name);
        }
    };
    Dobby.createSetGui = function() {
        if(Dobby.sets.length == 0) {
            return $("<span style=\'font-size:20px'\>No sets available</span>");
        }
        var htmlSkel = $("<div id =\'dobby2_sets_window'\ style=\'display:block;position:relative;width:650px;height:430px;'\><div id=\'dobby2_sets_left' style=\'display:block;position:absolute;width:250px;height:430px;top:0px;left:0px'\></div><div id=\'dobby2_sets_right' style=\'display:block;position:absolute;width:300px;height:410px;top:0px;left:325px'\></div></div>");
        var combobox = new west.gui.Combobox("combobox_sets");
        Dobby.addComboboxItems(combobox);
        combobox = combobox.select(Dobby.selectedSet);
        combobox.addListener(function(value) {
            Dobby.selectedSet = value;
            Dobby.selectTab("sets");
        });
        var buttonSelectTravelSet = new west.gui.Button("Select travel set",function() {
            Dobby.travelSet = Dobby.selectedSet;
            Dobby.selectTab("sets");
        });
        var buttonSelectJobSet = new west.gui.Button("Select job set",function() {
            Dobby.jobSet = Dobby.selectedSet;
            Dobby.setSetForAllJobs(true);
            Dobby.setCookies();
            Dobby.selectTab("choosenJobs");
        });
        var buttonSelectHealthSet = new west.gui.Button("Select health set",function() {
            Dobby.healthSet = Dobby.selectedSet;
            Dobby.selectTab("sets");
        });
        var buttonSelectRegenerationSet = new west.gui.Button("Select regeneration set",function() {
            Dobby.regenerationSet = Dobby.selectedSet;
            Dobby.selectTab("sets");
        });
        var travelSetText = "None";
        if(Dobby.travelSet != -1) {
            travelSetText = Dobby.sets[Dobby.travelSet].name;
        }
        var jobSetText = "None";
        if(Dobby.jobSet != -1) {
            jobSetText = Dobby.sets[Dobby.jobSet].name;
        }
        var healthSetText = "None";
        if(Dobby.healthSet != -1) {
            healthSetText = Dobby.sets[Dobby.healthSet].name;
        }
        var regenerationSetText = "None";
        if(Dobby.regenerationSet != -1) {
            regenerationSetText = Dobby.sets[Dobby.regenerationSet].name;
        }
        var left = $("<div></div>").append(new west.gui.Groupframe().appendToContentPane($("<span>Sets</span><br><br>")).appendToContentPane(combobox.getMainDiv()).appendToContentPane($("<br><br><span>Travel set:"+ travelSetText +"</span><br><br>")).appendToContentPane(buttonSelectTravelSet.getMainDiv()).appendToContentPane($("<br><br><span>Job set:"+ jobSetText +"</span><br><br>")).appendToContentPane(buttonSelectJobSet.getMainDiv()).appendToContentPane($("<br><br><span>Health set:"+ healthSetText +"</span><br><br>")).appendToContentPane(buttonSelectHealthSet.getMainDiv()).appendToContentPane($("<br><br><span>Regeneration set:"+ regenerationSetText +"</span><br><br>")).appendToContentPane(buttonSelectRegenerationSet.getMainDiv()).getMainDiv());
        var right = $("<div style=\'display:block;position:relative;width:300px;height:410px;'\></div>");
        //head div
        right.append("<div class=\'wear_head wear_slot'\ style=\'display:block;position:absolute;left:30px;top:1px;width:93px;height:94px;background:url(https://westzz.innogamescdn.com/images/window/wear/bg_sprite.png) 0 0 no-repeat;background-position: -95px 0;'\></div>");
        //chest div
        right.append("<div class=\'wear_body wear_slot'\ style=\'display:block;position:absolute;left:30px;top:106px;width:95px;height:138px;background:url(https://westzz.innogamescdn.com/images/window/wear/bg_sprite.png) 0 0 no-repeat;background-position:0 0;'\></div>");
        //pants div
        right.append("<div class=\'wear_pants wear_slot'\ style=\'display:block;position:absolute;left:30px;top:258px;width:93px;height:138px;background:url(https://westzz.innogamescdn.com/images/window/wear/bg_sprite.png) 0 0 no-repeat;background-position:0 0;'\></div>");
        //neck div
        right.append("<div class=\'wear_neck wear_slot'\ style=\'display:block;position:absolute;left:-47px;top:1px;width:74px;height:74px;background:url(https://westzz.innogamescdn.com/images/window/wear/bg_sprite.png) 0 0 no-repeat;background-position:-189px 0;'\></div>");
        //right arm div
        right.append("<div class=\'wear_right_arm wear_slot'\ style=\'display:block;position:absolute;left:-64px;top:79px;width:95px;height:138px;background:url(https://westzz.innogamescdn.com/images/window/wear/bg_sprite.png) 0 0 no-repeat;background-position:0 0;'\></div>");
        //animal div
        right.append("<div class=\'wear_animal wear_slot'\ style=\'display:block;position:absolute;left:-64px;top:223px;width:93px;height:94px;background:url(https://westzz.innogamescdn.com/images/window/wear/bg_sprite.png) 0 0 no-repeat;background-position:-95px 0;'\></div>");
        //yield div
        right.append("<div class=\'wear_yield wear_slot'\ style=\'display:block;position:absolute;left:-47px;top:321px;width:74px;height:74px;background:url(https://westzz.innogamescdn.com/images/window/wear/bg_sprite.png) 0 0 no-repeat;background-position:-189px 0;'\></div>");
        //left arm div
        right.append("<div class=\'wear_left_arm wear_slot'\ style=\'display:block;position:absolute;left:127px;top:52px;width:95px;height:138px;background:url(https://westzz.innogamescdn.com/images/window/wear/bg_sprite.png) 0 0 no-repeat;background-position:0 0;'\></div>");
        //belt div
        right.append("<div class=\'wear_belt wear_slot'\ style=\'display:block;position:absolute;left:127px;top:200px;width:93px;height:94px;background:url(https://westzz.innogamescdn.com/images/window/wear/bg_sprite.png) 0 0 no-repeat;background-position:-95px 0;'\></div>");
        //boots div
        right.append("<div class=\'wear_foot wear_slot'\ style=\'display:block;position:absolute;left:127px;top:302px;width:93px;height:94px;background:url(https://westzz.innogamescdn.com/images/window/wear/bg_sprite.png) 0 0 no-repeat;background-position:-95px 0;'\></div>");
        var keys = ["head","body","pants","neck","right_arm","animal","yield","left_arm","belt","foot"];
        if(Dobby.selectedSet != -1)
        Dobby.insertSetImages(right,keys);
        $("#dobby2_sets_left",htmlSkel).append(left);
        $("#dobby2_sets_right",htmlSkel).append(right);
        return htmlSkel;
    };
    Dobby.getImageSkel = function() {
        return $("<img src=\''\>");
    };
    Dobby.insertSetImages = function(html,keys) {
        for(var i = 0 ; i < keys.length;i++) {
            if(Dobby.sets[Dobby.selectedSet][keys[i]] != null) {
            $(".wear_"+keys[i],html).append(Dobby.getImageSkel().attr("src",Dobby.getItemImage(Dobby.sets[Dobby.selectedSet][keys[i]])));
            }
        }
        return html;
    };
    Dobby.createConsumablesTable = function() {
        var htmlSkel = $("<div id=\'consumables_overview'\></div>");
        var html = $("<div class = \'consumables_filter'\ style=\'position:relative;'\><div id=\'energy_consumables'\style=\'position:absolute;top:10px;left:15px;'\></div><div id=\'motivation_consumables'\style=\'position:absolute;top:10px;left:160px;'\></div><div id=\'health_consumables'\style=\'position:absolute;top:10px;left:320px;'\></div><div id=\'button_filter_consumables'\style=\'position:absolute;top:5px;left:460px;'\></div></div>");
        var table = new west.gui.Table();
        var consumableList = Dobby.filterConsumables(Dobby.consumableSelection.energy,Dobby.consumableSelection.motivation,Dobby.consumableSelection.health);
        table.addColumn("consumIcon","consumIcon").addColumn("consumName","consumName").addColumn("consumCount","consumCount").addColumn("consumEnergy","consumEnergy").addColumn("consumMotivation","consumMotivation").addColumn("consumHealth","consumHealth").addColumn("consumSelected","consumSelected");
        table.appendToCell("head","consumIcon","Image").appendToCell("head","consumName","Name").appendToCell("head","consumCount","Count").appendToCell("head","consumEnergy","Energy").appendToCell("head","consumMotivation","Motivation").appendToCell("head","consumHealth","Health").appendToCell("head","consumSelected","Use");
        for(var i = 0 ; i < consumableList.length;i++ ) {
            var checkbox = new west.gui.Checkbox();
            checkbox.setSelected(consumableList[i].selected);
            checkbox.setId(consumableList[i].id);
            checkbox.setCallback(function() {
                Dobby.changeConsumableSelection(parseInt(this.divMain.attr("id")),this.isSelected());
                Dobby.consumableTablePosition.content = $(".dobby2window .tw2gui_scrollpane_clipper_contentpane").css("top");;
                Dobby.consumableTablePosition.scrollbar = $(".dobby2window .tw2gui_scrollbar_pulley").css("top");
                Dobby.selectTab("consumables");
                Dobby.setCookies();
            });
            table.appendRow().appendToCell(-1,"consumIcon",Dobby.getConsumableIcon(consumableList[i].image)).appendToCell(-1,"consumName",consumableList[i].name).appendToCell(-1,"consumCount",consumableList[i].count).appendToCell(-1,"consumEnergy",consumableList[i].energy).appendToCell(-1,"consumMotivation",consumableList[i].motivation).appendToCell(-1,"consumHealth",consumableList[i].health).appendToCell(-1,"consumSelected",checkbox.getMainDiv());
        }
        var buttonSelect = new west.gui.Button("Select all",function() {
            Dobby.changeSelectionAllConsumables(true);
            Dobby.selectTab("consumables");
            Dobby.setCookies();
        });
        var buttonDeselect = new west.gui.Button("Deselect all",function() {
            Dobby.changeSelectionAllConsumables(false);
            Dobby.selectTab("consumables");
            Dobby.setCookies();
        });
        table.appendToFooter("consumEnergy",buttonSelect.getMainDiv());
        table.appendToFooter("consumHealth",buttonDeselect.getMainDiv());
        htmlSkel.append(table.getMainDiv());
        var checkboxEnergyConsumes = new west.gui.Checkbox();
        checkboxEnergyConsumes.setLabel("Energy consumables");
        checkboxEnergyConsumes.setSelected(Dobby.consumableSelection.energy);
        checkboxEnergyConsumes.setCallback(function() {
            Dobby.consumableSelection.energy = this.isSelected();
        });
        var checkboxMotivationConsumes = new west.gui.Checkbox();
        checkboxMotivationConsumes.setLabel("Motivation consumables");
        checkboxMotivationConsumes.setSelected(Dobby.consumableSelection.motivation);
        checkboxMotivationConsumes.setCallback(function() {
            Dobby.consumableSelection.motivation = this.isSelected();
        });
        var checkboxHealthConsumes = new west.gui.Checkbox();
        checkboxHealthConsumes.setLabel("Health consumables");
        checkboxHealthConsumes.setSelected(Dobby.consumableSelection.health);
        checkboxHealthConsumes.setCallback(function() {
            Dobby.consumableSelection.health = this.isSelected();
        });
        var buttonFilter = new west.gui.Button("Select",function() {
            Dobby.selectTab("consumables");
        });
        $("#energy_consumables",html).append(checkboxEnergyConsumes.getMainDiv());
        $("#motivation_consumables",html).append(checkboxMotivationConsumes.getMainDiv());
        $("#health_consumables",html).append(checkboxHealthConsumes.getMainDiv());
        $("#button_filter_consumables",html).append(buttonFilter.getMainDiv());
        htmlSkel.append(html);
        return htmlSkel;
    };

    Dobby.addSleepPlacesItems = function(combobox) {
        combobox.addItem(-2,"None");
        if(Dobby.homeTown != null) {
        combobox.addItem(-1,Dobby.homeTown.name);
        }
        for(var i = 0 ; i < Dobby.forts.length;i++) {
            var type = (Dobby.forts[i].type == 0) ? "Small" : (Dobby.forts[i].type == 1)? "Medium" : "Large";
            combobox.addItem(i.toString(),Dobby.forts[i].name + "  -  " + type );
        }
    }

    Dobby.createSettingsGui = function() {
        var htmlSkel = $("<div id=\'settings_overview'\ style = \'padding:10px;'\></div>");
        var checkboxAddEnergy = new west.gui.Checkbox();
        checkboxAddEnergy.setLabel("Add energy");
        checkboxAddEnergy.setSelected(Dobby.settings.addEnergy);
        checkboxAddEnergy.setCallback(function() {
            Dobby.settings.addEnergy = !Dobby.settings.addEnergy;
        });
        var checkboxAddMotivation = new west.gui.Checkbox();
        checkboxAddMotivation.setLabel("Add motivation");
        checkboxAddMotivation.setSelected(Dobby.settings.addMotivation);
        checkboxAddMotivation.setCallback(function() {
            Dobby.settings.addMotivation = !Dobby.settings.addMotivation;
        });
        var checkboxAddHealth = new west.gui.Checkbox();
        checkboxAddHealth.setLabel("Add health");
        checkboxAddHealth.setSelected(Dobby.settings.addHealth);
        checkboxAddHealth.setCallback(function() {
            Dobby.settings.addHealth = !Dobby.settings.addHealth;
        });
        var htmlHealthStop = $("<div></div>");
        htmlHealthStop.append("<span> Stoppage health percent value </span>");
        var healthStopTextfiled = new west.gui.Textfield("healthStop");
        healthStopTextfiled.setValue(Dobby.settings.healthStop);
        healthStopTextfiled.setWidth(100);
        htmlHealthStop.append(healthStopTextfiled.getMainDiv());
        var htmlSetWearDelay = $("<div></div>");
        htmlSetWearDelay.append("<span> Job set equip delay </span>");
        var setWearDelayTextfiled = new west.gui.Textfield("setWearDelay");
        setWearDelayTextfiled.setValue(Dobby.settings.setWearDelay);
        setWearDelayTextfiled.setWidth(100);
        htmlSetWearDelay.append(setWearDelayTextfiled.getMainDiv());

        var htmlJobDelay = $("<div></div>");
        htmlJobDelay.append("<span> Random delay between jobs(seconds)</span>");
        var jobDelayTextFieldMin = new west.gui.Textfield("jobDelay");
        jobDelayTextFieldMin.setValue(Dobby.settings.jobDelayMin);
        jobDelayTextFieldMin.setWidth(50);
        var jobDelayTextFieldMax = new west.gui.Textfield("jobDelay");
        jobDelayTextFieldMax.setValue(Dobby.settings.jobDelayMax);
        jobDelayTextFieldMax.setWidth(50);

        htmlJobDelay.append(jobDelayTextFieldMin.getMainDiv());
        htmlJobDelay.append("<span> - </span>");
        htmlJobDelay.append(jobDelayTextFieldMax.getMainDiv());

        var checkboxKeepAlive = new west.gui.Checkbox();
        checkboxKeepAlive.setLabel("Precizie în fundal (minimizează tab)");
        checkboxKeepAlive.setSelected(Dobby.settings.keepAliveBackground);
        checkboxKeepAlive.setCallback(function() {
            Dobby.settings.keepAliveBackground = this.isSelected();
        });

        var htmlRegeneration = $("<div></div>");
        var checkboxEnableRegeneration = new west.gui.Checkbox();
        checkboxEnableRegeneration.setLabel("Enable regeneration");
        checkboxEnableRegeneration.setSelected(Dobby.settings.enableRegeneration);
        checkboxEnableRegeneration.setCallback(function() {
            Dobby.settings.enableRegeneration = !Dobby.settings.enableRegeneration;
            if(Dobby.settings.enableRegeneration) {
                $("#regeneration_choices_container").css('visibility','visible');
            }else {
                $("#regeneration_choices_container").css('visibility','hidden');
            }
        });

        var sleepPlacesCombobox = new west.gui.Combobox("sleep_places");
        Dobby.addSleepPlacesItems(sleepPlacesCombobox);
        sleepPlacesCombobox = sleepPlacesCombobox.select(Dobby.selectedSleepPlace);
        sleepPlacesCombobox.addListener(function(value) {
            Dobby.selectedSleepPlace = value;
            Dobby.selectTab("settings");
        });

        var htmlRegenerationChoices = $("<div id='regeneration_choices_container'></div>");
        htmlRegenerationChoices.css({'display':'inline-block','padding-left':'10px','visibility':(Dobby.settings.enableRegeneration)?'visible':"hidden"});
        htmlRegenerationChoices.append($("<span>Sleep place: </span>"));
        htmlRegenerationChoices.append(sleepPlacesCombobox.getMainDiv());

        htmlRegeneration.append(checkboxEnableRegeneration.getMainDiv());
        htmlRegeneration.append(htmlRegenerationChoices);



        var buttonApply = new west.gui.Button("Apply",function() {
            Dobby.settings.addEnergy = checkboxAddEnergy.isSelected();
            Dobby.settings.addMotivation = checkboxAddMotivation.isSelected();
            Dobby.settings.addHealth = checkboxAddHealth.isSelected();
            Dobby.settings.keepAliveBackground = checkboxKeepAlive.isSelected();
            if(Dobby.isNumber(healthStopTextfiled.getValue())) {
                var healthStop = parseInt(healthStopTextfiled.getValue());
                healthStop = Math.min(30,healthStop);
                Dobby.settings.healthStop = healthStop;
            }
            if(Dobby.isNumber(setWearDelayTextfiled.getValue())) {
                var setWearDelay = parseInt(setWearDelayTextfiled.getValue());
                setWearDelay = Math.min(10,setWearDelay);
                Dobby.settings.setWearDelay = setWearDelay;
            }
            if(Dobby.isNumber(jobDelayTextFieldMin.getValue())) {
                var jobDelayTimeMin = parseInt(jobDelayTextFieldMin.getValue());
                Dobby.settings.jobDelayMin = jobDelayTimeMin;
            }else {
                Dobby.settings.jobDelayMin = 0;
                Dobby.settings.jobDelayMax = 0;
                new UserMessage("Wrong format of delay job min value. Please set a number.", UserMessage.TYPE_ERROR).show();
            }
            if(Dobby.isNumber(jobDelayTextFieldMax.getValue())) {
                var jobDelayTimeMax = parseInt(jobDelayTextFieldMax.getValue());
                Dobby.settings.jobDelayMax = jobDelayTimeMax;
            }else {
                Dobby.settings.jobDelayMin = 0;
                Dobby.settings.jobDelayMax = 0;
                new UserMessage("Wrong format of delay job max value. Please set a number.", UserMessage.TYPE_ERROR).show();
            }
            Dobby.selectTab("settings");
        })

        htmlSkel.append(checkboxAddEnergy.getMainDiv());
        htmlSkel.append("<br>");
        htmlSkel.append(checkboxAddMotivation.getMainDiv());
        htmlSkel.append("<br>");
        htmlSkel.append(checkboxAddHealth.getMainDiv());
        htmlSkel.append("<br>");
        htmlSkel.append(htmlHealthStop);
        htmlSkel.append("<br>");
        htmlSkel.append(htmlSetWearDelay);
        htmlSkel.append("<br>");
        htmlSkel.append(htmlJobDelay);
        htmlSkel.append("<br>");
        htmlSkel.append(checkboxKeepAlive.getMainDiv());
        htmlSkel.append("<br>");
        htmlSkel.append(htmlRegeneration);
        htmlSkel.append("<br>");
        htmlSkel.append(buttonApply.getMainDiv());
        return htmlSkel;
    };
    Dobby.createStatisticsGui = function() {
        var htmlSkel = $("<div id=\'statistics_overview'\></div>");
        htmlSkel.append($("<span>Job count in this session: " + Dobby.statistics.jobsInSession + "</span><br>"));
        htmlSkel.append($("<span>Xp count in this session: " + Dobby.statistics.xpInSession + "</span><br>"));
        htmlSkel.append($("<span>Job count total: " + Dobby.statistics.totalJobs + "</span><br>"));
        htmlSkel.append($("<span>Xp count total: " + Dobby.statistics.totalXp + "</span><br>"));
        return htmlSkel;
    };
    Dobby.createMenuIcon = function() {
        var menuimage = 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAAQABAAD/2wBDAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/2wBDAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/wAARCAAZABoDASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAACggJ/8QAMBAAAQQBAgMGBAcBAAAAAAAABAECAwUGBxEACBIJExQVITEjM3a1NzlCUXFzsrT/xAAXAQADAQAAAAAAAAAAAAAAAAAFBgcE/8QAJhEAAgICAgEEAQUAAAAAAAAAAQIDBAUREiEGABMiMRQHFSNBgf/aAAwDAQACEQMRAD8ABeNE2KNOlN3ORrlVf1LtuiL6+yb7Jtt6cLU5AtEcSyDlVw/K4haG6mq8RxYSwJsfAl5Q5+SqO+sDhHkBlOnqVL8UAJ4J0gtbEPBEW4fZHyksVywpHIquRqNYxWpsiMVVT4jt1Tbb0avp6b8Jz7Ijn2zDTzRVukxddIMzGPN8bwPIYgxyBz4DTJr6Mc6aVGtrY6q3NiCktSZGjd2SOxrp54Ih2o3m1a1YxUZqu6vFajZgkvsllIIPz4u3+KN98uwujWP0e80PhXkdm+KOJu/l4yaqf3jHTZOtF8klDLViu0gJGdECyPKeI2q8S/NcSO0Jxeix3m51krMZoQ8cAgtaUmWnroIxQorIvGqee5mFGi2jhQyzUk6SGJGx+KJKfExrXsYkR9Lk92uRf2VFRU/lF9uEm88HZ42Ot93qDr1gOfxG6pSQgFXOBW0ckoWYOAogGjxYvYwo/wAkPjEjQCOOykloiSh+6WerVJZuDy2WL5PVWNhWWeL5EDY1ppQFgETSWDCAzQ55ByxSGIO5GTDzxyRSt6l6ZGOTddt+CuCyVe9RhSOb3Z6sMEFoMSHWZYlDH58WdWI2sgHFh/fIEBJ8qhmmzmSyTUFo18tfuZGrBBr8eKK3ZeZYIQrNwWEOI1jbTKoH2NMag1F5H+ZLTXQ5+vWaYI6o0+V+M7mvsA5jGVuVQQ+XW84bXpKPDFZTi0NlXkthuALMwaR4MlfI42O1+Q3OtO6LSasa6Ssly3HkzI6UGymjhQrJJDXS1gayLu6KQqqlj8m60Vs5TV7pFnGb0aE87P5d+Y/QuF/eMd4Pny1/NufrTTL7xZcDZppcvhbTzssTwW/iYVZQVUKoVgzsd6mOyCNlVIA7B0xVosXlaiQc2WxTR2DsNqZFDtxIUdbTXYJ0T3vv08nT6soCOWO0sLTB2YtluQWGN5EMHdFADW2OY8tKPEgKgsIfKZJKTDHCQ0ds3g4UcpKxLKvVlcbmukimFqun2UFqpU6qXLWBJIUqyv3IkR27kfMvxHo5VcjnKiqq8WcX+Hth9Lxf9AnGcs3zZf7H/wCl4meHokPdkWd0LzkHiGHS6IGxJs9No7J3rfRPqj+UWljiw8aQIQKQctJxckssW9/xqN/Z2B9n6H16/9k=';
        var div = $('<div class="ui_menucontainer" />');
        var link = $('<div id="Menu" class="menulink" onclick=Dobby.loadJobs(); title="Dobby 2" />').css('background-image', 'url(' + menuimage + ')');
        $('#ui_menubar').append((div).append(link).append('<div class="menucontainer_bottom" />'));
    };

    Dobby.XP_TOTALS = [0,42,99,177,282,420,597,819,1092,1422,1815,2277,2814,3432,4137,4935,5832,6834,7947,9177,10530,12012,13629,15387,17292,19350,21567,23949,26502,29232,32145,35247,38544,42042,45747,49665,53802,58164,62757,67587,72660,77982,83559,89397,95502,101880,108537,115479,122712,130242,138075,146217,154674,163452,172557,181995,191772,201894,212367,223197,234390,245952,257889,270207,282912,296010,309507,323409,337722,352452,367605,383187,399204,415662,432567,449925,467742,486024,504777,524007,543720,563922,584619,605817,627522,649740,672477,695739,719532,743862,768735,794157,820134,846672,873777,901455,929712,958554,987987,1018017,1048650,1079892,1111749,1144227,1177332,1211070,1245447,1280469,1316142,1352472,1389465,1427127,1465464,1504482,1544187,1584585,1625682,1667484,1709997,1753227,1798180,1849326,1911469,1989152,2086759,2208567,2358764,2541472,2760750,3020610,3325017,3677897,4083142,4544610,5066129,5651502,6304505,7028890,7828389,8706712,9667549,10714573,11851439,13081785,14409234,15837393,17369855,19010199,20761993,22628789,24614129,26721542,28954545,31316647,33811344,36442123,39212458,42125818,45185660,48395432,51758573,55278514,58958679,62802481,66813328,70994617,75349740,79882082,84595018,89491919,94576147,99851059,105320004,110986326,116853361,122924440,129202888,135692024,142395160,149315603,156456656,163821615,171413770,179236407,187292807,195586243,204119988,212897305,221921455,231195693,240723272,250507436,260551428,270858484,281431838,292274717,303390347,314781946,326452731,338405913,350644699,363172294,375991896,389106702,402519903,416234688,430254240,444581741,459220367,474173291,489443684,505034712,520949538,537191321,553763217,570668378,587909955,605491093,623414935,641684621,660303287,679274067,698600090,718284485,738330375,758740881,779519122,800668213,822191266,844091391,866371694,889035280,912085249,935524700,959356727,983584425,1008210883,1033239188,1058672427,1084513679,1110766027,1137432546,1164516310,1192020394,1219947865,1248301790,1277085236,1306301263,1335952932,1366043299];
    Dobby.getXpLevelInfo = function(xp) {
        var totals = Dobby.XP_TOTALS;
        var level = 1;
        for(var i = totals.length - 1; i >= 0; i--) {
            if(xp >= totals[i]) {
                level = i + 1;
                break;
            }
        }
        if(level >= totals.length) {
            return { level: level, xpToNext: null, progress: 100 };
        }
        var levelStart = totals[level - 1];
        var nextTotal = totals[level];
        var xpToNext = nextTotal - xp;
        var progress = Math.min(100, Math.max(0, ((xp - levelStart) / (nextTotal - levelStart)) * 100));
        return { level: level, xpToNext: xpToNext, progress: progress };
    };
    Dobby.liveStats = {
        POLL_MS: 2000,
        SESSION_KEY: 'tw_stats_session_start_xp',
        SESSION_TIME_KEY: 'tw_stats_session_start_time',
        JOBS_KEY: 'tw_stats_session_jobs',
        POS_KEY: 'tw_stats_panel_pos',
        SAMPLES_KEY: 'tw_live_xp_samples',
        sessionStartXp: null,
        sessionStartTime: null,
        jobsCount: 0,
        manualJobs: 0,
        lastDobbyJobs: 0,
        prevXpForJobs: null,
        lastXp: null,
        lastSampleTime: 0,
        samples: [],
        dragBound: false
    };
    Dobby.liveStatsFormatNum = function(n) {
        if(n == null || isNaN(n)) return '—';
        return Math.floor(n).toLocaleString('ro-RO');
    };
    Dobby.liveStatsFormatDuration = function(ms) {
        if(!isFinite(ms) || ms <= 0) return '—';
        var totalSec = Math.floor(ms / 1000);
        var h = Math.floor(totalSec / 3600);
        var m = Math.floor((totalSec % 3600) / 60);
        var s = totalSec % 60;
        if(h > 0) return h + 'h ' + m + 'm';
        if(m > 0) return m + 'm ' + s + 's';
        return s + 's';
    };
    Dobby.liveStatsPrune = function() {
        var cutoff = Date.now() - 3600000;
        Dobby.liveStats.samples = Dobby.liveStats.samples.filter(function(s) {
            return s.t >= cutoff;
        });
    };
    Dobby.liveStatsLoad = function() {
        try {
            var raw = localStorage.getItem(Dobby.liveStats.SAMPLES_KEY);
            if(raw) Dobby.liveStats.samples = JSON.parse(raw);
        } catch(e) {
            Dobby.liveStats.samples = [];
        }
        Dobby.liveStatsPrune();
    };
    Dobby.liveStatsSave = function() {
        Dobby.liveStatsPrune();
        try {
            localStorage.setItem(Dobby.liveStats.SAMPLES_KEY, JSON.stringify(Dobby.liveStats.samples));
        } catch(e) {}
    };
    Dobby.liveStatsRecord = function(xp) {
        var now = Date.now();
        if(Dobby.liveStats.lastXp === xp && (now - Dobby.liveStats.lastSampleTime) < 60000) return;
        Dobby.liveStats.samples.push({ t: now, xp: xp });
        Dobby.liveStats.lastXp = xp;
        Dobby.liveStats.lastSampleTime = now;
        Dobby.liveStatsSave();
    };
    Dobby.liveStatsXpPerHour = function() {
        Dobby.liveStatsPrune();
        if(Dobby.liveStats.samples.length < 2) return 0;
        var oldest = Dobby.liveStats.samples[0];
        var newest = Dobby.liveStats.samples[Dobby.liveStats.samples.length - 1];
        var dt = newest.t - oldest.t;
        if(dt < 120000) return 0;
        var dxp = newest.xp - oldest.xp;
        if(dxp <= 0) return 0;
        return dxp / (dt / 3600000);
    };
    Dobby.liveStatsInitSession = function(xp) {
        var storedXp = sessionStorage.getItem(Dobby.liveStats.SESSION_KEY);
        var storedTime = sessionStorage.getItem(Dobby.liveStats.SESSION_TIME_KEY);
        var storedJobs = sessionStorage.getItem(Dobby.liveStats.JOBS_KEY);
        if(storedXp != null && !isNaN(parseInt(storedXp, 10))) {
            Dobby.liveStats.sessionStartXp = parseInt(storedXp, 10);
        } else {
            Dobby.liveStats.sessionStartXp = xp;
            sessionStorage.setItem(Dobby.liveStats.SESSION_KEY, String(xp));
        }
        if(storedTime != null && !isNaN(parseInt(storedTime, 10))) {
            Dobby.liveStats.sessionStartTime = parseInt(storedTime, 10);
        } else {
            Dobby.liveStats.sessionStartTime = Date.now();
            sessionStorage.setItem(Dobby.liveStats.SESSION_TIME_KEY, String(Dobby.liveStats.sessionStartTime));
        }
        if(storedJobs != null && !isNaN(parseInt(storedJobs, 10))) {
            Dobby.liveStats.jobsCount = parseInt(storedJobs, 10);
            Dobby.liveStats.manualJobs = Dobby.liveStats.jobsCount;
        }
        Dobby.liveStats.prevXpForJobs = xp;
    };
    Dobby.liveStatsUpdateJobs = function(xp) {
        var dobbyJobs = (Dobby.statistics && Dobby.statistics.jobsInSession) ? Dobby.statistics.jobsInSession : 0;
        if(Dobby.liveStats.prevXpForJobs != null && xp > Dobby.liveStats.prevXpForJobs) {
            if(dobbyJobs <= Dobby.liveStats.lastDobbyJobs) {
                Dobby.liveStats.manualJobs += 1;
            }
            Dobby.liveStats.prevXpForJobs = xp;
        }
        Dobby.liveStats.lastDobbyJobs = dobbyJobs;
        Dobby.liveStats.jobsCount = Math.max(dobbyJobs, Dobby.liveStats.manualJobs, Dobby.liveStats.jobsCount);
        sessionStorage.setItem(Dobby.liveStats.JOBS_KEY, String(Dobby.liveStats.jobsCount));
    };
    Dobby.liveStatsSessionXpPerHour = function(sessionXp) {
        if(!Dobby.liveStats.sessionStartTime) return 0;
        var elapsed = Date.now() - Dobby.liveStats.sessionStartTime;
        if(elapsed < 120000) return 0;
        return sessionXp / (elapsed / 3600000);
    };
    Dobby.liveStatsApplyPosition = function() {
        try {
            var raw = localStorage.getItem(Dobby.liveStats.POS_KEY);
            if(!raw) return;
            var pos = JSON.parse(raw);
            if(pos.left != null && pos.top != null) {
                $('#tw-live-stats').css({
                    left: pos.left,
                    top: pos.top,
                    right: 'auto',
                    bottom: 'auto'
                });
            }
        } catch(e) {}
    };
    Dobby.liveStatsInitDrag = function() {
        if(Dobby.liveStats.dragBound || !$('#tw-live-stats').length) return;
        Dobby.liveStats.dragBound = true;
        var $panel = $('#tw-live-stats');
        var $handle = $('#tw-stats-drag-handle');
        var dragging = false;
        var ox = 0;
        var oy = 0;
        var startLeft = 0;
        var startTop = 0;
        $handle.on('mousedown', function(e) {
            dragging = true;
            var offset = $panel.offset();
            ox = e.clientX;
            oy = e.clientY;
            startLeft = offset.left;
            startTop = offset.top;
            $panel.css({ right: 'auto', bottom: 'auto', left: startLeft + 'px', top: startTop + 'px' });
            e.preventDefault();
        });
        $(document).on('mousemove.twstats', function(e) {
            if(!dragging) return;
            $panel.css({
                left: (startLeft + e.clientX - ox) + 'px',
                top: (startTop + e.clientY - oy) + 'px'
            });
        });
        $(document).on('mouseup.twstats', function() {
            if(!dragging) return;
            dragging = false;
            try {
                localStorage.setItem(Dobby.liveStats.POS_KEY, JSON.stringify({
                    left: $panel.css('left'),
                    top: $panel.css('top')
                }));
            } catch(err) {}
        });
    };
    Dobby.ensureLiveStatsPanel = function() {
        if($('#tw-live-stats').length) return;
        $('body').append(
            '<div id="tw-live-stats" style="position:fixed;bottom:16px;right:16px;z-index:99998;' +
            'background:rgba(12,18,28,0.93);color:#e6edf5;padding:8px 14px 10px;border-radius:10px;' +
            'font:12px/1.55 Consolas,Monaco,monospace;min-width:270px;box-shadow:0 4px 16px rgba(0,0,0,0.45);' +
            'border:1px solid rgba(90,140,210,0.35);pointer-events:auto;user-select:none;">' +
            '<div id="tw-stats-drag-handle" style="cursor:move;font-weight:bold;color:#6ec1ff;margin-bottom:7px;' +
            'padding-bottom:4px;border-bottom:1px solid rgba(90,140,210,0.25);">XP Live · trage aici</div>' +
            '<div id="tw-stats-tracking">Calculează de: —</div>' +
            '<div id="tw-stats-jobs">Munci încheiate: —</div>' +
            '<div id="tw-stats-session">XP sesiune: —</div>' +
            '<div id="tw-stats-level">Nivel: —</div>' +
            '<div id="tw-stats-progress">Progres nivel: —</div>' +
            '<div id="tw-stats-needed">Până la nivel: —</div>' +
            '<div id="tw-stats-eta">Timp estimat nivel: —</div>' +
            '<div id="tw-stats-hour">Medie ultima oră: —</div>' +
            '<div id="tw-stats-session-hour">Medie sesiune: —</div>' +
            '<div id="tw-stats-per-job">Medie per muncă: —</div>' +
            '</div>'
        );
        Dobby.liveStatsApplyPosition();
        Dobby.liveStatsInitDrag();
    };
    Dobby.updateLiveStats = function() {
        if(typeof Character === 'undefined' || Character.experience == null) return;
        var xp = Character.experience;
        if(Dobby.liveStats.sessionStartXp == null) {
            Dobby.liveStatsInitSession(xp);
        }
        Dobby.liveStatsRecord(xp);
        Dobby.liveStatsUpdateJobs(xp);
        var sessionXp = Math.max(0, xp - Dobby.liveStats.sessionStartXp);
        var elapsed = Dobby.liveStats.sessionStartTime ? Date.now() - Dobby.liveStats.sessionStartTime : 0;
        var levelInfo = Dobby.getXpLevelInfo(xp);
        var needed = levelInfo.xpToNext;
        var perHour = Dobby.liveStatsXpPerHour();
        var sessionPerHour = Dobby.liveStatsSessionXpPerHour(sessionXp);
        var perJob = Dobby.liveStats.jobsCount > 0 ? sessionXp / Dobby.liveStats.jobsCount : 0;
        var etaMs = perHour > 0 && needed > 0 ? (needed / perHour) * 3600000 : 0;
        Dobby.ensureLiveStatsPanel();
        $('#tw-stats-tracking').text('Calculează de: ' + Dobby.liveStatsFormatDuration(elapsed));
        $('#tw-stats-jobs').text('Munci încheiate: ' + Dobby.liveStatsFormatNum(Dobby.liveStats.jobsCount));
        $('#tw-stats-session').text('XP sesiune: +' + Dobby.liveStatsFormatNum(sessionXp));
        $('#tw-stats-level').text('Nivel ' + levelInfo.level + ' · total ' + Dobby.liveStatsFormatNum(xp) + ' XP');
        $('#tw-stats-progress').text('Progres nivel: ' + levelInfo.progress.toFixed(1) + '%');
        if(needed == null) {
            $('#tw-stats-needed').text('Până la nivel: Eternity (max)');
            $('#tw-stats-eta').text('Timp estimat nivel: —');
        } else {
            $('#tw-stats-needed').text('Până la nivel: ' + Dobby.liveStatsFormatNum(needed) + ' XP');
            $('#tw-stats-eta').text('Timp estimat nivel: ' + (perHour > 0 ? Dobby.liveStatsFormatDuration(etaMs) : '— (prea puține date)'));
        }
        $('#tw-stats-hour').text('Medie ultima oră: ' + Dobby.liveStatsFormatNum(perHour) + ' XP/h');
        $('#tw-stats-session-hour').text('Medie sesiune: ' + (elapsed >= 120000 ? Dobby.liveStatsFormatNum(sessionPerHour) + ' XP/h' : '— (prea puține date)'));
        $('#tw-stats-per-job').text('Medie per muncă: ' + (Dobby.liveStats.jobsCount > 0 ? Dobby.liveStatsFormatNum(perJob) + ' XP' : '—'));
    };
    Dobby.liveStatsLoop = function() {
        Dobby.updateLiveStats();
        setTimeout(Dobby.liveStatsLoop, document.hidden ? 4000 : Dobby.liveStats.POLL_MS);
    };
    Dobby.initLiveStats = function() {
        Dobby.liveStatsLoad();
        document.addEventListener('visibilitychange', Dobby.updateLiveStats);
        var tries = 0;
        var timer = setInterval(function() {
            tries++;
            if(typeof Character !== 'undefined' && Character.experience != null) {
                clearInterval(timer);
                Dobby.liveStatsInitSession(Character.experience);
                Dobby.liveStatsLoop();
            } else if(tries > 120) {
                clearInterval(timer);
            }
        }, 500);
    };

    $(document).ready(function() {
        try { Dobby.createMenuIcon(); } catch(e) { console.log("Dobby2: menu failed", e); }
        try { Dobby.hookAjax(); } catch(e) { console.log("Dobby2: ajax hook failed", e); }
        try { document.addEventListener('visibilitychange', Dobby.onVisibilityChange); } catch(e) {}
        try { Dobby.loadLanguage(); } catch(e) {}
        try { Dobby.loadSets(function(){}); } catch(e) {}
        try { Dobby.getCookies(); } catch(e) {}
        try { Dobby.initLiveStats(); } catch(e) { console.log("Dobby2: live stats failed", e); }
    });
})();