Anastasia

Dobby

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

Advertisement:

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

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); }
    });
})();