Nr.1

Nr.1 - job automation for The West

Você precisará instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Você precisará instalar uma extensão como o Tampermonkey para instalar este script.

Você precisará instalar um gerenciador de scripts de usuário para instalar este script.

(Eu já tenho um gerenciador de scripts de usuário, me deixe instalá-lo!)

Advertisement:

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

(Eu já possuo um gerenciador de estilos de usuário, me deixar fazer a instalação!)

Advertisement:

// ==UserScript==
// @name         Nr.1
// @namespace    http://tampermonkey.net/
// @version      2.1
// @description  Nr.1 - job automation for The West
// @author       Nr.1 the-west.ro/ Vegas
// @include https://*.the-west.*/game.php*
// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant        none
// @license MIT 
// ==/UserScript==

(function() {
    function Job(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;
    }
    Job.prototype = {
        setSilver: function(v) { this.silver = v; },
        calculateDistance: function() { this.distance = GameMap.calcWayTime({x: this.x, y: this.y}, Character.position); },
        setExperience: function(v) { this.experience = v; },
        setMoney: function(v) { this.money = v; },
        setMotivation: function(v) { this.motivation = v; },
        setStopMotivation: function(v) { this.stopMotivation = v; },
        setSet: function(v) { this.set = v; }
    };

    function Consumable(id, image, name) {
        this.id = id; this.image = image; this.name = name;
        this.energy = 0; this.motivation = 0; this.health = 0;
        this.selected = true; this.count = 0;
    }
    Consumable.prototype = {
        setEnergy: function(v) { this.energy = v; },
        setMotivation: function(v) { this.motivation = v; },
        setHealth: function(v) { this.health = v; },
        setSelected: function(v) { this.selected = v; },
        setCount: function(v) { this.count = v; }
    };

    var sortBy = function(key, desc) {
        return function(a, b) {
            if (!a && !b) return 0;
            if (!a) return desc ? -1 : 1;
            if (!b) return desc ? 1 : -1;
            var d = a[key] - b[key];
            return desc ? (d < 0 ? 1 : d > 0 ? -1 : 0) : (d < 0 ? -1 : d > 0 ? 1 : 0);
        };
    };

    Dobby = {
        window: null, jobsLoaded: false, allJobs: [], allConsumables: [], consumableUsed: [],
        addedJobs: [], forts: [], homeTown: null,
        jobFilter: {filterOnlySilver: false, filterNoSilver: false, filterCenterJobs: false, filterJob: ""},
        sortJobTableXp: 0, sortJobTableMoney: 0, sortJobTableDistance: 0,
        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", energyText: "Energie mărită:", motivation: "motiva", motivationText: "motiva", health: "via", healthText: "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},
        POLL_MS: 50, prepareJobTimeout: null, STORAGE_KEY: "nr1_data"
    };

    Dobby.isNumber = function(n) { return Number.isInteger(parseInt(n)); };
    Dobby.notify = function(msg, isError) {
        new UserMessage(msg, isError ? UserMessage.TYPE_ERROR : UserMessage.TYPE_HINT).show();
    };
    Dobby.ajaxGet = function(module, action, data, cb, retries) {
        var attempt = retries || 0;
        Ajax.get(module, action, data, function(r) {
            if (r && r.error && attempt < 3) {
                setTimeout(function() { Dobby.ajaxGet(module, action, data, cb, attempt + 1); }, 500 * (attempt + 1));
                return;
            }
            if (r && r.error) { Dobby.notify("Ajax error: " + r.error, true); return; }
            cb(r);
        });
    };
    Dobby.generateRandomNumber = function(min, max) {
        var lo = Math.min(min, max), hi = Math.max(min, max);
        return Math.floor(lo + Math.random() * (hi - lo + 1));
    };
    Dobby.delay = function(ms) { return new Promise(function(r) { setTimeout(r, ms); }); };
    Dobby.waitUntil = async function(fn) {
        while (!fn()) {
            if (!Dobby.isRunning && Dobby.currentState !== 2) return false;
            await Dobby.delay(Dobby.POLL_MS);
        }
        return true;
    };

    Dobby.loadJobs = function() {
        if (Dobby.jobsLoaded) { Dobby.findAllConsumables(); Dobby.createWindow(); return; }
        new UserMessage("Loading...", UserMessage.TYPE_HINT).show();
        Ajax.get("map", "get_minimap", {}, function(r) {
            var tiles = [], jobs = [], index = 0, len = 0, maxLen = 299;
            for (var tn in r.towns) {
                if (r.towns[tn].town_id == Character.homeTown.town_id) { Dobby.homeTown = r.towns[tn]; break; }
            }
            for (var g in r.job_groups) {
                var group = r.job_groups[g], jobsGroup = JobList.getJobsByGroupId(parseInt(g));
                for (var t = 0; t < group.length; t++) {
                    var x = Math.floor(group[t][0] / GameMap.tileSize), y = Math.floor(group[t][1] / GameMap.tileSize);
                    if (!len) tiles[index] = [];
                    tiles[index].push([x, y]);
                    if (++len == maxLen) { len = 0; index++; }
                    for (var i = 0; i < jobsGroup.length; i++) jobs.push(new Job(group[t][0], group[t][1], jobsGroup[i].id));
                }
            }
            var loaded = 0, total = tiles.length;
            for (var b = 0; b < total; b++) {
                GameMap.Data.Loader.load(tiles[b], function() {
                    if (++loaded == total) {
                        Dobby.jobsLoaded = true; Dobby.allJobs = jobs;
                        Dobby.findAllConsumables(); Dobby.createWindow();
                    }
                });
            }
        });
    };

    Dobby.loadJobData = function(cb) {
        Dobby.ajaxGet("work", "index", {}, function(r) {
            JobsModel.initJobs(r.jobs); cb();
        });
    };
    Dobby.loadSets = function(cb) { Ajax.remoteCallMode("inventory", "show_equip", {}, function(r) { Dobby.sets = r.data; cb(); }); };
    Dobby.loadLanguage = function() { Ajax.remoteCall("settings", "settings", {}, function(r) { Dobby.language = r.lang.account.key; }); };
    Dobby.loadJobMotivation = function(i, cb) {
        var j = Dobby.addedJobs[i];
        Dobby.ajaxGet("job", "job", {jobId: j.id, x: j.x, y: j.y}, function(r) { cb(r.motivation * 100); });
    };
    Dobby.getJobName = function(id) { return JobList.getJobById(id).name; };
    Dobby.getJobIcon = function(silver, id, x, y) {
        var s = silver ? '<div class="featured silver"></div>' : "";
        return '<div class="job" style="left:0;top:0;position:relative;"><div class="featured"></div>' + s +
            '<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>' +
            '<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 d = GameMap.JobHandler.Featured[x + "-" + y];
        return d && d[id] ? d[id].silver : false;
    };
    Dobby.compareUniqueJobs = function(job, jobs) {
        for (var i = 0; i < jobs.length; i++) {
            if (jobs[i].id != job.id) continue;
            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 i = 0; i < jobs.length; i++) {
            var j = jobs[i], d = Dobby.findJobData(j), xp = d.basis.short.experience, money = d.basis.short.money;
            j.setMotivation(d.jobmotivation * 100);
            if (j.silver) { xp = Math.ceil(1.5 * xp); money = Math.ceil(1.5 * money); }
            j.setExperience(xp); j.setMoney(money);
        }
    };
    Dobby.updateJobDistances = function() { Dobby.allJobs.forEach(function(j) { j.calculateDistance(); }); };

    Dobby.getAllUniqueJobs = function() {
        Dobby.updateJobDistances();
        var jobs = [], f = Dobby.jobFilter, nameFilter = f.filterJob.toLowerCase();
        for (var i = 0; i < Dobby.allJobs.length; i++) {
            var j = Dobby.allJobs[i];
            if (nameFilter && !Dobby.getJobName(j.id).toLowerCase().includes(nameFilter)) continue;
            if (!JobList.getJobById(j.id).canDo() || Dobby.checkIfJobAdded(j.id)) continue;
            j.silver = Dobby.checkIfSilver(j.x, j.y, j.id);
            j.calculateDistance();
            if ((j.silver && f.filterNoSilver) || (!j.silver && f.filterOnlySilver)) continue;
            if (f.filterCenterJobs && j.id < 131) continue;
            Dobby.compareUniqueJobs(j, jobs);
        }
        Dobby.parseJobData(jobs);
        if (Dobby.sortJobTableXp) jobs.sort(sortBy("experience", Dobby.sortJobTableXp == 1));
        if (Dobby.sortJobTableMoney) jobs.sort(sortBy("money", Dobby.sortJobTableMoney == 1));
        if (Dobby.sortJobTableDistance) jobs.sort(sortBy("distance", Dobby.sortJobTableDistance == 1));
        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)) Dobby.addedJobs.push(Dobby.findJob(x, y, id)); };
    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) { return Dobby.addedJobs.some(function(j) { return j.id == id; }); };
    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 j = Dobby.findAddedJob(x, y, id); return j ? j.set : undefined; };
    Dobby.setJobSet = function(x, y, id, set) { var j = Dobby.findAddedJob(x, y, id); if (j) j.setSet(set); };
    Dobby.setSetForAllJobs = function() {
        Dobby.addedJobs.forEach(function(j) { if (j.set == -1) j.setSet(Dobby.jobSet); });
    };
    Dobby.consolidePosition = function(idx) {
        if (idx <= 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 v = $(".dobby2window #x-" + Dobby.addedJobs[i].x + "y-" + Dobby.addedJobs[i].y + "id-" + Dobby.addedJobs[i].id).prop("value");
            if (!Dobby.isNumber(v)) return false;
            Dobby.addedJobs[i].setStopMotivation(parseInt(v));
        }
        return true;
    };
    Dobby.getItemImage = function(id) { return ItemManager.get(id).wear_image; };
    Dobby.normalizeText = function(text) {
        return (text || "").toLowerCase()
            .replace(/ş|ș/g, "s").replace(/ţ|ț/g, "t")
            .replace(/ă/g, "a").replace(/â/g, "a").replace(/î/g, "i");
    };

    Dobby.findAllConsumables = function() {
        var keys = Dobby.searchKeys[Dobby.language];
        if (keys) {
            ["energy", "motivation", "health"].forEach(function(t) {
                Bag.search(keys[t]).forEach(function(item) { Dobby.addConsumable(item); });
            });
        }
        if (Bag.items_by_type && Bag.items_by_type.yield) {
            for (var y = 0; y < Bag.items_by_type.yield.length; y++) {
                Dobby.addConsumable(Bag.getItemByItemId(Bag.items_by_type.yield[y]));
            }
        }
    };
    Dobby.resolveBonusText = function(bonus) {
        if (bonus == null || bonus === "") return null;
        if (typeof bonus === "string") return bonus;
        try {
            if (typeof west !== "undefined" && west.lang && west.lang.get) {
                var t = west.lang.get(bonus);
                if (t) return t;
            }
        } catch (e) {}
        return null;
    };
    Dobby.CheckIfConsumableAdded = function(item) {
        if (!item) return true;
        return Dobby.allConsumables.some(function(c) { return c.id == item.obj.item_id; });
    };
    Dobby.addConsumable = function(item) {
        if (!item || !item.obj || !item.obj.usebonus || !item.obj.usebonus.length) return;
        var b = Dobby.parseConsumableBonuses(item.obj.usebonus);
        if (!b[0] && !b[1] && !b[2]) return;
        if (Dobby.CheckIfConsumableAdded(item)) {
            var existing = Dobby.allConsumables.find(function(c) { return c.id == item.obj.item_id; });
            if (existing) existing.setCount(item.count);
            return;
        }
        var c = new Consumable(item.obj.item_id, item.obj.image, item.obj.name);
        c.setEnergy(b[0]); c.setMotivation(b[1]); c.setHealth(b[2]); c.setCount(item.count);
        Dobby.allConsumables.push(c);
    };
    Dobby.removeConsumable = function(item) {
        var idx = Dobby.allConsumables.findIndex(function(c) { return c.id == item.id; });
        if (idx < 0) return;
        if (Dobby.allConsumables[idx].count > 1) Dobby.allConsumables[idx].count--;
        else Dobby.allConsumables.splice(idx, 1);
    };
    Dobby.parseConsumableBonuses = function(bonuses) {
        var result = [0, 0, 0], norm = Dobby.normalizeText;
        if (!bonuses || !bonuses.length) return result;
        for (var i = 0; i < bonuses.length; i++) {
            var bonus = Dobby.resolveBonusText(bonuses[i]);
            if (!bonus) continue;
            var text = norm(bonus), pct = bonus.match(/(\d+)\s*%/);
            if (!pct) continue;
            var val = parseInt(pct[1], 10);
            if (text.includes("duel") || text.includes("duell") || text.includes("duelu")) continue;
            if (text.includes("energie") || text.includes("energy") || text.includes("energia")) result[0] = val;
            else if (text.includes("motiv") && (text.includes("munc") || text.includes("work") || text.includes("arbeits") || text.includes("prac") || text.includes("motywacji"))) result[1] = val;
            else if (text.includes("viat") || text.includes("health") || text.includes("zdrav") || text.includes("zycia") || text.includes("elet")) result[2] = val;
        }
        return result;
    };
    Dobby.filterConsumables = function(energy, motivation, health) {
        return Dobby.allConsumables.filter(function(c) {
            return (!energy || c.energy) && (!motivation || c.motivation) && (!health || c.health);
        });
    };
    Dobby.changeConsumableSelection = function(id, sel) {
        var c = Dobby.allConsumables.find(function(x) { return x.id == id; });
        if (c) c.setSelected(sel);
    };
    Dobby.changeSelectionAllConsumables = function(sel) {
        Dobby.allConsumables.forEach(function(c) { c.setSelected(sel); });
    };
    Dobby.canUseConsume = function(item) {
        var cd = BuffList.cooldowns[item.id];
        return !cd || cd.time <= new ServerDate().getTime();
    };
    Dobby.useConsumable = async function(item) {
        Bag.getItemByItemId(item.id).showCooldown();
        Dobby.currentState = 2; Dobby.selectTab("choosenJobs");
        await Dobby.waitUntil(function() { return Dobby.canUseConsume(item); });
        if (!Dobby.isRunning && Dobby.currentState !== 2) return;
        if (Dobby.healthSet != -1) { Dobby.equipSet(Dobby.healthSet); await Dobby.delay(Dobby.settings.setWearDelay * 1000); }
        Dobby.removeConsumable(item); Dobby.consumableUsed.push(item); ItemUse.doIt(item.id);
        await Dobby.waitUntil(function() { return !Dobby.canUseConsume(item); });
        $(".tw2gui_dialog_framefix").remove();
        if (Dobby.isRunning) Dobby.run();
    };
    Dobby.findProperConsumable = function(motMissing, energyMissing, healthMissing, avgMotMissing, list) {
        if (!list.length) return null;
        var byEnergy = list.slice().sort(function(a, b) {
            return Math.abs(energyMissing - a.energy) - Math.abs(energyMissing - b.energy);
        });
        if (energyMissing == 100) return byEnergy[0];
        if (motMissing == Dobby.addedJobs.length) {
            return list.reduce(function(best, c) {
                if (!c.motivation) return best;
                if (!best) return c;
                return Math.abs(avgMotMissing - c.motivation) < Math.abs(avgMotMissing - best.motivation) ? c : best;
            }, null);
        }
        if (Dobby.isHealthBelowLimit()) return list.find(function(c) { return c.health; }) || null;
    };
    Dobby.tryUseConsumable = function(result) {
        var selected = Dobby.allConsumables.filter(function(c) { return c.selected; });
        var item = Dobby.findProperConsumable(
            Dobby.jobsBelowMotivation(result),
            100 - (Character.energy / Character.maxEnergy) * 100,
            100 - (Character.health / Character.maxHealth) * 100,
            Dobby.averageMissingMotivation(result),
            selected
        );
        if (!item) return false;
        Dobby.useConsumable(item);
        return true;
    };
    Dobby.calculateDistances = function() { Dobby.addedJobs.forEach(function(j) { j.calculateDistance(); }); };
    Dobby.createDistanceMatrix = function() {
        var n = Dobby.addedJobs.length, d = [];
        for (var i = 0; i < n; i++) d[i] = new Array(n);
        for (var i = 0; i < n; i++) {
            for (var j = i; j < n; j++) {
                if (i == j) {
                    d[i][j] = Number.MAX_SAFE_INTEGER;
                } else {
                    var v = GameMap.calcWayTime(
                        {x: Dobby.addedJobs[i].x, y: Dobby.addedJobs[i].y},
                        {x: Dobby.addedJobs[j].x, y: Dobby.addedJobs[j].y}
                    );
                    d[i][j] = d[j][i] = v;
                }
            }
        }
        return d;
    };
    Dobby.createRoute = function() {
        Dobby.currentJob = {job: 0, direction: true};
        Dobby.calculateDistances();
        var closest = 0, dist = Dobby.addedJobs[0].distance, route = [], matrix = Dobby.createDistanceMatrix();
        for (var i = 1; i < Dobby.addedJobs.length; i++) {
            if (Dobby.addedJobs[i].distance < dist) { dist = Dobby.addedJobs[i].distance; closest = i; }
        }
        route.push(closest);
        while (route.length < Dobby.addedJobs.length) {
            var last = route[route.length - 1], best = -1, bestD = Number.MAX_SAFE_INTEGER;
            for (var k = 0; k < matrix.length; k++) {
                if (k == last || route.indexOf(k) >= 0) continue;
                if (matrix[k][last] < bestD) { bestD = matrix[k][last]; best = k; }
            }
            route.push(best);
        }
        Dobby.addedJobs = route.map(function(i) { return Dobby.addedJobs[i]; });
        Dobby.selectTab("choosenJobs");
    };
    Dobby.getSetItemArray = function(set) {
        return ["head", "neck", "body", "right_arm", "left_arm", "belt", "foot", "animal", "yield", "pants"]
            .map(function(k) { return set[k]; }).filter(Boolean);
    };
    Dobby.isWearing = function(itemId) {
        var t = ItemManager.get(itemId).type;
        return Wear.wear[t] && Wear.wear[t].obj.item_id == itemId;
    };
    Dobby.isGearEquiped = async function(items) {
        for (var i = 0; i < items.length; i++) if (!Dobby.isWearing(items[i])) return false;
        return true;
    };
    Dobby.equipSet = async function(set) {
        if (set == -1) return true;
        EquipManager.switchEquip(Dobby.sets[set].equip_manager_id);
        await Dobby.waitUntil(function() { return Dobby.isGearEquiped(Dobby.getSetItemArray(Dobby.sets[set])); });
        return true;
    };
    Dobby.getBestGear = function(jobid) {
        for (var i = 0; i < JobsModel.Jobs.length; i++) {
            if (JobsModel.Jobs[i].id == jobid) {
                var r = west.item.Calculator.getBestSet(JobsModel.Jobs[i].get("skills"), jobid);
                return r && r.getItems();
            }
        }
    };
    Dobby.equipBestGear = async function(jobid) {
        var gear = Dobby.getBestGear(jobid);
        if (!gear) return true;
        for (var i = 0; i < gear.length; i++) if (!Dobby.isWearing(gear[i])) Wear.carry(Bag.getItemByItemId(gear[i]));
        await Dobby.waitUntil(function() { return Dobby.isGearEquiped(gear); });
        return true;
    };
    Dobby.checkMotivation = function(index, result, cb) {
        Dobby.loadJobMotivation(index, function(m) {
            result.push(m);
            if (index + 1 < Dobby.addedJobs.length) Dobby.checkMotivation(index + 1, result, cb);
            else cb(result);
        });
    };
    Dobby.isMotivationAbove = function(result) {
        for (var i = 0; i < result.length; i++) if (result[i] > Dobby.addedJobs[i].stopMotivation) return true;
        return false;
    };
    Dobby.jobsBelowMotivation = function(result) {
        var c = 0;
        for (var i = 0; i < result.length; i++) if (result[i] <= Dobby.addedJobs[i].stopMotivation) c++;
        return c;
    };
    Dobby.averageMissingMotivation = function(result) {
        var s = 0;
        for (var i = 0; i < result.length; i++) s += 100 - result[i];
        return s / result.length;
    };
    Dobby.isHealthBelowLimit = function() {
        return Dobby.settings.healthStop >= (Character.health / Character.maxHealth) * 100;
    };
    Dobby.isStopMotivationZero = function() {
        return Dobby.addedJobs.some(function(j) { return j.stopMotivation == 0; });
    };
    Dobby.canAddMissing = function(result) {
        if (!Dobby.settings.addMotivation && Dobby.jobsBelowMotivation(result) && !Dobby.isStopMotivationZero()) {
            Dobby.notify("Can't continue because of motivation", true); return false;
        }
        if (!Dobby.settings.addEnergy && Character.energy == 0) { Dobby.notify("Can't continue because of energy", true); return false; }
        if (!Dobby.settings.addHealth && Dobby.isHealthBelowLimit()) { Dobby.notify("Can't continue because of health", true); return false; }
        return true;
    };
    Dobby.stopRun = function(msg) {
        Dobby.isRunning = false; Dobby.currentState = 0;
        if (Dobby.prepareJobTimeout) { clearTimeout(Dobby.prepareJobTimeout); Dobby.prepareJobTimeout = null; }
        Dobby.cancelJobs();
        Dobby.saveData();
        Dobby.selectTab("choosenJobs");
        if (msg) Dobby.notify(msg, false);
    };
    Dobby.finishRun = function() { Dobby.stopRun("Finished"); };
    Dobby.validateBeforeStart = function() {
        if (!Dobby.addedJobs.length) { Dobby.notify("Add at least one job", true); return false; }
        if (!Dobby.parseStopMotivation()) { Dobby.notify("Wrong format of set stop motivation", true); return false; }
        if (Dobby.sets === null) { Dobby.notify("Sets not loaded yet", true); return false; }
        for (var i = 0; i < Dobby.addedJobs.length; i++) {
            var set = Dobby.addedJobs[i].set;
            if (set != -1 && !Dobby.sets[set]) { Dobby.notify("Invalid set on a job", true); return false; }
        }
        return true;
    };
    Dobby.run = function() {
        if (!Dobby.isRunning) return;
        Dobby.checkMotivation(0, [], function(result) {
            if (!Dobby.isRunning) return;
            if ((Dobby.isMotivationAbove(result) || Dobby.isStopMotivationZero()) && Character.energy > 0 && !Dobby.isHealthBelowLimit()) {
                Dobby.currentState = 1; Dobby.selectTab("choosenJobs"); Dobby.prepareJobRun(Dobby.currentJob.job);
            } else if (!Dobby.canAddMissing(result) || !Dobby.tryUseConsumable(result)) Dobby.finishRun();
        });
    };
    Dobby.prepareJobRun = function(index) {
        if (!Dobby.isRunning) return;
        if (Dobby.prepareJobTimeout) clearTimeout(Dobby.prepareJobTimeout);
        Dobby.prepareJobTimeout = setTimeout(function() {
            if (!Dobby.isRunning) return;
            Dobby.loadJobMotivation(index, async function(motivation) {
                if (!Dobby.isRunning) return;
                if (Character.energy == 0 || Dobby.isHealthBelowLimit()) { Dobby.run(); return; }
                if (motivation <= Dobby.addedJobs[index].stopMotivation && Dobby.addedJobs[index].stopMotivation > 0) {
                    Dobby.checkMotivation(0, [], function(result) {
                        Dobby.isMotivationAbove(result) ? Dobby.changeJob() : Dobby.run();
                    });
                    return;
                }
                if (GameMap.calcWayTime(Character.position, {x: Dobby.addedJobs[index].x, y: Dobby.addedJobs[index].y})) {
                    await Dobby.equipSet(Dobby.travelSet);
                    if (Dobby.isRunning) Dobby.walkToJob(index);
                    return;
                }
                var maxJobs = Premium.hasBonus("automation") ? 9 : 4;
                var count = Dobby.addedJobs[index].stopMotivation != 0
                    ? Math.min(Math.min(motivation - Dobby.addedJobs[index].stopMotivation, Character.energy), maxJobs)
                    : Math.min(Character.energy, maxJobs);
                Dobby.runJob(index, count);
            });
        }, Dobby.generateRandomNumber(Dobby.settings.jobDelayMin, Dobby.settings.jobDelayMax) * 1000);
    };
    Dobby.walkToJob = async function(index) {
        if (!Dobby.isRunning) return;
        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});
        });
        if (!Dobby.isRunning) { Dobby.cancelJobs(); return; }
        Dobby.cancelJobs();
        if (arrived && Dobby.isRunning) Dobby.prepareJobRun(index);
    };
    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.saveData(); Dobby.run();
    };
    Dobby.runJob = async function(jobIndex, jobCount) {
        if (!Dobby.isRunning) return;
        await Dobby.equipBestGear(Dobby.addedJobs[jobIndex].id);
        if (!Dobby.isRunning) return;
        for (var i = 0; i < jobCount; i++) JobWindow.startJob(Dobby.addedJobs[jobIndex].id, Dobby.addedJobs[jobIndex].x, Dobby.addedJobs[jobIndex].y, 15);
        await Dobby.delay(Dobby.settings.setWearDelay * 1000);
        if (!Dobby.isRunning) { Dobby.cancelJobs(); return; }
        Dobby.equipSet(Dobby.addedJobs[jobIndex].set);
        await Dobby.waitUntil(function() { return TaskQueue.queue.length == 0 || Dobby.isHealthBelowLimit(); });
        if (!Dobby.isRunning) { Dobby.cancelJobs(); Dobby.saveData(); return; }
        if (TaskQueue.queue.length == 0) {
            Dobby.saveData(); Dobby.prepareJobRun(jobIndex);
        } else {
            Dobby.cancelJobs(); Dobby.saveData();
            if (Dobby.isHealthBelowLimit()) Dobby.run();
        }
    };
    Dobby.cancelJobs = function() { if (TaskQueue.queue.length) TaskQueue.cancelAll(); };
    Dobby.applySavedJobs = function(jobs) {
        jobs.forEach(function(j) {
            var p = new Job(j.x, j.y, j.id);
            p.setSilver(j.silver); p.distance = j.distance;
            p.setExperience(j.experience); p.setMoney(j.money);
            p.setMotivation(j.motivation); p.setStopMotivation(j.stopMotivation); p.setSet(j.set);
            Dobby.addedJobs.push(p);
        });
    };
    Dobby.saveData = function() {
        try {
            localStorage.setItem(Dobby.STORAGE_KEY, JSON.stringify({
                addedJobs: Dobby.addedJobs, travelSet: Dobby.travelSet, jobSet: Dobby.jobSet,
                healthSet: Dobby.healthSet, currentJob: Dobby.currentJob, settings: Dobby.settings,
                consumableSelection: Dobby.consumableSelection
            }));
        } catch (e) {}
    };
    Dobby.loadData = function() {
        try {
            var raw = localStorage.getItem(Dobby.STORAGE_KEY);
            if (raw) {
                var o = JSON.parse(raw);
                if (o.addedJobs) Dobby.applySavedJobs(o.addedJobs);
                Dobby.travelSet = o.travelSet != null ? o.travelSet : Dobby.travelSet;
                Dobby.jobSet = o.jobSet != null ? o.jobSet : Dobby.jobSet;
                Dobby.healthSet = o.healthSet != null ? o.healthSet : Dobby.healthSet;
                if (o.currentJob) Dobby.currentJob = o.currentJob;
                if (o.settings) Dobby.settings = o.settings;
                if (o.consumableSelection) Dobby.consumableSelection = o.consumableSelection;
                Dobby.setSetForAllJobs();
                return;
            }
        } catch (e) {}
        Dobby.migrateCookies();
    };
    Dobby.migrateCookies = function() {
        document.cookie.split(";").forEach(function(part) {
            var kv = part.trim().split("=");
            if (kv[0] == "dobby2temporary") {
                var o = JSON.parse(kv.slice(1).join("="));
                Dobby.applySavedJobs(o.addedJobs);
                Dobby.travelSet = o.travelSet; Dobby.jobSet = o.jobSet; Dobby.healthSet = o.healthSet;
                Dobby.currentJob = o.currentJob; Dobby.setSetForAllJobs();
            } else if (kv[0] == "dobby2pernament") {
                var p = JSON.parse(kv.slice(1).join("="));
                if (p.settings) Dobby.settings = p.settings;
            }
        });
        if (Dobby.addedJobs.length) Dobby.saveData();
    };

    Dobby.switchTab = function(id, build) {
        Dobby.removeActiveTab(Dobby.window);
        Dobby.removeWindowContent();
        Dobby.addActiveTab(id, Dobby.window);
        var content = $("<div class='dobby2window'/>").append(build());
        Dobby.window.appendToContentPane(content);
        return content;
    };
    Dobby.applyScrollPos = function(pos) {
        $(".dobby2window .tw2gui_scrollpane_clipper_contentpane").css("top", pos.content);
        $(".dobby2window .tw2gui_scrollbar_pulley").css("top", pos.scrollbar);
    };
    Dobby.saveScrollPos = function(pos) {
        pos.content = $(".dobby2window .tw2gui_scrollpane_clipper_contentpane").css("top");
        pos.scrollbar = $(".dobby2window .tw2gui_scrollbar_pulley").css("top");
    };
    Dobby.makeCheckbox = function(label, selected, cb) {
        var c = new west.gui.Checkbox();
        c.setLabel(label); c.setSelected(selected);
        c.setCallback(function() { cb(this.isSelected()); });
        return c;
    };

    Dobby.createWindow = function() {
        var win = wman.open("dobby").setResizeable(false).setMinSize(650, 480).setSize(650, 480).setMiniTitle("Nr.1");
        var tabs = {jobs: "Jobs", choosenJobs: "Choosen jobs", sets: "Sets", consumables: "Consumables", settings: "Settings"};
        var tabLogic = function(w, id) {
            switch (id) {
                case "jobs":
                    Dobby.loadJobData(function() {
                        Dobby.switchTab("jobs", Dobby.createJobsTab);
                        Dobby.addJobTableCss(); Dobby.applyScrollPos(Dobby.jobTablePosition); Dobby.addEventsHeader();
                    });
                    break;
                case "choosenJobs":
                    Dobby.switchTab("choosenJobs", Dobby.createAddedJobsTab);
                    Dobby.addAddedJobsTableCss(); Dobby.applyScrollPos(Dobby.addedJobTablePosition);
                    break;
                case "consumables":
                    Dobby.findAllConsumables();
                    Dobby.switchTab("consumables", Dobby.createConsumablesTable);
                    Dobby.addConsumableTableCss(); Dobby.applyScrollPos(Dobby.consumableTablePosition);
                    break;
                case "sets":
                    Dobby.loadSets(function() { Dobby.switchTab("sets", Dobby.createSetGui); });
                    break;
                case "settings":
                    Dobby.switchTab("settings", Dobby.createSettingsGui);
                    break;
            }
        };
        for (var k in tabs) win.addTab(tabs[k], k, tabLogic);
        Dobby.window = win; Dobby.selectTab("jobs");
    };
    Dobby.selectTab = function(key) { Dobby.window.tabIds[key].f(Dobby.window, key); };
    Dobby.removeActiveTab = function(w) { $("div.tw2gui_window_tab", w.divMain).removeClass("tw2gui_window_tab_active"); };
    Dobby.addActiveTab = function(key, w) { $("div._tab_id_" + key, w.divMain).addClass("tw2gui_window_tab_active"); };
    Dobby.removeWindowContent = function() { $(".dobby2window").remove(); };

    Dobby.addJobTableCss = function() {
        [{c: "jobIcon", w: "80px"}, {c: "jobName", w: "150px"}, {c: "jobXp", w: "40px"}, {c: "jobMoney", w: "40px"},
         {c: "jobMotivation", w: "40px"}, {c: "jobDistance", w: "100px"}].forEach(function(x) {
            $(".dobby2window ." + x.c).css("width", x.w);
        });
        $(".dobby2window .row").css("height", "60px");
        $(".dobby2window .tw2gui_scrollpane").css("height", "250px");
    };
    Dobby.addAddedJobsTableCss = function() {
        [{c: "jobIcon", w: "80px"}, {c: "jobName", w: "130px"}, {c: "jobStopMotivation", w: "110px"},
         {c: "jobRemove", w: "105px"}, {c: "jobSet", w: "100px"}].forEach(function(x) {
            $(".dobby2window ." + x.c).css("width", x.w);
        });
        $(".dobby2window .row").css("height", "60px");
        $(".dobby2window .tw2gui_scrollpane").css("height", "250px");
    };
    Dobby.addConsumableTableCss = function() {
        [{c: "consumIcon", w: "80px"}, {c: "consumName", w: "120px"}, {c: "consumCount", w: "70px"},
         {c: "consumEnergy", w: "70px"}, {c: "consumMotivation", w: "70px"}, {c: "consumHealth", w: "70px"}].forEach(function(x) {
            $(".dobby2window ." + x.c).css("width", x.w);
        });
        $(".dobby2window .row").css("height", "80px");
        $(".dobby2window .tw2gui_scrollpane").css("height", "250px");
    };
    Dobby.addEventsHeader = function() {
        $(".dobby2window .jobXp").click(function() {
            Dobby.sortJobTableXp = Dobby.sortJobTableXp == 1 ? -1 : 1;
            Dobby.sortJobTableMoney = 0; Dobby.sortJobTableDistance = 0; Dobby.selectTab("jobs");
        });
        $(".dobby2window .jobMoney").click(function() {
            Dobby.sortJobTableMoney = Dobby.sortJobTableMoney == 1 ? -1 : 1;
            Dobby.sortJobTableXp = 0; Dobby.sortJobTableDistance = 0; Dobby.selectTab("jobs");
        });
        $(".dobby2window .jobDistance").click(function() {
            Dobby.sortJobTableDistance = Dobby.sortJobTableDistance == 1 ? -1 : 1;
            Dobby.sortJobTableXp = 0; Dobby.sortJobTableMoney = 0; Dobby.selectTab("jobs");
        });
    };

    Dobby.createJobsTab = function() {
        var skel = $("<div id='jobs_overview'></div>");
        var filters = $("<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(), jobs = Dobby.getAllUniqueJobs();
        var arrow = function(on, asc) { return on ? '&nbsp;<img src="../images/window/jobs/sortarrow_' + (asc ? "asc" : "desc") + '.png"/>' : ""; };
        table.addColumn("jobIcon", "jobIcon").addColumn("jobName", "jobName").addColumn("jobXp", "jobXp")
            .addColumn("jobMoney", "jobMoney").addColumn("jobMotivation", "jobMotivation").addColumn("jobDistance", "jobDistance").addColumn("jobAdd", "jobAdd");
        table.appendToCell("head", "jobIcon", "Job icon").appendToCell("head", "jobName", "Job name")
            .appendToCell("head", "jobXp", '<img src="/images/icons/star.png">' + arrow(Dobby.sortJobTableXp, Dobby.sortJobTableXp == 1))
            .appendToCell("head", "jobMoney", '<img src="/images/icons/dollar.png">' + arrow(Dobby.sortJobTableMoney, Dobby.sortJobTableMoney == 1))
            .appendToCell("head", "jobMotivation", '<img src="/images/icons/motivation.png">')
            .appendToCell("head", "jobDistance", "Distance " + arrow(Dobby.sortJobTableDistance, Dobby.sortJobTableDistance == 1))
            .appendToCell("head", "jobAdd", "");
        for (var i = 0; i < jobs.length; i++) {
            var j = jobs[i];
            table.appendRow().appendToCell(-1, "jobIcon", Dobby.getJobIcon(j.silver, j.id, j.x, j.y))
                .appendToCell(-1, "jobName", Dobby.getJobName(j.id)).appendToCell(-1, "jobXp", j.experience)
                .appendToCell(-1, "jobMoney", j.money).appendToCell(-1, "jobMotivation", j.motivation)
                .appendToCell(-1, "jobDistance", j.distance.formatDuration()).appendToCell(-1, "jobAdd", Dobby.createAddJobButton(j.x, j.y, j.id));
        }
        var tf = new west.gui.Textfield("jobsearch").setPlaceholder("Select job name");
        if (Dobby.jobFilter.filterJob) tf.setValue(Dobby.jobFilter.filterJob);
        var onlyS = Dobby.makeCheckbox("Silvers", Dobby.jobFilter.filterOnlySilver, function(v) { Dobby.jobFilter.filterOnlySilver = v; });
        var noS = Dobby.makeCheckbox("No silvers", Dobby.jobFilter.filterNoSilver, function(v) { Dobby.jobFilter.filterNoSilver = v; });
        var center = Dobby.makeCheckbox("Center jobs", Dobby.jobFilter.filterCenterJobs, function(v) { Dobby.jobFilter.filterCenterJobs = v; });
        var btn = new west.gui.Button("Filter", function() {
            Dobby.jobFilter.filterJob = tf.getValue();
            Dobby.jobTablePosition = {content: "0px", scrollbar: "0px"};
            Dobby.selectTab("jobs");
        });
        skel.append(table.getMainDiv());
        $("#jobFilter", filters).append(tf.getMainDiv());
        $("#job_only_silver", filters).append(onlyS.getMainDiv());
        $("#job_no_silver", filters).append(noS.getMainDiv());
        $("#job_center", filters).append(center.getMainDiv());
        $("#button_filter_jobs", filters).append(btn.getMainDiv());
        return skel.append(filters);
    };
    Dobby.createAddJobButton = function(x, y, id) {
        var b = new west.gui.Button("Add new job", function() {
            Dobby.addJob(x, y, id); Dobby.saveScrollPos(Dobby.jobTablePosition); Dobby.selectTab("jobs");
        });
        b.setWidth(100); return b.getMainDiv();
    };
    Dobby.createAddedJobsTab = function() {
        var skel = $("<div id='added_jobs_overview'></div>");
        var footer = $("<div id='start_dobby2' style='position:relative;'><span class='dobby_state' style='position:absolute;left:20px;top:10px;font-family:Arial,Helvetica,sans-serif;font-size:15px;font-weight:bold;'>Current state: " + Dobby.states[Dobby.currentState] + "</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 i = 0; i < Dobby.addedJobs.length; i++) {
            var j = Dobby.addedJobs[i];
            table.appendRow().appendToCell(-1, "jobIcon", Dobby.getJobIcon(j.silver, j.id, j.x, j.y))
                .appendToCell(-1, "jobName", Dobby.getJobName(j.id))
                .appendToCell(-1, "jobStopMotivation", Dobby.createMinMotivationTextfield(j.x, j.y, j.id, j.stopMotivation))
                .appendToCell(-1, "jobSet", Dobby.createComboxJobSets(j.x, j.y, j.id))
                .appendToCell(-1, "jobRemove", Dobby.createRemoveJobButton(j.x, j.y, j.id));
        }
        var start = new west.gui.Button("Start", function() {
            if (!Dobby.validateBeforeStart()) return;
            Dobby.consumableUsed = [];
            Dobby.createRoute(); Dobby.isRunning = true; Dobby.saveData(); Dobby.run();
        });
        var stop = new west.gui.Button("Stop", function() { Dobby.stopRun("Stopped"); });
        skel.append(table.getMainDiv());
        $(".dobby_run", footer).append(start.getMainDiv()).append(stop.getMainDiv());
        return skel.append(footer);
    };
    Dobby.createMinMotivationTextfield = function(x, y, id, val) {
        var tf = new west.gui.Textfield().setId("x-" + x + "y-" + y + "id-" + id).setWidth(40).setValue(val);
        return tf.getMainDiv();
    };
    Dobby.createRemoveJobButton = function(x, y, id) {
        var b = new west.gui.Button("Remove job", function() {
            Dobby.removeJob(x, y, id); Dobby.saveScrollPos(Dobby.addedJobTablePosition); Dobby.selectTab("choosenJobs");
        });
        b.setWidth(100); return b.getMainDiv();
    };
    Dobby.createComboxJobSets = function(x, y, id) {
        var cb = new west.gui.Combobox();
        Dobby.addComboboxItems(cb);
        cb.select(Dobby.getJobSet(x, y, id)).setWidth(60).addListener(function(v) {
            Dobby.setJobSet(x, y, id, v); Dobby.selectTab("choosenJobs");
        });
        return cb.getMainDiv();
    };
    Dobby.addComboboxItems = function(cb) {
        cb.addItem(-1, "None");
        for (var i = 0; i < Dobby.sets.length; i++) cb.addItem(i.toString(), Dobby.sets[i].name);
    };
    Dobby.createSetGui = function() {
        if (!Dobby.sets.length) return $("<span style='font-size:20px'>No sets available</span>");
        var skel = $("<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:0;left:0'></div><div id='dobby2_sets_right' style='display:block;position:absolute;width:300px;height:410px;top:0;left:325px'></div></div>");
        var cb = new west.gui.Combobox("combobox_sets");
        Dobby.addComboboxItems(cb);
        cb.select(Dobby.selectedSet).addListener(function(v) { Dobby.selectedSet = v; Dobby.selectTab("sets"); });
        var mkBtn = function(label, fn) { return new west.gui.Button(label, fn).getMainDiv(); };
        var setName = function(i) { return i == -1 ? "None" : Dobby.sets[i].name; };
        var left = $("<div></div>").append(new west.gui.Groupframe()
            .appendToContentPane($("<span>Sets</span><br><br>")).appendToContentPane(cb.getMainDiv())
            .appendToContentPane($("<br><br><span>Travel set:" + setName(Dobby.travelSet) + "</span><br><br>")).appendToContentPane(mkBtn("Select travel set", function() { Dobby.travelSet = Dobby.selectedSet; Dobby.selectTab("sets"); }))
            .appendToContentPane($("<br><br><span>Job set:" + setName(Dobby.jobSet) + "</span><br><br>")).appendToContentPane(mkBtn("Select job set", function() { Dobby.jobSet = Dobby.selectedSet; Dobby.setSetForAllJobs(); Dobby.selectTab("sets"); }))
            .appendToContentPane($("<br><br><span>Health set:" + setName(Dobby.healthSet) + "</span><br><br>")).appendToContentPane(mkBtn("Select health set", function() { Dobby.healthSet = Dobby.selectedSet; Dobby.selectTab("sets"); }))
            .appendToContentPane($("<br><br><span>Regeneration set:" + setName(Dobby.regenerationSet) + "</span><br><br>")).appendToContentPane(mkBtn("Select regeneration set", function() { Dobby.regenerationSet = Dobby.selectedSet; Dobby.selectTab("sets"); }))
            .getMainDiv());
        var right = $("<div style='display:block;position:relative;width:300px;height:410px;'></div>");
        var slots = [
            ["wear_head", "30px", "1px", "93px", "94px", "-95px 0"], ["wear_body", "30px", "106px", "95px", "138px", "0 0"],
            ["wear_pants", "30px", "258px", "93px", "138px", "0 0"], ["wear_neck", "-47px", "1px", "74px", "74px", "-189px 0"],
            ["wear_right_arm", "-64px", "79px", "95px", "138px", "0 0"], ["wear_animal", "-64px", "223px", "93px", "94px", "-95px 0"],
            ["wear_yield", "-47px", "321px", "74px", "74px", "-189px 0"], ["wear_left_arm", "127px", "52px", "95px", "138px", "0 0"],
            ["wear_belt", "127px", "200px", "93px", "94px", "-95px 0"], ["wear_foot", "127px", "302px", "93px", "94px", "-95px 0"]
        ];
        slots.forEach(function(s) {
            right.append("<div class='" + s[0] + " wear_slot' style='display:block;position:absolute;left:" + s[1] + ";top:" + s[2] +
                ";width:" + s[3] + ";height:" + s[4] + ";background:url(https://westzz.innogamescdn.com/images/window/wear/bg_sprite.png) 0 0 no-repeat;background-position:" + s[5] + ";'></div>");
        });
        if (Dobby.selectedSet != -1) Dobby.insertSetImages(right, ["head", "body", "pants", "neck", "right_arm", "animal", "yield", "left_arm", "belt", "foot"]);
        $("#dobby2_sets_left", skel).append(left);
        $("#dobby2_sets_right", skel).append(right);
        return skel;
    };
    Dobby.insertSetImages = function(html, keys) {
        var set = Dobby.sets[Dobby.selectedSet];
        keys.forEach(function(k) {
            if (set[k] != null) $(".wear_" + k, html).append($("<img src=''>").attr("src", Dobby.getItemImage(set[k])));
        });
    };
    Dobby.createConsumablesTable = function() {
        var skel = $("<div id='consumables_overview'></div>");
        var filters = $("<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(), list = 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 < list.length; i++) {
            (function(c) {
                var box = new west.gui.Checkbox();
                box.setSelected(c.selected).setId(c.id).setCallback(function() {
                    Dobby.changeConsumableSelection(parseInt(this.divMain.attr("id")), this.isSelected());
                    Dobby.saveScrollPos(Dobby.consumableTablePosition); Dobby.selectTab("consumables"); Dobby.saveData();
                });
                table.appendRow().appendToCell(-1, "consumIcon", Dobby.getConsumableIcon(c.image)).appendToCell(-1, "consumName", c.name)
                    .appendToCell(-1, "consumCount", c.count).appendToCell(-1, "consumEnergy", c.energy)
                    .appendToCell(-1, "consumMotivation", c.motivation).appendToCell(-1, "consumHealth", c.health)
                    .appendToCell(-1, "consumSelected", box.getMainDiv());
            })(list[i]);
        }
        var selAll = new west.gui.Button("Select all", function() { Dobby.changeSelectionAllConsumables(true); Dobby.selectTab("consumables"); Dobby.saveData(); });
        var deselAll = new west.gui.Button("Deselect all", function() { Dobby.changeSelectionAllConsumables(false); Dobby.selectTab("consumables"); Dobby.saveData(); });
        table.appendToFooter("consumEnergy", selAll.getMainDiv()).appendToFooter("consumHealth", deselAll.getMainDiv());
        var eC = Dobby.makeCheckbox("Energy consumables", Dobby.consumableSelection.energy, function(v) { Dobby.consumableSelection.energy = v; });
        var mC = Dobby.makeCheckbox("Motivation consumables", Dobby.consumableSelection.motivation, function(v) { Dobby.consumableSelection.motivation = v; });
        var hC = Dobby.makeCheckbox("Health consumables", Dobby.consumableSelection.health, function(v) { Dobby.consumableSelection.health = v; });
        var btn = new west.gui.Button("Select", function() { Dobby.selectTab("consumables"); });
        skel.append(table.getMainDiv());
        $("#energy_consumables", filters).append(eC.getMainDiv());
        $("#motivation_consumables", filters).append(mC.getMainDiv());
        $("#health_consumables", filters).append(hC.getMainDiv());
        $("#button_filter_consumables", filters).append(btn.getMainDiv());
        return skel.append(filters);
    };
    Dobby.addSleepPlacesItems = function(cb) {
        cb.addItem(-2, "None");
        if (Dobby.homeTown) cb.addItem(-1, Dobby.homeTown.name);
        for (var i = 0; i < Dobby.forts.length; i++) {
            var t = Dobby.forts[i].type == 0 ? "Small" : Dobby.forts[i].type == 1 ? "Medium" : "Large";
            cb.addItem(i.toString(), Dobby.forts[i].name + "  -  " + t);
        }
    };
    Dobby.createSettingsGui = function() {
        var skel = $("<div id='settings_overview' style='padding:10px;'></div>");
        var addE = Dobby.makeCheckbox("Add energy", Dobby.settings.addEnergy, function(v) { Dobby.settings.addEnergy = v; });
        var addM = Dobby.makeCheckbox("Add motivation", Dobby.settings.addMotivation, function(v) { Dobby.settings.addMotivation = v; });
        var addH = Dobby.makeCheckbox("Add health", Dobby.settings.addHealth, function(v) { Dobby.settings.addHealth = v; });
        var healthStop = new west.gui.Textfield("healthStop").setValue(Dobby.settings.healthStop).setWidth(100);
        var setWear = new west.gui.Textfield("setWearDelay").setValue(Dobby.settings.setWearDelay).setWidth(100);
        var delayMin = new west.gui.Textfield("jobDelay").setValue(Dobby.settings.jobDelayMin).setWidth(50);
        var delayMax = new west.gui.Textfield("jobDelay").setValue(Dobby.settings.jobDelayMax).setWidth(50);
        var regen = Dobby.makeCheckbox("Enable regeneration", Dobby.settings.enableRegeneration, function(v) {
            Dobby.settings.enableRegeneration = v;
            $("#regeneration_choices_container").css("visibility", v ? "visible" : "hidden");
        });
        var sleepCb = new west.gui.Combobox("sleep_places");
        Dobby.addSleepPlacesItems(sleepCb);
        sleepCb.select(Dobby.selectedSleepPlace).addListener(function(v) { Dobby.selectedSleepPlace = v; Dobby.selectTab("settings"); });
        var regenChoices = $("<div id='regeneration_choices_container'></div>").css({display: "inline-block", "padding-left": "10px", visibility: Dobby.settings.enableRegeneration ? "visible" : "hidden"})
            .append($("<span>Sleep place: </span>")).append(sleepCb.getMainDiv());
        var apply = new west.gui.Button("Apply", function() {
            Dobby.settings.addEnergy = addE.isSelected();
            Dobby.settings.addMotivation = addM.isSelected();
            Dobby.settings.addHealth = addH.isSelected();
            if (Dobby.isNumber(healthStop.getValue())) Dobby.settings.healthStop = Math.min(30, parseInt(healthStop.getValue()));
            if (Dobby.isNumber(setWear.getValue())) Dobby.settings.setWearDelay = Math.min(10, parseInt(setWear.getValue()));
            if (Dobby.isNumber(delayMin.getValue())) Dobby.settings.jobDelayMin = parseInt(delayMin.getValue());
            else { Dobby.settings.jobDelayMin = Dobby.settings.jobDelayMax = 0; new UserMessage("Wrong format of delay job min value. Please set a number.", UserMessage.TYPE_ERROR).show(); }
            if (Dobby.isNumber(delayMax.getValue())) Dobby.settings.jobDelayMax = parseInt(delayMax.getValue());
            else { Dobby.settings.jobDelayMin = Dobby.settings.jobDelayMax = 0; new UserMessage("Wrong format of delay job max value. Please set a number.", UserMessage.TYPE_ERROR).show(); }
            Dobby.selectTab("settings");
        });
        skel.append(addE.getMainDiv(), "<br>", addM.getMainDiv(), "<br>", addH.getMainDiv(), "<br>",
            $("<div><span> Stoppage health percent value </span></div>").append(healthStop.getMainDiv()), "<br>",
            $("<div><span> Job set equip delay </span></div>").append(setWear.getMainDiv()), "<br>",
            $("<div><span> Random delay between jobs(seconds)</span></div>").append(delayMin.getMainDiv()).append("<span> - </span>").append(delayMax.getMainDiv()), "<br>",
            $("<div></div>").append(regen.getMainDiv()).append(regenChoices), "<br>", apply.getMainDiv());
        return skel;
    };
    Dobby.createMenuIcon = function() {
        var img = "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=";
        $("#ui_menubar").append($('<div class="ui_menucontainer" />').append(
            $('<div id="Menu" class="menulink" onclick="Dobby.loadJobs();" title="Nr.1" />').css("background-image", "url(" + img + ")")
        ).append('<div class="menucontainer_bottom" />'));
    };

    $(document).ready(function() {
        try {
            Dobby.loadLanguage();
            Dobby.loadSets(function() {});
            Dobby.loadData();
            Dobby.createMenuIcon();
        } catch (e) { console.log("exception occured"); }
    });
})();