Greasy Fork is available in English.

Janka Tietzova

A user script for The West, optimized Dobby2 for butt plugs and cake decorations.

بۇ قوليازمىنى بىۋاسىتە قاچىلاشقا بولمايدۇ. بۇ باشقا قوليازمىلارنىڭ ئىشلىتىشى ئۈچۈن تەمىنلەنگەن ئامبار بولۇپ، ئىشلىتىش ئۈچۈن مېتا كۆرسەتمىسىگە قىستۇرىدىغان كود: // @require https://update.greasyfork.org/scripts/512872/1465973/Janka%20Tietzova.js

// ==UserScript==
// @name            Janka Tietzova
// @namespace       http://tampermonkey.net/
// @version         2.1.1
// @description     A user script for The West, optimized Dobby2 for butt plugs and cake decorations.
// @author          Chvostnatý Gábor
// @include         https://*.the-west.*/game.php*
// @license         MIT
// @icon            data:image/jpeg;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAAZCAYAAADE6YVjAAAAIGNIUk0AAHomAACAhAAA+gAAAIDoAAB1MAAA6mAAADqYAAAXcJy6UTwAAAAGYktHRAD/AP8A/6C9p5MAAAAJcEhZcwAAEnQAABJ0Ad5mH3gAAAAHdElNRQfoAwISKiXgp+5lAAAFKUlEQVRIx4WVy28cWRXGf+dWudvtdrc78TOjJJOHIUOiZDGLGbFAQUKDYMWfwGKGBRIrHmLBgoeGBRI7/gxACGmkEQiQYEZkCBNmnJB4jOPYcWx30u9HdXVVddU9LLr65Qhxpaq6p+rc853vfLfOlQ/vbqsxgjEGJ72MMTiOQUTGlzHTts7YxgjGmfg6jsGYqW8iTJyngo7GyFa1WBsDishsAiP3/2W78DLANHA8CDn6zz28epm18+fI5ZfIn7nAQnE1DQRiJokZI1MAw6crAgJjkJnsgedP7lP++A+4SUJ/UMcun6Vb22f9ypsUVi6C6DBAum56PQgCuIbTLCaOfa/N4f07nD9T4NLVTVDweh0afovKwT/JFZbJ5PII0+t4aW7kFMDESehWj3hxsEsQK5VGGysO7W5I7WmZ2tM9/NbRKf2YmY9MM/1hmrKi9DpVwiDk37snPNh+itdusP2kzO6TFxw93qdTeYZNEv7fMKczGdsKg7CP6whv3LjKGzc/h9rh/Pbr13AFWuXHxKHHJOfJUJ3M3dNbdoIIySAimxU06GK9ObqVDu6Cg9fvMJfL4nt1wm6VbL6EqqKaVkF1JqaZ3m4zlFRRG+InMUf1Fkmzy1Kti+kF1Hp9EjEMogi/U8PaZBxcpyioDt+ZmdRTR2xCMuiTyw5YW8lTqVY48dqEmxsc97t8uv0Y11FElaB1TNRrjbUZBVZVlOFzRhPVIVavXaZx8BGxV4EkZmV5CTeT5dH9BxwfP6ewtEDpTIFCYZFBp0z7ZIdEbRp8CkRBX9JEgDAi8/AzGtt/4oVtU280+fz5K1y6usm5V16h1aqze7zHfDZHoVike3BI6RDcM1fQ9Y2xNtNA7oxIfh/3V7+k9LvfUHQTovVF9FyJ1QubzGfnWSwWyQDB82cMtj6l3gjJ3d9j/aDCwh//jP+Td7HXb55iI8i9rT0VEcR1MXc+pPSddyj0fRwgEqG7kCW4uMb8pQsUvQTb6dIddAm7HsVnDfLAHBABXP8C0U/fJb79FYwIjjPs3mZaEHP3Dk7fRxAcgRzKai9gdecIHjwiONiFw30kDDhX7bIMZAUsQojQf7SN/OD7mI/vYh0Hq2CtTUFEwPeZ2/oXAsQiKIIzFI28tayftMgEMVE+x1K5zXwwGCaT/hs9gZYIrYN99Ec/ZO7997CdLlbBqCqIIPUacniAAj6QpD1UZNi+HYWlapvVozqLfjjuTQ6QCJRVaaqSiBB98gnxt97G/uzH2HZ7wsS0mphuBxA8oD3sLMNjQGX01w4TmmrjCnhiGHzxS8Sv3cAAAxFafo/mr39L+MEHuCMQp9MmE0WYlIWHsILi2CGQIFh00qUEVCBQxXv1Ihs//wVJPyD49jtw+IQI6K2VaFb3UiZRhPz1LzhhiIgwlzLopaULUCIUmyYQAYGCp3AMlC9dRtfWca9dI/nm27TyBZqrywTf+DLNuI2L4yAPH5K8/x4+MI8yQFAUD5gXIatKBkVT1oMUyEc4UYVCCTXO8BT82lsEtceYrJJsrFDthLgqguzvYRpVQqCvUBMoMSrTzG10CqAIMRACa4Uiqoq3/xm9nX+Q3VylHcYEmbNc/+ptXBKLXr5M762vEz64R7/n04oGnI2icdAk1cOkAAkyLtsc4DhKc+tvBCc7hKFPn3kWXn2d1269ycJiEVesRW/cwv/u9wg++j3hcYXk71s42ztYwKYcIgQzLBgJkKDECBnX5bB1jP/wDswtULx8i4tXbrK4soEYQxzH/BeqMKTC7KpG/gAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAyNC0wMy0wMlQxODo0MjozMSswMDowMC8Ty8sAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMjQtMDMtMDJUMTg6NDI6MzErMDA6MDBeTnN3AAAAKHRFWHRkYXRlOnRpbWVzdGFtcAAyMDI0LTAzLTAyVDE4OjQyOjM3KzAwOjAwaotnkgAAAABJRU5ErkJggg==
// @grant           none
// @downloadURL https://update.greasyfork.org/scripts/497915/Zdenka%20Studenkova.user.js
// @updateURL https://update.greasyfork.org/scripts/497915/Zdenka%20Studenkova.meta.js
// ==/UserScript==




(function() {
    async function sleep(milliseconds) {  
        await new Promise(r => setTimeout(r, milliseconds))
    }

    function parseConsumableBonuses(bonuses) {
        const { energyText, motivationText, healthText, speedText, luckText, dropText, experienceText, moneyText } = Vajda.searchKeys[Vajda.language]

        const result = {
            energy: 0,
            health: 0,
            motivation: 0,
            hasBuffs: false,
            buffs: {
                character: {
                    luck: 0,
                    money: 0,
                    experience: 0,
                    drop: 0
                },
                travel: 0
            }
        }

        function getValue(bonus, bonusText) {
            return Number(bonus.toLowerCase().replace(bonusText.toLowerCase(), '').replace(' ', '').replace('%', '').replace('+', ''))
        }
        
        for (const bonus of bonuses) {
            if ( bonus.includes(energyText) ) {
                result.energy = getValue(bonus, energyText)
                continue
            }
            
            if ( bonus.includes(motivationText) ) {
                result.motivation = getValue(bonus, motivationText)
                continue
            }

            if ( bonus.includes(healthText) ) { 
                result.health = getValue(bonus, healthText)
                continue
            }

            if ( bonus.includes(speedText) ) {
                result.buffs.travel = getValue(bonus, speedText)
                result.hasBuffs = true
                continue
            }

            if ( bonus.includes(luckText) ) {
                result.buffs.character.luck = getValue(bonus, luckText)
                result.hasBuffs = true
                continue
            }

            if ( bonus.includes(experienceText) ) {
                result.buffs.character.experience = getValue(bonus, experienceText)
                result.hasBuffs = true
                continue
            }

            if ( bonus.includes(moneyText) ) {
                result.buffs.character.money = getValue(bonus, moneyText)
                result.hasBuffs = true
                continue
            }

            if ( bonus.includes(dropText) ) {
                result.buffs.character.drop = getValue(bonus, dropText)
                result.hasBuffs = true
            }
        }

        return result
    }


    class Job {
        constructor(x, y, id, groupId) {
            this.x = x
            this.y = y
            this.id = id
            this.groupId = groupId
            this.isSilver = false
            this.distances = []
            this.experience = 0
            this.money = 0
            this.motivation = 0
            this.stopMotivation = 75
            this.set = -1
            this.bestEquipment_ = undefined
        }

        setIsSilver(val) {
            this.isSilver = val
            return this
        }

        setExperience(val) {
            this.experience = val
            return this
        }

        setMoney(val) {
            this.money = val
            return this
        }

        setMotivation(valOrUpdater) {
            if ( typeof valOrUpdater === 'function' ) {
                this.motivation = valOrUpdater(this.motivation)
            } else {
                this.motivation = valOrUpdater
            } 

            this.motivation = Math.min(100, this.motivation)

            return this
        }

        setStopMotivation(val) {
            this.stopMotivation = val
            return this
        }

        setSet(index) {
            this.set = index
            return this
        }

        getDistances() {
            return this.distances
        }

        async loadMotivation() {
            await Ajax.get('job', 'job', {jobId: this.id, x: this.x, y: this.y}, (r) => {
                this.motivation = r.motivation * 100
            })

            return this.motivation
        }

        /**
         * Calculate job distance to current character position
         */
        calculateDistance() {
            this.distance = GameMap.calcWayTime({x: this.x, y: this.y}, Character.position)
            return this.distance
        }

        /**
         * Calculate job distance to other selected jobs
         */
        calculateJobDistances() {
            this.distances = []
            for ( const job of Vajda.addedJobs ) {
                this.distances.push(GameMap.calcWayTime({x: this.x, y: this.y}, {x: job.x, y: job.y}))
            }
            return this.distances
        }


        saveBestEquipment() {
            const skills = JobsModel.Jobs.find(j => j.id === this.id).get('skills')
            const set = west.item.Calculator.getBestSet(skills, this.id)
            const itemIds = set && set.getItems() || []
            this.bestEquipment_ = itemIds
            return itemIds
        }

        get bestEquipment() {
            return this.bestEquipment_ === undefined ? this.saveBestEquipment() : this.bestEquipment_
        }


        getBestEquipment() {
            if (!Bag.loaded) {
                EventHandler.listen('inventory_loaded', function() {
                    this.getBestEquipment()
                    return EventHandler.ONE_TIME_EVENT
                })
                return
            }
            
            const items = Bag.getItemsByItemIds(this.bestEquipment)
            const result = []

            for (const item of items) {
                const wearItem = Wear.get(item.getType())
                if (!wearItem || (wearItem && (wearItem.getItemBaseId() !== item.getItemBaseId() || wearItem.getItemLevel() < item.getItemLevel()))) {
                    result.push(item)
                }
            }
            return result.map(item => item.obj.getId())
        }
    }

    class Consumable {
        constructor(id, image, name) {
            this.id = id
            this.image = image
            this.name = name
            this.energy = 0
            this.motivation = 0
            this.health = 0
            this.isSelected_ = false
            this.count = 0
        }

        isCakeDecoration() {
            return this.id === 53339000
        }


        removeOne() {
            this.count -= 1

            if ( this.count === 0 ) {
                const consumables = Manager.consumables
                const index = consumables.findIndex(c => c.id === this.id)
                consumables.splice(index, 1)
            }
            return this
        }

        setCount(valOrUpdater) {
            if ( typeof valOrUpdater === 'function' ) {
                this.count = valOrUpdater(this.count)
            } else {
                this.count = valOrUpdater
            }

            this.count = Math.max(0, this.count)
            return this
        }

        setEnergy(val) {
            this.energy = val
            return this
        }

        setMotivation(val) {
            this.motivation = val
            return this
        }

        setHealth(val) {
            this.health = val
            return this
        }

        setIsSelected(isSelected) {
            this.isSelected_ = isSelected
            return this
        }

        hasCooldown() {
            return this.energy > 0 || this.motivation > 0 || this.health > 0
        }

        getBuffHTML() {
            return '<strong>No Buffs</strong>'
        }

        get isSelected() {
            return this.isSelected_
        }
    }

    class Buff extends Consumable {
        constructor(id, image, name, buffs) {
            super(id, image, name, buffs)
            this.buffs_ = buffs
        }

        getBuffImage() {
            return `https://westsk.innogamescdn.com/images/buffs/${this.getBuffType()}.jpg`
        }

        getBuffType() {
            return this.buffs_.travel === 0 ? 'character' : 'travel'
        }

        canUseAsBuff() {
            return CharacterSkills.buffs[this.getBuffType()] === null && !this.hasCooldown() 
        }

        getBuffHTML() {
            const containerStyle = `'
                display: grid;
                grid-template-columns: 30px auto;
            '`

            const imgContainerStyle = `'
                display: flex;
                padding-bottom: 5px;
            '`

            const buffsContainerStyle = `'
                display: grid;
                grid-template-rows: repeat(auto-fill, 15px);
                transform: translateY(5px)
            '`
            const buffs = this.buffs.reduce((acc, b) => acc.concat(`<p>${b}</p>`), '')

            const html = `
                <div style=${containerStyle}>
                    <span style=${imgContainerStyle}>
                        <img style=align-self:end width=25px height=25px src=${this.getBuffImage()} alt='Buff Image'>
                    </span>

                    <span style=${buffsContainerStyle}>
                        ${
                            buffs
                        }
                    </span>
                </div>
            `

            return html
        }

        removeOne() {
            this.count -= 1

            if ( this.count === 0 ) {
                const consumables = Manager.consumables
                const buffs = Manager.buffs
                let index = consumables.findIndex(c => c.id === this.id)
                if ( index !== -1 ) {
                    consumables.splice(index, 1)
                    return this
                }
                index = buffs.findIndex(b => b.id === this.id)
                if ( index !== -1 ) {
                    buffs.splice(index, 1)
                    if ( Manager.selectedBuffs.some(b => b.id === this.id) ) {
                        Manager.selectedBuffs_[this.getBuffType()] = null
                    }
                }
            }
            return this
        }

        get isSelected() {
            return Manager.selectedBuffs.some(b => b.id === this.id) || this.isSelected_
        }


        get buffs() {
            if ( this.getBuffType() === 'travel' ) {
                return [`+${this.buffs_.travel}% speed`]
            }

            const buffs = []
            for ( const buff in this.buffs_.character ) {
                if ( this.buffs_.character[buff] > 0 ) {
                    buffs.push(`+${this.buffs_.character[buff]}% ${buff}`)
                }  
            }
            return buffs
        }
    }


    class ConsumablesManager {
        selectedBuffs_ = {
            travel: null,
            character: null
        }

        constructor() {
            this.consumables_ = []
            this.buffs_ = []
            this.isOptimized_ = false
            this.jobsLeftInRound_ = 0
            this.schedule_ = []
            this.selectedBuffs_ = {
                travel: null,
                character: null
            }
        }

        async loadJobMotivation(updatedJobsMotivation = undefined) {
            let expectedJobCount = 0
            const uniqueJobsCount = Vajda.addedJobs.length

            if ( updatedJobsMotivation !== undefined ) {
                expedtedJobCount = updatedJobsMotivation.reduce((acc, curr) => acc + curr, 0)

                return { expectedJobCount, uniqueJobsCount }
            }

            for ( const job of Vajda.addedJobs ) {
                expectedJobCount += Math.max(await job.loadMotivation() - job.stopMotivation, 0)

                await sleep(500)
            }

            return { expectedJobCount, uniqueJobsCount }
        }

        async createSchedule(updatedJobsMotivation) {
            const bottlePlugs = this.consumables.find(c => c.id === 52871000)

            //in case the game runs in the background and the job to travel to is not canceled we gonna need extra energy point
            //this is unlikely to happen but the energy wont go to waste anyway so why the fuck not
            const { expectedJobCount, uniqueJobsCount } = await this.loadJobMotivation(updatedJobsMotivation)

            this.jobsLeftInRound = expectedJobCount
    
            const availableEnergy = Character.energy
            const energyToFill = expectedJobCount + uniqueJobsCount - availableEnergy
            const refillCount = Math.ceil(
                energyToFill / ( bottlePlugs.energy / 100 * Character.maxEnergy )
            )

            //use plugs when n jobs left in round
            const schedule = []

            for ( let i = 0; i < refillCount; i++ ) {
                //40 15s jobs in consumable cooldown
                schedule.push(40 + i*40)
            }

            this.schedule_ = schedule
        
            return schedule
        }

        isScheduledRefill() {
            return this.isOptimized_ && this.refillsLeft > 0 && this.jobsLeftInRound <= this.schedule.at(-1) 
        }

        async checkSchedule(jobCount) {
            const bottlePlug = this.consumables.find(c => c.id === 52871000)

            for ( let i = 0; i < jobCount; i++ ) {
                this.jobsLeftInRound = p => p - 1
                if ( this.isScheduledRefill() ) {
                    //the use of consumable blocks the start of new jobs for a short amount of time, so this sleep exists to prevent that
                    await sleep(10000)
                    this.useConsumableOrWaitForCooldown(bottlePlug)
                    this.schedule.pop()
                    this.jobsLeftInRound = p => p - (jobCount - i - 1)
                    return
                }
            }

            if ( Character.energy - jobCount <= 10 ) {
                //same here
                await sleep(10000)
                this.useConsumableOrWaitForCooldown(bottlePlug)
                this.schedule.pop()
            }
        }

        canUseConsumable(consumable) {
            if ( consumable instanceof Buff ) {
                return consumable.canUseAsBuff()
            }

            if ( BuffList.cooldowns[consumable.id] !== undefined && BuffList.cooldowns[consumable.id].time > new ServerDate().getTime() ) {
                return false
            }
            return true
        } 

        findProperConsumable(motivationMissing, energyMissing, averageMotivationMissing) {
            const consumablesPool = this.getSelectedConsumables()

            function betterEnergy(item1, item2) {
                let distanceItem1 = Math.abs(energyMissing - item1.energy)
                let distanceItem2 = Math.abs(energyMissing - item2.energy)
                return (distanceItem1 < distanceItem2 ) ? -1 : (distanceItem1 > distanceItem2) ? 1 : 0
            }
            function betterMotivation(item1, item2) {
                let distanceItem1 = Math.abs(averageMotivationMissing - item1.motivation)
                let distanceItem2 = Math.abs(averageMotivationMissing - item2.motivation)
                return (distanceItem2 < distanceItem1) ? item2 : item1
            }
            function findMotivationConsume(consumes) {
                let consumeToChoose = null
                for ( let 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
            }
            function findHealthConsume(consumes) {
                for ( let i = 0; i < consumes.length; i++ ) {
                    if ( consumes[i].health !== 0 ) {
                        return consumes[i]
                    }
                }
                return null
            }

            if ( consumablesPool.length  === 0 ) return null
    
            const consumables = consumablesPool.sort(betterEnergy)
            
            if ( Vajda.settings.addEnergy && energyMissing === 100 ) {
                return consumables[0]
            }
    
            if ( Vajda.settings.addMotivation && motivationMissing === Vajda.addedJobs.length ) {
                return findMotivationConsume(consumables) 
            }
    
            if ( Vajda.settings.addHealth && Vajda.isHealthBelowLimit() ) {
                if ( this.isOptimized ) {
                    this.schedule.pop()
                }
                return findHealthConsume(consumables)
            }
        }

        async useConsumable(consumable) {
            const item = Bag.getItemByItemId(consumable.id)
            item.showCooldown()

            if ( Vajda.shouldEquipHealthSet(consumable) )
                await Vajda.equipSet(Vajda.healthSet)

            ItemUse.doIt(consumable.id)
            consumable.removeOne()     
            
            while(true) {
                if ( !this.canUseConsumable(consumable) ) {
                    $('.tw2gui_dialog_framefix').remove()
                    break
                }
                await sleep(1)
            }

            Vajda.currentState = 1
        }

        async useConsumableOrWaitForCooldown(consumableOrId, isSync = false) {
            const consumable = consumableOrId instanceof Consumable ? consumableOrId : this.getSelectedConsumables().find(c => c.id === consumableOrId)
            if ( consumable === undefined ) {
                return false
            }

            Vajda.currentState = 2

            while (true) {
                if ( consumable.hasCooldown() === false || this.canUseConsumable(consumable) ) {
                    break
                }

                if ( !Vajda.isRunning ) {
                    break
                }

                await sleep(1000)
            }

            await this.useConsumable(consumable)
            
            isSync && Vajda.run()
        }

        isConsumableAdded (item) {
            if ( item === undefined )
                return true
            for ( const consumable of this.consumables_ ) {
                if ( consumable.id === item.obj.item_id ) {
                    return true
                }
            }
            for ( const buff of this.buffs_ ) {
                if ( buff.id === item.obj.item_id ) {
                    return true
                }
            }
            return false
        }

        useBuff(type) {
            const buff = this.selectedBuffs_[type]

            if ( buff?.canUseAsBuff() ) {
                this.useConsumable(buff)
            }
        }

        addNewConsumable(item) {
            if ( this.isConsumableAdded(item) ) {
                return
            }
            
            const { energy, motivation, health, hasBuffs, buffs } = parseConsumableBonuses(item.obj.usebonus)

            if ( health === 0 && motivation === 0 && energy === 0 && !hasBuffs )
                return

            if ( hasBuffs ) {
                const buff = new Buff(item.obj.item_id, item.obj.image, item.obj.name, buffs)
                    .setEnergy(energy)
                    .setMotivation(motivation)
                    .setHealth(health)
                    .setCount(item.count)
                
                return buff.hasCooldown() ? this.consumables_.push(buff) : this.buffs_.push(buff)
            }
            
            const consumable = new Consumable(item.obj.item_id, item.obj.image, item.obj.name)
                .setEnergy(energy)
                .setMotivation(motivation)
                .setHealth(health)
                .setCount(item.count)
            this.consumables.push(consumable)
        }

        hasEnoughPlugsAndDecorations() {
            const bottlePlugs = this.consumables.find(c => c.id === 52871000)
            const decorations = this.consumables.find(c => c.id === 53339000)

            if ( !bottlePlugs || !decorations ) {
                new UserMessage("No plugs or decorations were found, defaulting back to selected consumables", UserMessage.TYPE_HINT).show()
                this.isOptimized_ = false
                return false
            }
            return true
        }

        getSelectedConsumables() {
            if ( this.isOptimized_ ) {
                const bottlePlugs = this.consumables.find(c => c.id === 52871000)
                const decorations = this.consumables.find(c => c.id === 53339000)

                return [bottlePlugs, decorations]
            } else {
                return this.consumables.filter(c => c.isSelected === true)
            }
        }

        addSelectedConsumable(val) {
            if ( typeof val === 'number' )
                this.consumables.at(val).isSelected = true

            if ( val instanceof Consumable ) 
                this.consumables.find(c => c.id === val.id).isSelected = true
        }

        get schedule() {
            return this.schedule_
        }

        set schedule(val) {
            this.schedule_ = val
        }

        get consumables() {
            return this.consumables_
        }

        set consumables(val) {
            this.consumables_ = val
        }

        get isOptimized() {
            return this.isOptimized_
        }

        set isOptimized(valOrUpdater) {
            if ( typeof valOrUpdater === 'function' ) {
                this.isOptimized_ = valOrUpdater(this.isOptimized_)
            } else {
                this.isOptimized_ = valOrUpdater
            }
        }

        get jobsLeftInRound() {
            return this.jobsLeftInRound_
        }
        
        set jobsLeftInRound(valOrUpdater) {
            if ( typeof valOrUpdater === 'function' ) {
                this.jobsLeftInRound_ = valOrUpdater(this.jobsLeftInRound_)
            } else {
                this.jobsLeftInRound_ = valOrUpdater
            }
        }

        get refillsLeft() {
            return this.schedule.length
        }

        get buffs() {
            return this.buffs_
        }

        get selectedBuffs() {
            const res = []
            for ( const type in this.selectedBuffs_ ) {
                this.selectedBuffs_[type] && res.push(this.selectedBuffs_[type])
            }

            return res
        }

        set selectedBuffs(buff) {
            const type = buff.getBuffType()
            if ( this.selectedBuffs_[type]?.id === buff.id ) {
                this.selectedBuffs_[type] = null

                return
            }
            this.selectedBuffs_[type] = buff
            new UserMessage(`New ${type} buff selected`, UserMessage.TYPE_SUCCESS).show()
        }
    }


    class ActivityObserver {
        constructor() {
            const fiveMinutes = 5 * 60 * 1000
            this.activityCheckTimeout_ = null
            this.refreshCount = 0
            this._isEnabled = false
            this.timeOut_ = fiveMinutes
            this.selectedConsumables = null
        }

        saveTempCookies() {
            const selectedConsumables = Manager.consumables.reduce((acc, c) => {
                c.isSelected && acc.push(c.id)
                return acc
            }, [])
            const timeOut = this.timeOut_     
            const buffs = {
                travel: Manager.selectedBuffs_.travel?.id || null,
                character: Manager.selectedBuffs_.character?.id || null
            }
            
            const settings = {
                statistics: Vajda.statistics,
                selectedConsumables,
                buffs,
                timeOut,
                direction: Vajda.direction,
                currentJob: Vajda.currentJob,
                refreshCount: this.refreshCount + 1,
                schedule: Manager.schedule,
                jobsLeftInRound: Manager.jobsLeftInRound,
                travelSet: Vajda.travelSet,
                jobSet: Vajda.jobSet,
                healthSet: Vajda.healthSet
            }
            
            const expiracyDateTemporary = new Date()
            const hour = expiracyDateTemporary.getHours()
            expiracyDateTemporary.setHours(2,0,0)

            if ( hour > 2 )
                expiracyDateTemporary.setDate(expiracyDateTemporary.getDate() + 1)

            document.cookie = `vajdasession=${JSON.stringify(settings)};expires=${expiracyDateTemporary.toGMTString()};`
        }

        getTempCookies() {
            const cookies = document.cookie.split("=")
            for ( let i = 0; i < cookies.length; i++ ) {
                if ( cookies[i].includes("vajdasession") ) {
                    const { 
                        timeOut, schedule, selectedConsumables, buffs, jobsLeftInRound, refreshCount, ...vajdaSettings 
                    } = JSON.parse(cookies[i+1].split(";")[0])

                    this.selectedConsumables = selectedConsumables
                    this.buffs = buffs
                    this.refreshCount = refreshCount
                    this.timeOut_ = timeOut
                    this.isEnabled = true
                    Manager.jobsLeftInRound = jobsLeftInRound
                    Manager.schedule = schedule
                    Vajda = {...Vajda, ...vajdaSettings, isRunning: true}
                    document.cookie = 'vajdasession=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;'
                    return true
                }
            }
            
            return false
        }

        restartSession() {
            if ( Vajda.currentState === 2 ) {
                this.start()
                return
            }

            this.saveTempCookies()
            location.reload()
        }

        resumeSession() {
            Vajda.findAllConsumables()
            if ( !this.getTempCookies() ) return
            Vajda.createWindow(false)
            Vajda.run()
        }

        start(forceStart = false) {
            if ( !this.isEnabled && !forceStart ) return

            clearTimeout(this.activityCheckTimeout_)
            this.activityCheckTimeout_ = setTimeout(this.restartSession.bind(this), this.timeOut_)
        }

        stop() {
            this.isEnabled = false
            if ( this.activityCheckTimeout_ !== null )
                clearTimeout(this.activityCheckTimeout_)
            this.activityCheckTimeout_ = null
        }

        getTimeOut(convertToMinutes) {
            if ( convertToMinutes ) {
                return this.timeOut_ / 1000 / 60
            }
            return this.timeOut_
        }

        get isEnabled() {
            return this._isEnabled
        }

        set isEnabled(valOrUpdater) {
            if ( typeof valOrUpdater === 'function' ) {
                this._isEnabled = valOrUpdater(this._isEnabled)
            } else {
                this._isEnabled = valOrUpdater
            }
        }

        set timeOut(val) {
            this.timeOut_ = Math.max(Number(val) * 60 * 1000, 2.5 * 60 * 1000)
        }
    }

    window.Manager = new ConsumablesManager()
    window.Observer = new ActivityObserver()


    Vajda = {
        addedJobs: [],
        allJobs: [],
        addedJobTablePosition: {
            content: "0px",
            scrollbar:"0px"
        },
        consumableSelection: {
            energy: false,
            motivation: false,
            health: false,
            hideBuffs: false
        },
        consumableTablePosition: {
            content: '0px',
            scrollbar: '0px'
        },
        consumableUsed: [],
        currentJob: {
            job: 0,
            direction: true
        },
        currentState: 0,
        healthSet: -1,
        language: "",
        isLoaded: false,
        isRunning: false,
        jobFilter: {
            filterOnlySilver: false,
            filterNoSilver: false,
            filterCenterJobs: false,
            filterJob: ""
        },
        jobSet: -1,
        jobsLoaded: false,
        jobTablePosition: {
            content: "0px",
            scrollbar: "0px"
        },
        lastJobStartTime: undefined,
        states: [
            'Idle',
            'Running',
            'Waiting for a consumable cooldown',
            'Calculating optimal route'
        ],
        statistics: {
            sessionJobsCount: 0,
            sessionXpCount: 0,
            sessionMoneyCount: 0,
            totalJobsCount: 0,
            totalXpCount: 0,
            totalMoneyCount: 0
        },
        selectedSet: 0,
        sets: null,
        settings: {
            addEnergy: false,
            addMotivation: false,
            addHealth: false,
            healthStop: 10,
            setWearDelay: 5,
            jobDelayMin: 0,
            jobDelayMax: 0,
            addDeposit: {
                isEnabled: false,
                limit: NaN
            }
        },
        sortJobTableXp:0,
        sortJobTableDistance:0,
        travelSet: -1,
        regenerationSet: -1,
        window: null,
        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:"Energie",
                energyText:"Zvýšenie energie:",
                motivation:"Pracovnej motivácie",
                motivationText:"Zvýšenie pracovnej motivácie:",
                health: "Bonus bodov zdravia",
                healthText:"Bonus bodov zdravia:",
                speedText: "Rýchlosť",
                experience: "skúseností",
                experienceText: "Skúseností z práce, duelov a boja o pevnosť",
                money: "peňazí",
                moneyText: "Peňazí z práce a duelov",
                luck: "šťastie",
                luckText: "Vylepšené šťastie",
                drop: "produktu",
                dropText: "Vylepšenie šance na získanie produktu"
            },
            "cs_CZ":{
                energy:"Energie",
                energyText:"Zvýšení energie:",
                motivation:"Pracovní motivace",
                motivationText:"Zvýšení pracovní motivace:",
                health: "Bonus zdraví",
                healthText:"Bonus zdraví:",
                speedText: "Rychlost",
                experience: "zkušeností",
                experienceText: "zkušeností z prací, duelů a bitev o pevnost",
                money: "prací",
                moneyText: "$ z prací a duelů",
                luck: "štěstí",
                luckText: "Vyšší šance na štěstí",
                drop: "produktu",
                dropText: "Vylepšené šance nálezu produktu"
            },
            "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 mărită:",
                energyText:"Energie mărită:",
                motivation:"Creştere a motivaţiei de muncă:",
                motivationText:"Creştere a motivaţiei de muncă:",
                health: "Puncte de viaţă:",
                healthText:"Puncte de viaţă:"
            }
         }
    }


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

    Vajda.RNG = function(min, max) {
        let minN = Math.min(min, max)
        let maxN = Math.max(min, max)

        let number =  Math.floor((minN + Math.random() * (maxN - minN + 1)))
        return number
    }


    Vajda.isAllowedToDepositMoney = function() {
        const hasMoreMoneyThanLimit = Vajda.settings.addDeposit.limit <= Character.money
        const hasHomeTown = Character.homeTown.town_id !== 0

        if ( hasHomeTown == false ) {
            new UserMessage("You don't have a home town", UserMessage.TYPE_HINT).show()
            return false
        }
        
        return Vajda.settings.addDeposit.isEnabled && hasMoreMoneyThanLimit
    }


    Vajda.getCookies = function() {
        const cookies = document.cookie.split("=")
        for ( let i = 0; i < cookies.length; i++ ) {
            if ( cookies[i].includes("vajdatemporary") ) {
                const obj = cookies[i+1].split(";")
                const tempObject = JSON.parse(obj[0])
                const tmpAddedJobs = tempObject.addedJobs

                for ( const job of tmpAddedJobs ) {
                    const newJob = new Job(job.x, job.y, job.id, job.groupId)
                        newJob
                            .setIsSilver(job.isSilver)
                            .setExperience(job.experience)
                            .setMoney(job.money)
                            .setMotivation(job.motivation)
                            .setStopMotivation(job.stopMotivation)   
                            .setSet(job.set)
                        Vajda.addedJobs.push(newJob)
                    }

                Vajda.travelSet = tempObject.travelSet
                Vajda.jobSet = tempObject.jobSet
                Vajda.healthSet = tempObject.healthSet
                Vajda.currentJob = tempObject.currentJob
                Vajda.setSetForAllJobs()
            }
            if ( cookies[i].includes("vajdapermanent") ) {
                const obj = cookies[i+1].split(";")
                const permanentObject = JSON.parse(obj[0])
                const { isOptimized, isEnabled, timeOut, ...settings } = permanentObject.settings

                Manager.isOptimized = !!isOptimized
                Observer.isEnabled = !!isEnabled
                Observer.timeOut = timeOut || 5
                Vajda.settings = settings
                if ( !settings.addDeposit ) {
                    Vajda.settings.addDeposit = {
                        isEnabled: false,
                        limit: NaN
                    }
                }
                Vajda.statistics.totalJobsCount = Math.floor(permanentObject.totalJobs)
                Vajda.statistics.totalXpCount = permanentObject.totalXp
                Vajda.statistics.totalMoneyCount = permanentObject.totalMoney
            }
        }
    }

    Vajda.setCookies = function() {
        let expiracyDateTemporary = new Date()
        let hour = expiracyDateTemporary.getHours()
        expiracyDateTemporary.setHours(2,0,0)
        if ( hour > 2 )
            expiracyDateTemporary.setDate(expiracyDateTemporary.getDate() + 1)

        const addedJobs = Vajda.addedJobs.map(j => ({
            x: j.x,
            y: j.y,
            id: j.id,
            groupId: j.groupId,
            isSilver: j.isSilver,
            experience: j.experience,
            money: j.money,
            motivation: j.motivation,
            stopMotivation: j.stopMotivation,
            set: j.set
        }))
        
        let temporaryObject = {
            addedJobs,
            travelSet: Vajda.travelSet,
            jobSet: Vajda.jobSet,
            healthSet: Vajda.healthSet,
            currentJob: Vajda.currentJob
        }
        let expiracyDatePernament = new Date()
        expiracyDatePernament.setDate(expiracyDatePernament.getDate() + 360000)
        let pernamentObject = {
            settings: {...Vajda.settings, isOptimized: Manager.isOptimized, isEnabled: Observer.isEnabled, timeOut: Observer.getTimeOut(true)},
            totalJobs: Vajda.statistics.totalJobsCount,
            totalXp: Vajda.statistics.totalXpCount,
            totalMoney: Vajda.statistics.totalMoneyCount
        }
        const jsonTemporary = JSON.stringify(temporaryObject)
        const jsonPernament = JSON.stringify(pernamentObject)
        document.cookie = `vajdatemporary=${jsonTemporary};expires=${expiracyDateTemporary.toGMTString()};`
        document.cookie = `vajdapermanent=${jsonPernament};expires=${expiracyDatePernament.toGMTString()};`
    }

    
    Vajda.shouldEquipHealthSet = function(consumable) {
        if ( !consumable.hasCooldown() ) {
            return false
        }

        if ( Manager.isOptimized ) {
            return false
        }

        return consumable.health > 0 && Vajda.healthSet > -1
    }


    Vajda.parseConsumableBonuses = function(bonuses) {
        let getBonus = (text,type) => {
            switch(type) {
                case 0:
                    text = text.replace(Vajda.searchKeys[Vajda.language].energyText,"")
                    break
                case 1:
                    text = text.replace(Vajda.searchKeys[Vajda.language].motivationText,"")
                    break
                case 2:
                    text = text.replace(Vajda.searchKeys[Vajda.language].healthText,"")
                    break
            }
            text = text.slice(1)
            text = text.replace("%","")
            return parseInt(text)
        }
        let result = Array(3).fill(0)
        for ( let i = 0 ; i < bonuses.length; i++ ) {
            let type = -1
            if ( bonuses[i].includes(Vajda.searchKeys[Vajda.language].energyText) ) {
                type = 0
            }else if ( bonuses[i].includes(Vajda.searchKeys[Vajda.language].motivationText) ) {
                type = 1
            }else if ( bonuses[i].includes(Vajda.searchKeys[Vajda.language].healthText) ) {
                type = 2
            }
            if ( type !=-1 )
            result[type] = getBonus(bonuses[i],type)

        }
        return result
    }

    Vajda.findAllConsumables = function() {
        if ( Vajda.searchKeys[Vajda.language] === undefined ) return
        const energyConsumables = Bag.search(Vajda.searchKeys[Vajda.language].energy)
        for ( const consumable of energyConsumables ) {
            Manager.addNewConsumable(consumable)
        }

        const motivationConsumables = Bag.search(Vajda.searchKeys[Vajda.language].motivation)
        for ( const consumable of motivationConsumables ) {
            Manager.addNewConsumable(consumable)
        }

        const healthConsumables = Bag.search(Vajda.searchKeys[Vajda.language].health)
        for ( const consumable of healthConsumables ) {
            Manager.addNewConsumable(consumable)
        }

        const speedConsumables = Bag.search(Vajda.searchKeys[Vajda.language].speedText)
        for ( const consumable of speedConsumables ) {
            if ( consumable.obj.usetype !== 'none' ) {
                Manager.addNewConsumable(consumable)
            }
        }

        const luckConsumables = Bag.search(Vajda.searchKeys[Vajda.language].luck)
        for ( const consumable of luckConsumables ) {
            if ( consumable.obj.usetype !== 'none' ) {
                Manager.addNewConsumable(consumable)
            }
        }

        const experienceConsumables = Bag.search(Vajda.searchKeys[Vajda.language].experience)
        for ( const consumable of experienceConsumables ) {
            if ( consumable.obj.usetype !== 'none' ) {
                Manager.addNewConsumable(consumable)
            }
        }

        const moneyConsumables = Bag.search(Vajda.searchKeys[Vajda.language].money)
        for ( const consumable of moneyConsumables ) {
            if ( consumable.obj.usetype !== 'none' ) {
                Manager.addNewConsumable(consumable)
            }
        }

        const dropConsumables = Bag.search(Vajda.searchKeys[Vajda.language].drop)
        for ( const consumable of dropConsumables ) {
            if ( consumable.obj.usetype !== 'none' ) {
                Manager.addNewConsumable(consumable)
            }
        }

        Observer.selectedConsumables?.forEach(id => {
            Manager.consumables.find(c => c.id === id).setIsSelected(true)
        })

        for ( const type in Observer.buffs ) {
            Manager.selectedBuffs_[type] = Manager.buffs_.find(b => b.id === Observer.buffs[type]) || null
        }
        Observer.selectedConsumables = null
        Observer.buffs = null
    }

    Vajda.filterConsumables = function(energy, motivation, health, hideBuffs) {
        const result = hideBuffs ? [] : [...Manager.buffs] 

        for ( const consumable of Manager.consumables ) {
            if ( energy && consumable.energy === 0 ) {
                continue
            }

            if ( motivation && consumable.motivation === 0 ) {
                continue
            }

            if ( health && consumable.health === 0 ) {
                continue
            }
            result.push(consumable)
        }

        return result
    }

    Vajda.changeConsumableSelection = function(id, isSelected) {
        Manager.consumables.find(c => c.id === id)?.setIsSelected(isSelected)
    }

    Vajda.changeSelectionAllConsumables = function(selected) {
        for ( const consumable of Manager.consumables ) {
            consumable.setIsSelected(selected)
        }
    }

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

                for ( let townNumber in r.towns ) {
                    if ( r.towns[townNumber].town_id === Character.homeTown.town_id ) {
                        Vajda.homeTown = r.towns[townNumber]
                        break
                    }
                }


                for ( let jobGroup in r.job_groups ) {
                    const groupId = parseInt(jobGroup)
                    let group = r.job_groups[jobGroup]
                    let jobsGroup = JobList.getJobsByGroupId(groupId)

                    for ( let tilecoord = 0; tilecoord < group.length; tilecoord++ ) {
                        let xCoord = Math.floor(group[tilecoord][0]/GameMap.tileSize)
                        let 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 ( let i = 0; i < jobsGroup.length; i++ ) {
                            jobs.push(new Job(group[tilecoord][0],group[tilecoord][1],jobsGroup[i].id, groupId))
                        }
                    }
                }
                let toLoad = tiles.length
                let loaded = 0
                for ( let blocks = 0; blocks < tiles.length; blocks++ ) {
                    GameMap.Data.Loader.load(tiles[blocks], function() {
                        loaded++
                        if ( loaded === toLoad ) {
                            Vajda.jobsLoaded = true
                            Vajda.allJobs = jobs
                            Vajda.findAllConsumables()
                            Vajda.createWindow()
                        }
                    })
                }
            })
        } else {
            Vajda.findAllConsumables()
            Vajda.createWindow()
        }
    }

    Vajda.loadJobData = function(callback) {
        Ajax.get('work','index', {}, function(r) {
            if( r.error ) {
                console.log(r.error)
                return
            }
            JobsModel.initJobs(r.jobs)
            callback()
        })
    }

    Vajda.getJobSet = function(x, y, id) {
        const job = Vajda.findAddedJob(x, y, id)
        if ( job !== null )
            return job.set
    }

    Vajda.setJobSet = function(x,y,id,set) {
        const job = Vajda.findAddedJob(x, y, id)
        if ( job !== null)
            return job.setSet(set)
    }

    Vajda.findAddedJob = function(x, y, id) {
        for ( const job of Vajda.addedJobs ) {
            if ( job.x === x && job.y === y && job.id === id ) {
                return job
            }
        }
        return null
    }

    Vajda.loadSets = async function(callback) {
        Ajax.remoteCallMode('inventory', 'show_equip', {}, function(r) {
            Vajda.sets = r.data
            if ( callback !== undefined ) 
                callback()
        })
    }

    Vajda.loadJobMotivation = function(index, callback) {
        const job = Vajda.addedJobs.at(index)
        Ajax.get('job', 'job', {jobId: job.id, x: job.x, y: job.y}, function(r) {
            if ( callback !== undefined )
                callback(r.motivation*100)
        })
    }

    Vajda.loadLanguage = function() {
        Ajax.remoteCall("settings", "settings", {}, function(resp) {
            Vajda.language = resp.lang.account.key
        })
    }

    Vajda.getConsumableIcon = function(src) {
        return `<div><img src='${src}'></div>`
    }

    Vajda.getItemImage = function(id) {
        return ItemManager.get(id).wear_image
    }

    Vajda.createComboxJobSets = function(x, y, id) {
        let combobox = new west.gui.Combobox()
        Vajda.addComboboxItems(combobox)
        combobox = combobox.select(Vajda.getJobSet(x, y, id))
        combobox.setWidth(60)
        combobox.addListener(function(value) {
            Vajda.setJobSet(x, y, id, value)
            Vajda.selectTab("chosenJobs")
        });
        return combobox.getMainDiv()
    }

    Vajda.addComboboxItems = function(combobox) {
        combobox.addItem(-1, "None")
        for ( let i = 0 ; i < Vajda.sets.length; i++) {
            combobox.addItem(i.toString(), Vajda.sets[i].name)
        }
    }

    Vajda.updateJobDistances = function() {
        for ( let i = 0; i < Vajda.allJobs.length; i++ ) {
            Vajda.allJobs[i].calculateDistance()
        }
    }
    
    Vajda.findJobData = function(job) {
        for ( let i = 0 ;i < JobsModel.Jobs.length; i++ ) {
            if ( JobsModel.Jobs[i].id === job.id ) {
                return JobsModel.Jobs[i]
            }
        }
    }

    Vajda.findJob = function(x, y, id) {
        for ( let i = 0; i < Vajda.allJobs.length; i++ ) {
            if ( Vajda.allJobs[i].id === id && Vajda.allJobs[i].x === x && Vajda.allJobs[i].y === y ) {
                return Vajda.allJobs[i]
            }
        }
    }

    Vajda.addJob = function(x, y, id) {
        if ( !Vajda.checkIfJobAdded(id) ) {
            Vajda.addedJobs.push(Vajda.findJob(x, y, id))
        }
    }

    Vajda.removeJob = function(x, y, id) {
        for ( let i = 0; i < Vajda.addedJobs.length; i++ ) {
            if ( Vajda.addedJobs[i].id === id && Vajda.addedJobs[i].x === x && Vajda.addedJobs[i].y === y) {
                Vajda.addedJobs.splice(i,1)
                Vajda.consolidePosition(i)
                break
            }
        }
    }

    Vajda.parseJobData = function(jobs) {
        for ( let job = 0; job < jobs.length; job++ ) {
            let currentJob = jobs[job]
            let data = Vajda.findJobData(currentJob)
            let xp = data.basis.short.experience
            let money = data.basis.short.money
            currentJob.setMotivation(data.jobmotivation*100)
            if ( currentJob.isSilver ) {
                xp = Math.ceil(1.5*xp)
                money = Math.ceil(1.5*money)
            }
            currentJob.setExperience(xp)
            currentJob.setMoney(money)
        }
    }

    Vajda.getJobName = function(id) {
        return JobList.getJobById(id).name
    }

    Vajda.getJobIcon = function(isSilver, id, x, y) {
        return `
            <div class="job" style="left: 0; top: 0; position: relative;">
                <div onclick="JobWindow.open(${id}, ${x}, ${y})" class="featured ${isSilver && 'silver'}"></div>
                <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" alt='job_image'>
            </div>
        `
    }

    Vajda.checkIfJobAdded = function(id) {
        for ( const job of Vajda.addedJobs ) {
            if ( job.id === id ) {
                return true
            }
        }

        return false
    }

    Vajda.isJobSilver = function(x, y, id) {
        const key = `${x}-${y}`
        const jobData = GameMap.JobHandler.Featured[key]
        if ( jobData === undefined || jobData[id] === undefined ) {
            return false
        } else {
            return jobData[id].silver
        }
    }

    Vajda.compareUniqueJobs = function(job, jobs){
        for ( let i = 0 ; i < jobs.length; i++ ) {
            if ( jobs[i].id === job.id ) {
                if ( job.isSilver && !jobs[i].isSilver || (job.isSilver === jobs[i].isSilver && job.distance < jobs[i].distance) ) {
                    const j = jobs.at(i)
                    if ( !Vajda.isJobSilver(j.x, j.y, j.id) )
                        jobs.splice(i,1)
                    jobs.push(job)
                }
                return
            }
        }
        jobs.push(job)
    }

    Vajda.getAllUniqueJobs = function() {
        Vajda.updateJobDistances()
        let jobs = []
        for ( let i = 0 ; i < Vajda.allJobs.length; i++ ) {
            const currentJob = Vajda.allJobs.at(i)
            if ( Vajda.jobFilter.filterJob !== "" ) {
                if ( !Vajda.getJobName(currentJob.id).toLowerCase().includes(Vajda.jobFilter.filterJob)) {
                    continue
                }
            }

            if ( Vajda.checkIfJobAdded(currentJob.id) ) {
                continue
            }
            
            let isSilver = Vajda.isJobSilver(currentJob.x, currentJob.y, currentJob.id)
            currentJob.isSilver = isSilver
            currentJob.calculateDistance()
            
            if ( isSilver && Vajda.jobFilter.filterNoSilver ) {
                continue
            }

            if ( !isSilver && Vajda.jobFilter.filterOnlySilver ) {
                continue
            }

            if ( Vajda.jobFilter.filterCenterJobs && currentJob.id < 131 ) {
                continue
            }

            Vajda.compareUniqueJobs(currentJob, jobs)
        }

        Vajda.parseJobData(jobs)

        let 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
            }

            let a1 = a.experience
            let b1 = b.experience
            return (a1 > b1) ? -1 :(a1 < b1) ? 1 :0
        }

        let 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
            }

            let a1 = a.experience
            let b1 = b.experience
            return (a1 > b1) ? 1 :(a1 < b1) ? -1 :0
        }

        let 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
            }

            let a1 = a.distance
            let b1 = b.distance
            return (a1 > b1) ? -1 :(a1 < b1) ? 1 :0
        }

        let 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
            }

            let a1 = a.distance
            let b1 = b.distance
            return (a1 > b1) ? 1 :(a1 < b1) ? -1 :0
        }

        if ( Vajda.sortJobTableXp === 1 ) {
            jobs.sort(experienceSort)
        }

        if ( Vajda.sortJobTableXp === -1 ) {
            jobs.sort(reverseExperienceSort)
        }

        if ( Vajda.sortJobTableDistance === 1 ) {
            jobs.sort(distanceSort)
        }

        if ( Vajda.sortJobTableDistance === -1 ) {
            jobs.sort(reverseDistanceSort)
        }

        return jobs
    }

    Vajda.parseStopMotivation = function() {
        for ( let i = 0; i < Vajda.addedJobs.length; i++ ) {
            let stopMotivation = $(".vajda-window #x-" + Vajda.addedJobs[i].x + "y-" + Vajda.addedJobs[i].y + "id-" + Vajda.addedJobs[i].id).prop("value")
            if ( Vajda.isNumber(stopMotivation) ) {
                Vajda.addedJobs[i].setStopMotivation(parseInt(stopMotivation))
            } else {
                return false
            }
        }
        return true
    }

    Vajda.setSetForAllJobs = function() {
        for ( let i = 0; i < Vajda.addedJobs.length; i++ ) {
            Vajda.addedJobs[i].setSet(Vajda.jobSet)
        }
    }

    Vajda.consolidePosition = function(removeIndex) {
        if ( removeIndex <= Vajda.currentJob.job && Vajda.currentJob.job > 0 ) {
            Vajda.currentJob.job--
        }
        if ( Vajda.addedJobs.length === 1 ) {
            Vajda.currentJob.direction = true
        }
    }

    Vajda.createDistanceMatrix = function() {
        const matrix = new Array(Vajda.addedJobs.length)

        for ( let i = 0; i < matrix.length; i++ ) {
            matrix[i] = Vajda.addedJobs[i].calculateJobDistances()
        }

        return matrix
    }

    Vajda.countSetBits = function(n) {
        let count = 0
        while (n) {
            n &= n - 1
            count++
        }
        return count
    }

    Vajda.heldKarpSymmetric = function(distances, startJob) {
        const n = distances.length
        const memo = Array(1 << n).fill().map(() => Array(n).fill({ cost: Infinity, path: [] }))
        memo[1 << startJob][startJob] = { cost: 0, path: [startJob] }
    
        for ( let subsetSize = 2; subsetSize <= n; subsetSize++ ) {
            for ( let subset = 0; subset < (1 << n); subset++ ) {
                if ( Vajda.countSetBits(subset) === subsetSize && (subset & (1 << startJob)) ) {
                    for ( let end = 0; end < n; end++ ) {
                        if ( (subset & (1 << end)) !== 0 ) {
                            for ( let prevEnd = 0; prevEnd < n; prevEnd++ ) {
                                if ( prevEnd !== end && (subset & (1 << prevEnd)) !== 0 ) {
                                    const newCost = memo[subset ^ (1 << end)][prevEnd].cost + distances[prevEnd][end]
                                    if (newCost < memo[subset][end].cost) {
                                        memo[subset][end] = { cost: newCost, path: memo[subset ^ (1 << end)][prevEnd].path.concat([end]) }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    
        let minCost = Infinity
        let minPath = []
        for ( let end = 0; end < n; end++ ) {
            if ( end !== startJob && memo[(1 << n) - 1][end].cost < minCost ) {
                minCost = memo[(1 << n) - 1][end].cost
                minPath = memo[(1 << n) - 1][end].path
            }
        }

        return { cost: minCost, path: minPath }
    }


    Vajda.setEntryPoint = function(route) {
        const firstJob = route.at(0)
        const lastJob = route.at(-1)

        if ( firstJob.calculateDistance() > lastJob.calculateDistance() )
            route.reverse()

        //i could in theory make vajda start with the job nearest to current character position but cba
    }

    Vajda.getOptimalRoute = function(distanceMatrix) {
        const jobsCount = distanceMatrix.length

        if ( jobsCount === 1 ) 
            return {
                cost: 0,
                path: [0]
            }

        const routes = []
        for ( let startJob = 0; startJob < jobsCount; startJob++ ) {
            const { cost, path } = Vajda.heldKarpSymmetric(distanceMatrix, startJob)
            routes.push({ cost, path })
        }

        return routes.reduce(function(prev, curr) {
            return prev.cost < curr.cost ? prev : curr
        })
    }

    Vajda.createRoute = function() {
        Vajda.currentJob = {
            job: 0,
            direction: true
        }

        const distanceMatrix = Vajda.createDistanceMatrix()
        const optimalRoute = Vajda.getOptimalRoute(distanceMatrix)

        const addedJobsOrder = []
        for ( const index of optimalRoute.path ) {
            addedJobsOrder.push(Vajda.addedJobs.at(index))
        }

        Vajda.setEntryPoint(addedJobsOrder)

        Vajda.addedJobs = addedJobsOrder
        Vajda.selectTab("chosenJobs")
    }

    Vajda.createSetGui = function() {
        if ( Vajda.sets.length === 0 ) {
            return $(`<span style='font-size: 20px'>No sets available</span>`)
        }
        let htmlSkel = $(`
            <div id='vajda_sets_window' style='display: block; position: relative; width: 650px; height:430px'>
                <div id='vajda_sets_left' style='display: block; position: absolute; width: 250px; height: 430px; top:0px; left:0px'></div>
                <div id='vajda_sets_right' style='display: block; position: absolute; width:300px; height: 410px; top: 0px; left: 325px'></div>
            </div>`)
        let combobox = new west.gui.Combobox("combobox_sets")
        Vajda.addComboboxItems(combobox)
        combobox = combobox.select(Vajda.selectedSet)
        combobox.addListener(function(value) {
            Vajda.selectedSet = value
            Vajda.selectTab("sets")
        })
        let buttonSelectTravelSet = new west.gui.Button("Select travel set", function() {
            Vajda.travelSet = Vajda.selectedSet
            Vajda.selectTab("sets")
        })
        let buttonSelectJobSet = new west.gui.Button("Select job set", function() {
            Vajda.jobSet = Vajda.selectedSet
            Vajda.setSetForAllJobs()
            Vajda.selectTab("sets")
        })
        let buttonSelectHealthSet = new west.gui.Button("Select health set", function() {
            Vajda.healthSet = Vajda.selectedSet
            Vajda.selectTab("sets")
        })
        let buttonSelectRegenSet = new west.gui.Button("Select regeneration set", function() {
            Vajda.regenerationSet = Vajda.selectedSet
            Vajda.selectTab("sets")
        })
        let travelSetText = "None"

        if ( Vajda.travelSet != -1 ) {
            travelSetText = Vajda.sets[Vajda.travelSet].name
        }

        let jobSetText = "None"
        if ( Vajda.jobSet != -1 ) {
            jobSetText = Vajda.sets[Vajda.jobSet].name
        }

        let healthSetText = "None"
        if ( Vajda.healthSet != -1 ) {
            healthSetText = Vajda.sets[Vajda.healthSet].name
        }
        
        let regenSetText = "None"
        if ( Vajda.regenerationSet != -1 ) {
            regenSetText = Vajda.sets[Vajda.regenerationSet].name
        }

        const 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())
                .getMainDiv()
            )
         const 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>")
        let keys = ["head","body","pants","neck","right_arm","animal","yield","left_arm","belt","foot"]
        if ( Vajda.selectedSet !== -1 )
            Vajda.insertSetImages(right,keys)
        $("#vajda_sets_left",htmlSkel).append(left)
        $("#vajda_sets_right",htmlSkel).append(right)
        return htmlSkel
    }

    Vajda.getImageSkel = function() {
        return $("<img src=\''\>")
    }

    Vajda.insertSetImages = function(html,keys) {
        for ( let i = 0; i < keys.length; i++ ) {
            if ( Vajda.sets[Vajda.selectedSet][keys[i]] !== null ) {
                $(".wear_"+keys[i], html).append(Vajda.getImageSkel().attr("src", Vajda.getItemImage(Vajda.sets[Vajda.selectedSet][keys[i]])))
            }
        }
        return html
    }

    Vajda.addEventsHeader = function() {
        $(".vajda-window .jobXp").click(function() {
            if ( Vajda.sortJobTableXp === 0 ) {
                Vajda.sortJobTableXp = 1
            } else {
                ( Vajda.sortJobTableXp === 1 ) ? Vajda.sortJobTableXp = -1 : Vajda.sortJobTableXp = 1
            }
            Vajda.sortJobTableDistance = 0
            Vajda.selectTab("jobs")
        })
        $(".vajda-window .jobDistance").click(function() {
            if ( Vajda.sortJobTableDistance === 0 ) {
                Vajda.sortJobTableDistance = 1
            } else {
                ( Vajda.sortJobTableDistance === 1 ) ? Vajda.sortJobTableDistance = -1 : Vajda.sortJobTableDistance = 1
            }
            Vajda.sortJobTableXp = 0
            Vajda.selectTab("jobs")
        })
    }

    Vajda.isInHomeTown = function() {
        const homeTown = Character.homeTown
        return GameMap.calcWayTime(Character.position,{x: homeTown.x, y: homeTown.y}) == 0
    }

    Vajda.addDeposit = async function(townId) {
        const amount = Character.money
        await Ajax.remoteCall("building_bank", "deposit", {
            town_id: townId,
            amount: amount
        }, function(data) {
            if (data.error == false) {
                BankWindow.Balance.Mupdate(data)
                Character.setDeposit(data.deposit)
                Character.setMoney(data.own_money)
                //new UserMessage(s(sextext('Vložil si $ %1 a zaplatil si poplatok $ %2', 'Vložila si $ %1 a zaplatila si poplatok $ %2', Character.charSex), amount, data.fee),UserMessage.TYPE_SUCCESS).show()
            } else
                new UserMessage(data.msg,UserMessage.TYPE_ERROR).show()
        }, BankWindow)
    }
    

    Vajda.goDepositMoney = async function() {
        const townId = Character.homeTown.town_id
        if ( !townId ) return

        await Vajda.equipSet(Vajda.travelSet)
        TaskQueue.add(new TaskWalk(townId, 'town'))

        while(true) {
            if ( Vajda.isInHomeTown() ) {
                break
            }

            if( !Vajda.isRunning ) {
                break
            }

            await sleep(1000)
        }

        await Vajda.addDeposit(townId)
        $('.tw2gui_dialog_framefix').remove()
    }

    Vajda.updateJobsMotivationOnRefill = function(val) {
        return Vajda.addedJobs.map(job => job.setMotivation(p => p + val).motivation - job.stopMotivation)
    }


    Vajda.checkMotivation = async function(index, result, callback) {
        let check = function(index, result) {
            Vajda.loadJobMotivation(index, function(motivation) {
                Vajda.addedJobs.at(index).setMotivation(motivation)
                result.push(motivation)
                if ( index + 1 < Vajda.addedJobs.length ) {
                    check(++index, result)
                } else if( index + 1 === Vajda.addedJobs.length ) {
                    callback(result)
                    return
                }
            })
        }
        check(index, result)
    }

    Vajda.isMotivationAbove = function(result) {
        for ( let i = 0; i < result.length; i++ ) {
            if ( result.at(i) > Vajda.addedJobs.at(i).stopMotivation ) {
                return true
            }
        }
        return false
    }

    Vajda.isStopMotivationZero = function() {
        for ( let i = 0; i < Vajda.addedJobs.length; i++ ) {
            if( Vajda.addedJobs[i].stopMotivation === 0 ) {
                return true
            }
        }
        return false
    }

    Vajda.isHealthBelowLimit = function() {
        if ( Vajda.settings.healthStop >= ((Character.health/Character.maxHealth) * 100) ) {
            return true
        }
        return false
    }

    Vajda.changeJob = function() {
        Vajda.currentJob.direction ? Vajda.currentJob.job++ : Vajda.currentJob.job--;
        if ( Vajda.currentJob.job === Vajda.addedJobs.length ) {
            Vajda.currentJob.job--
            Vajda.currentJob.direction = false
        } else if ( Vajda.currentJob.job < 0 ) {
            Vajda.currentJob.job++
            Vajda.currentJob.direction = true
        }
        Vajda.setCookies()
        Vajda.run()
    }

    Vajda.searchBest = function(skills, jobId, onlyWearable = true) {
        if (!Bag.loaded) {
            EventHandler.listen('inventory_loaded', function() {
                Vajda.searchBest(skills, jobId, onlyWearable)
                return EventHandler.ONE_TIME_EVENT
            })
            return
        }
        let set = west.item.Calculator.getBestSet(skills, jobId), items = set && set.getItems() || [], invItems = Bag.getItemsByItemIds(items), result = [], i, invItem, wearItem
        for (i = 0; i < invItems.length; i++) {
            invItem = invItems[i]
            wearItem = Wear.get(invItem.getType())
            if (!wearItem || (wearItem && (wearItem.getItemBaseId() !== invItem.getItemBaseId() || wearItem.getItemLevel() < invItem.getItemLevel()))) {
                result.push(invItem)
            }
        }
        return result.map(item => item.obj.getId())
    }

    Vajda.getBestGear = function(jobid) {
        let modelId = function(jobid) {
            for ( let i = 0; i < JobsModel.Jobs.length; i++ ) {
                if ( JobsModel.Jobs[i].id === jobid )
                    return i
            }
            return -1
        }
        const result = west.item.Calculator.getBestSet(JobsModel.Jobs[modelId(jobid)].get('skills'), jobid)
        const bestItems = result && result.getItems()
        return bestItems
    }

    Vajda.isWearing = function(itemId) {
        if ( Wear.wear[ItemManager.get(itemId).type] === undefined) return false
        return Wear.wear[ItemManager.get(itemId).type].obj.item_id == itemId
    }

    Vajda.isGearEquiped = async function(items) {
        for ( const itemId of items ) {
            if ( !Vajda.isWearing(itemId) ) {
                return false
            }
        }

        return true
    }

    Vajda.equipBestGear = async function(job) {
        const bestGear = job.getBestEquipment()

        for ( const itemId of bestGear ) {
            Wear.carry(Bag.getItemByItemId(itemId))
        }

        while (true) {
            const isFinished = await Vajda.isGearEquiped(bestGear)
            if ( isFinished ) break
            await sleep(50)
        }
        return Promise.resolve(true)
    }

    Vajda.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
    }

    Vajda.equipSet = async function(set) {
        if ( set === -1 ) return true
        EquipManager.switchEquip(Vajda.sets[set].equip_manager_id)
        while ( true ) {
            let isFinished = await Vajda.isGearEquiped(Vajda.getSetItemArray(Vajda.sets[set]))
            if ( isFinished ) break
            await sleep(50)
        }
        return Promise.resolve(true)
    }

    Vajda.cancelJobs = function() {
        if ( TaskQueue.queue.length > 0 )
            TaskQueue.cancelAll()
    }


    //https://prnt.sc/KAgbLNqB4zK6
    Vajda.runJob = async function(jobIndex, jobCount) {
        Vajda.statistics.sessionJobsCount += jobCount
        Vajda.statistics.totalJobsCount += jobCount
        const oldXp = Character.experience
        const oldMoney = Character.money
        const job = Vajda.addedJobs.at(jobIndex)
        await Vajda.equipBestGear(job)
        for ( let i = 0; i < jobCount; i++ ) {
            JobWindow.startJob(job.id, job.x, job.y, 15)
        }
        Observer.start()
        await sleep(Vajda.settings.setWearDelay * 1000)
        Vajda.equipSet(job.set)
        Manager.useBuff('character')
        while (true) {
            if ( TaskQueue.queue.length === 0 ) {
                Vajda.updateStatistics(oldXp, oldMoney)
                Vajda.setCookies()
                Vajda.prepareJobRun(jobIndex)
                return
            }
            if ( !Vajda.isRunning || Vajda.isHealthBelowLimit() ) {
                break
            }
            await sleep(1000)
        }
        Vajda.statistics.sessionJobsCount -= TaskQueue.queue.length
        Vajda.statistics.totalJobsCount -= TaskQueue.queue.length
        Vajda.updateStatistics(oldXp, oldMoney)
        Vajda.setCookies()
        Vajda.cancelJobs()

        if ( Vajda.isRunning && Vajda.isHealthBelowLimit() ) {
            await sleep(2000)
            Vajda.run()
        
        }
    }

    Vajda.walkToJob = async function(index) {
        const job = Vajda.addedJobs.at(index)
        const jobGroup = JobList.getJobsByGroupId(job.groupId)

        const jobToWalkTo = jobGroup.map(j => 
            j.id
        ).map( id => {
            const j = JobsModel.getById(id)

            return {
                id: id,
                workpoints: j.workpoints,   //required LP
                jobpoints: j.jobpoints      //character has LP
            }
        }).reduce((prev, curr) => {
            return prev.workpoints < curr.workpoints ? prev : curr
        })

        await Manager.useBuff('travel')

        if ( Vajda.isAllowedToDepositMoney() ) {
            Observer.start()
            await Vajda.goDepositMoney()
        }

        JobWindow.startJob(jobToWalkTo.id, job.x, job.y, 15)
        Observer.start()

        while (true) {
            if ( GameMap.calcWayTime(Character.position, {x: job.x, y: job.y} ) === 0 ) {
                break
            }

            if ( !Vajda.isRunning ) {
                break
            }
            await sleep(1000)
        }
        Vajda.cancelJobs()
        if ( Vajda.isRunning )
            Vajda.prepareJobRun(index)
    }

    Vajda.prepareJobRun = async function(index) {
        const job = Vajda.addedJobs.at(index)
        setTimeout(function() {
            Vajda.loadJobMotivation(index, async function(motivation) {
                if ( Character.energy === 0 || Vajda.isHealthBelowLimit() ) {
                    Vajda.run()
                }   else if ( motivation <= job.stopMotivation && job.stopMotivation > 0 ) {
                    Vajda.checkMotivation(0, [], function(result) {
                        if ( Vajda.isMotivationAbove(result) ) {
                            Vajda.changeJob()
                        } else {
                            Vajda.run()
                        }
                    })
                } else if ( GameMap.calcWayTime(Character.position,{x: job.x, y: job.y}) === 0 ) {
                    let maxJobs = Premium.hasBonus('automation') ? 9 : 4
                    let numberOfJobs
                    if (job.stopMotivation !== 0 ) {
                        numberOfJobs = Math.min(Math.min(motivation - job.stopMotivation, Character.energy), maxJobs)
                    } else {
                        numberOfJobs = Math.min(Character.energy, maxJobs)
                    }
                    Vajda.runJob(index, Math.floor(numberOfJobs))
                    if ( Manager.isOptimized )
                        Manager.checkSchedule(numberOfJobs)
                } else {
                    await Vajda.equipSet(Vajda.travelSet)
                    Vajda.walkToJob(index)
                }
            })
        }, Vajda.RNG(Vajda.settings.jobDelayMin, Vajda.settings.jobDelayMax) * 1000)
    }

    Vajda.canAddMissing = function(result) {
        if ( !Vajda.settings.addMotivation && Vajda.jobsBelowMotivation(result) && !Vajda.isStopMotivationZero() ) {
            alert("Can't continue because of motivation")
            return false
        }

        if ( !Vajda.settings.addEnergy && Character.energy === 0 ) {
            alert("Can't continue because of energy")
            return false
        }

        if ( !Vajda.settings.addHealth && Vajda.isHealthBelowLimit() ) {
            alert("Can't continue because of health")
            return false
        }
        return true
    }

    Vajda.finishRun = function() {
        Vajda.currentState = 0
        Vajda.isRunning = false
        Vajda.selectTab("chosenJobs")
        alert("Finished")
    }

    Vajda.jobsBelowMotivation = function(result) {
        let count = 0
        for ( let i = 0; i < result.length; i++ ) {
            if ( result[i] <= Vajda.addedJobs[i].stopMotivation ) {
                count++
            }
        }
        return count
    }



    Vajda.averageMissingMotivation = function(result) {
        let motivation = 0
        for ( let i = 0; i < result.length; i++ ) {
            motivation += 100-result[i]
        }
        return motivation/result.length
    }


    Vajda.fillUp = async function(result) {
        const energyMissing = 100 - (Character.energy/Character.maxEnergy) * 100
        const motivationMissing = Vajda.jobsBelowMotivation(result)
        const averageMotivationMissing = Vajda.averageMissingMotivation(result)

        const consumableToUse = Manager.findProperConsumable(motivationMissing, energyMissing, averageMotivationMissing)
        

        if ( consumableToUse === null ) return false

        await Manager.useConsumableOrWaitForCooldown(consumableToUse, true)

        if ( Manager.isOptimized && consumableToUse.isCakeDecoration() ) {
            const updatedMotivation = Vajda.updateJobsMotivationOnRefill(consumableToUse.motivation)
            await Manager.createSchedule(updatedMotivation)
        }

        return true
    }

    Vajda.updateStatistics = function(oldXp, oldMoney) {
        const xpDiff = Character.experience - oldXp
        const moneyDiff = Character.money - oldMoney
        
        Vajda.statistics.sessionXpCount += xpDiff
        Vajda.statistics.totalXpCount += xpDiff

        if ( moneyDiff > 0 ) {      //spending money while vajda is running would make this a bit funky
            Vajda.statistics.sessionMoneyCount += moneyDiff
            Vajda.statistics.totalMoneyCount += moneyDiff
        }
    }

    Vajda.run = async function() {
        Vajda.checkMotivation(0, [], async function(result) {
            if ( ( Vajda.isMotivationAbove(result) || Vajda.isStopMotivationZero()) && Character.energy > 0 && !Vajda.isHealthBelowLimit() ) {
                Vajda.currentState = 1
                Vajda.selectTab("chosenJobs")
                Vajda.prepareJobRun(Vajda.currentJob.job)
            } else {
                if ( !Vajda.canAddMissing(result) ) {
                    Vajda.finishRun()
                } else {
                    let answer = await Vajda.fillUp(result)
                    if ( !answer ) {
                        Vajda.finishRun()
                    }
                }
            }
        })
    }

    Vajda.formatNumber = function(number) {
        if ( typeof number === 'number' ) {
            number = String(number)
        }

        const numberString = number.replace(/[ ,]/g, '')
    
        let formattedNumber = ''
        for ( let i = 0; i < numberString. length; i++ ) {
            if ( i > 0 && i % 3 === 0 ) {
                formattedNumber = ' ' + formattedNumber
            }

            formattedNumber = numberString[numberString.length - 1 - i] + formattedNumber
        }
        
        return formattedNumber
    }

    Vajda.createConsumablesTable = function() {
        let htmlSkel = $(`<div id='consumables_overview'></div>`)
        let 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='buff_consumables' style='position: absolute; top: 10px; left: 460px'></div>
            </div>`
        )

        let table = new west.gui.Table()
        let consumableList = Vajda.filterConsumables(Vajda.consumableSelection.energy, Vajda.consumableSelection.motivation, Vajda.consumableSelection.health, Vajda.consumableSelection.hideBuffs)
        table.addColumn("consumIcon","consumIcon").addColumn("consumCount","consumCount").addColumn("consumEnergy","consumEnergy").addColumn("consumMotivation","consumMotivation").addColumn("consumHealth","consumHealth").addColumn("consumBuffs", "consumBuffs").addColumn("consumSelected","consumSelected")
        table.appendToCell("head","consumIcon","Image").appendToCell("head","consumCount","Count").appendToCell("head","consumEnergy","Energy").appendToCell("head","consumMotivation","Motivation").appendToCell("head","consumHealth","Health").appendToCell("head", "consumBuffs", "Buffs").appendToCell("head","consumSelected","Use")
        for ( const consumable of consumableList ) {
            const checkbox = new west.gui.Checkbox()
            checkbox.setSelected(consumable.isSelected)
            checkbox.setId(consumable.id)
            checkbox.setCallback(function() {
                Vajda.consumableTablePosition.content = $(".vajda-window .tw2gui_scrollpane_clipper_contentpane").css("top")
                Vajda.consumableTablePosition.scrollbar = $(".vajda-window .tw2gui_scrollbar_pulley").css("top")
                if ( consumable instanceof Buff && !consumable.hasCooldown() ) {
                    Manager.selectedBuffs = consumable
                    Vajda.selectTab('consumables')
                    Vajda.setCookies()
                    return
                }
                Vajda.changeConsumableSelection(parseInt(this.divMain.attr("id")), this.isSelected())
                Vajda.selectTab("consumables")
                Vajda.setCookies()
            })
            table.appendRow().appendToCell(-1,"consumIcon", Vajda.getConsumableIcon(consumable.image)).appendToCell(-1,"consumCount",consumable.count).appendToCell(-1,"consumEnergy",consumable.energy).appendToCell(-1,"consumMotivation",consumable.motivation).appendToCell(-1,"consumHealth",consumable.health).appendToCell(-1, "consumBuffs", consumable.getBuffHTML()).appendToCell(-1,"consumSelected",checkbox.getMainDiv())
        }
        const buttonSelect = new west.gui.Button("Select all", function() {
            Vajda.changeSelectionAllConsumables(true)
            Vajda.selectTab("consumables")
            Vajda.setCookies()
        })
        const buttonDeselect = new west.gui.Button("Deselect all", function() {
            Vajda.changeSelectionAllConsumables(false)
            Vajda.selectTab("consumables")
            Vajda.setCookies()
        })
        table.appendToFooter("consumEnergy",buttonSelect.getMainDiv())
        table.appendToFooter("consumHealth",buttonDeselect.getMainDiv())
        htmlSkel.append(table.getMainDiv())
        const checkboxEnergyConsumes = new west.gui.Checkbox()
        checkboxEnergyConsumes.setLabel("Energy consumables")
        checkboxEnergyConsumes.setSelected(Vajda.consumableSelection.energy)
        checkboxEnergyConsumes.setCallback(function() {
            Vajda.consumableSelection.energy = this.isSelected()
            Vajda.selectTab("consumables")
        })
        const checkboxMotivationConsumes = new west.gui.Checkbox()
        checkboxMotivationConsumes.setLabel("Motivation consumables")
        checkboxMotivationConsumes.setSelected(this.consumableSelection.motivation)
        checkboxMotivationConsumes.setCallback(function() {
            Vajda.consumableSelection.motivation = this.isSelected()
            Vajda.selectTab("consumables")
        })
        const checkboxHealthConsumes = new west.gui.Checkbox()
        checkboxHealthConsumes.setLabel("Health consumables")
        checkboxHealthConsumes.setSelected(this.consumableSelection.health)
        checkboxHealthConsumes.setCallback(function() {
            Vajda.consumableSelection.health = this.isSelected()
            Vajda.selectTab("consumables")
        })
        const buffsFilter = new west.gui.Checkbox()
        buffsFilter.setLabel("Hide buffs")
        buffsFilter.setSelected(this.consumableSelection.hideBuffs)
        buffsFilter.setCallback(function() {
            Vajda.consumableSelection.hideBuffs = this.isSelected()
            Vajda.selectTab("consumables")
        })
        $("#energy_consumables", html).append(checkboxEnergyConsumes.getMainDiv())
        $("#motivation_consumables", html).append(checkboxMotivationConsumes.getMainDiv())
        $("#health_consumables", html).append(checkboxHealthConsumes.getMainDiv())
        $("#buff_consumables", html).append(buffsFilter.getMainDiv())
        htmlSkel.append(html)
        return htmlSkel
    }

    Vajda.createAddedJobsTab = function() {
        const htmlSkel = $(`<div id='added_jobs_overview'></div>`)
        const footerHtml = $(`
            <div id='start_vajda' style='position: relative'>
                <span id='vajda-state-info' class='vajda_state' style='position: absolute; left: 20px; top: 10px; font-family: Arial, Helvetica, sans-serif; font-size: 15px; font-weight: bold;'> 
                    Current state: ${Vajda.states[Vajda.currentState]}
                </span>
                <div class='vajda_run' style='position: absolute; left: 350px; top: 20px'></div>
            </div>
        `)
        const 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 ( let job = 0; job < Vajda.addedJobs.length; job++ ) {
            table.appendRow().appendToCell(-1,"jobIcon", Vajda.getJobIcon(Vajda.addedJobs[job].isSilver, Vajda.addedJobs[job].id, Vajda.addedJobs[job].x, Vajda.addedJobs[job].y)).appendToCell(-1,"jobName", Vajda.getJobName(Vajda.addedJobs[job].id)).appendToCell(-1,"jobStopMotivation", Vajda.createMinMotivationTextfield(Vajda.addedJobs[job].x, Vajda.addedJobs[job].y, Vajda.addedJobs[job].id, Vajda.addedJobs[job].stopMotivation)).appendToCell(-1,"jobSet", Vajda.createComboxJobSets(Vajda.addedJobs[job].x, Vajda.addedJobs[job].y, Vajda.addedJobs[job].id)).appendToCell(-1,"jobRemove", Vajda.createRemoveJobButton(Vajda.addedJobs[job].x, Vajda.addedJobs[job].y, Vajda.addedJobs[job].id))
        }
        const buttonStart = new west.gui.Button("Start", async function() {
            const parseSuccesful = Vajda.parseStopMotivation()
            if ( parseSuccesful ) {
                Vajda.currentState = 3
                $(`#vajda-state-info`).text(`Current state: ${Vajda.states[3]}`)
                Vajda.createRoute()
                Vajda.isRunning = true
                Vajda.setCookies()
                if ( Manager.isOptimized && Manager.hasEnoughPlugsAndDecorations() ) {
                    await Manager.createSchedule()
                }
                Observer.start()
                Vajda.run()
            } else {
                new UserMessage("Wrong format of set stop motivation", UserMessage.TYPE_ERROR).show()
            }
        });
        const buttonStop = new west.gui.Button("Stop",function() {
            Vajda.isRunning = false
            Vajda.currentState = 0
            Vajda.selectTab("chosenJobs")
            Observer.stop()
        })
        htmlSkel.append(table.getMainDiv())
        $(".vajda_run",footerHtml).append(buttonStart.getMainDiv())
        $(".vajda_run",footerHtml).append(buttonStop.getMainDiv())
        htmlSkel.append(footerHtml)
        return htmlSkel
    }


    Vajda.createStatisticsGui = function() {
        const offsetLeft = '.5rem'

        const statsBubbleStyle = `'
            padding: 1rem 2rem;
            border-radius: 10px;
            background-color: rgba(255, 255, 228, .3);
            position: relative;
            box-shadow: 0 0 5px rgba(0, 0, 0, .2)
        '`

        const pseudoHeadingStyle = `'
            display: block;
            width: calc(100% - ${offsetLeft});
            padding-left: ${offsetLeft};
            border-bottom: 1px solid black;
            margin-bottom: .5rem
        '`

        const refreshStats = `
            <div style=${statsBubbleStyle}>
                <b style=${pseudoHeadingStyle}>REFRESH COUNT</b>
                <p style='padding-left:${offsetLeft}'>Total in this session: ${Observer.refreshCount}</p>
            </div>
        `

        return $(`
            <div id='statistics_overview' style='padding: 0 2rem; display: grid; gap: 1rem'>
                <div style=${statsBubbleStyle}>
                    <b style=${pseudoHeadingStyle}>EXPERIENCE</b>
                    <p style='padding-left: ${offsetLeft}'>Total in this session: ${Vajda.formatNumber(Vajda.statistics.sessionXpCount)}</p>
                    <p style='padding-left: ${offsetLeft}'>Total overall: ${Vajda.formatNumber(Vajda.statistics.totalXpCount)}</p>
                </div>

                <div style=${statsBubbleStyle}>
                    <b style=${pseudoHeadingStyle}>MONEY</b>
                    <p style='padding-left: ${offsetLeft}'>Total in this session: ${Vajda.formatNumber(Vajda.statistics.sessionMoneyCount)}</p>
                    <p style='padding-left: ${offsetLeft}'>Total overall: ${Vajda.formatNumber(Vajda.statistics.totalMoneyCount)}</p>
                </div>

                <div style=${statsBubbleStyle}>
                    <b style=${pseudoHeadingStyle}>JOBS</b>
                    <p style='padding-left: ${offsetLeft}'>Total in this session: ${Vajda.formatNumber(Vajda.statistics.sessionJobsCount)}</p>
                    <p style='padding-left: ${offsetLeft}'>Total overall: ${Vajda.formatNumber(Vajda.statistics.totalJobsCount)}</p>
                </div>

                ${Observer.isEnabled ? refreshStats : ''}
            </div>
        `)
    }

    Vajda.createSettingsGui = function() {
        const htmlSkel = $(`<div id='settings_overview' style='padding: 10px'></div>`)
        const checkboxAddEnergy = new west.gui.Checkbox()
        checkboxAddEnergy.setLabel("Add energy")
        checkboxAddEnergy.setSelected(Vajda.settings.addEnergy)
        checkboxAddEnergy.setCallback(function() {
            Vajda.settings.addEnergy = !Vajda.settings.addEnergy
        })
        const checkboxAddMotivation = new west.gui.Checkbox()
        checkboxAddMotivation.setLabel("Add motivation")
        checkboxAddMotivation.setSelected(Vajda.settings.addMotivation)
        checkboxAddMotivation.setCallback(function() {
            Vajda.settings.addMotivation = !Vajda.settings.addMotivation
        })
        const checkboxAddHealth = new west.gui.Checkbox()
        checkboxAddHealth.setLabel("Add health")
        checkboxAddHealth.setSelected(Vajda.settings.addHealth)
        checkboxAddHealth.setCallback(function() {
            Vajda.settings.addHealth = !Vajda.settings.addHealth
        })

        const buttPlugsCheckbox = new west.gui.Checkbox()
        buttPlugsCheckbox.setLabel('Optimize for butt plugs and cake decorations')
        buttPlugsCheckbox.setSelected(Manager.isOptimized)
        buttPlugsCheckbox.setCallback(() => {
            Manager.isOptimized = p => !p
        })

        const enableObserverCheckbox = new west.gui.Checkbox()
        enableObserverCheckbox.setLabel('[Experimental] Enable auto refresh → Read user manual')
        enableObserverCheckbox.setSelected(Observer.isEnabled)
        enableObserverCheckbox.setCallback(() => {
            Observer.isEnabled = previous => {
                $('#observer-delay-input').css('max-height', previous ? '0px' : '30px')
                   
                Vajda.isRunning && !previous && Observer.start(forceStart = true)
                Vajda.isRunning && previous && Observer.stop()

                return !previous
            }
        })

        const observerDelayInput = new west.gui.Textfield()
        observerDelayInput.setWidth(100)
        observerDelayInput.setValue(Observer.getTimeOut(true))
        observerDelayInput.getMainDiv()[0].querySelector('input').addEventListener('change', e => {
            const val = e.target.value

            if ( Vajda.isNumber(val) ) {
                Observer.timeOut = val
            }
        })

        const style = (isOpen) => `'
            overflow: hidden;
            max-height: ${isOpen ? '30' : '0'}px;
            transition: max-height .5s;
            margin-bottom: 1rem;
        '`
        const observerDelay = $(`
            <div style=${style(Observer.isEnabled)} id='observer-delay-input'>
                Refresh page after 
            </div>
        `)
        observerDelay.append(observerDelayInput.getMainDiv())
        observerDelay.append(` minutes since the last action`)

        const saveMoneyCheckbox = new west.gui.Checkbox()
        saveMoneyCheckbox.setLabel('Deposit money in your hometown')
        saveMoneyCheckbox.setSelected(Vajda.settings.addDeposit.isEnabled)
        saveMoneyCheckbox.setCallback(() => {
            if ( Character.homeTown.town_id === 0 ) {
                new UserMessage("You don' have a home town", UserMessage.TYPE_HINT).show()
                return
            }

            const oldVal = !!Vajda.settings.addDeposit.isEnabled
            Vajda.settings.addDeposit.isEnabled = !oldVal

            $(`#deposit-limit-input`).css('max-height', oldVal ? '0px' : '30px')
        })

        const depositLimit = $(`
            <div style=${style(Vajda.settings.addDeposit.isEnabled)} id='deposit-limit-input'>
                Deposit when more than 
            </div>
        `)

        const limitInput = new west.gui.Textfield()
        limitInput.setWidth(100)
        limitInput.setValue(Number.isNaN(Vajda.settings.addDeposit.limit) ? '' : Vajda.settings.addDeposit.limit)
        limitInput.getMainDiv()[0].querySelector('input').addEventListener('change', e => {
            const val = e.target.value

            if ( Vajda.isNumber(val) ) {
                Vajda.settings.addDeposit.limit = val
            }
        })

        depositLimit.append(limitInput.getMainDiv())
        depositLimit.append(` $ in cash`)


  
        const htmlHealthStop = $("<div></div>")
        htmlHealthStop.append(`<span>Stoppage health percent value</span>`)
        const healthStopTextfiled = new west.gui.Textfield("healthStop")
        healthStopTextfiled.setValue(Vajda.settings.healthStop)
        healthStopTextfiled.setWidth(100)
        htmlHealthStop.append(healthStopTextfiled.getMainDiv())
        const htmlSetWearDelay = $("<div></div>")
        htmlSetWearDelay.append("<span> Job set equip delay </span>")
        const setWearDelayTextfiled = new west.gui.Textfield("setWearDelay")
        setWearDelayTextfiled.setValue(Vajda.settings.setWearDelay)
        setWearDelayTextfiled.setWidth(100)
        htmlSetWearDelay.append(setWearDelayTextfiled.getMainDiv())

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

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


        const manualLink = `
            <div onclick=Vajda.selectTab('manual') style="cursor: pointer; text-decoration: underline; position: absolute; top: 20px; right: 25px;">
                Open User Manual <svg style="width: 10px" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M352 0c-12.9 0-24.6 7.8-29.6 19.8s-2.2 25.7 6.9 34.9L370.7 96 201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L416 141.3l41.4 41.4c9.2 9.2 22.9 11.9 34.9 6.9s19.8-16.6 19.8-29.6V32c0-17.7-14.3-32-32-32H352zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z"/></svg>
            </div>
        `

        const buttonApply = new west.gui.Button("Apply", function() {
            Vajda.settings.addEnergy = checkboxAddEnergy.isSelected()
            Vajda.settings.addMotivation = checkboxAddMotivation.isSelected()
            Vajda.settings.addHealth = checkboxAddHealth.isSelected()
            if ( Vajda.isNumber(healthStopTextfiled.getValue()) ) {
                let healthStop = parseInt(healthStopTextfiled.getValue())
                healthStop = Math.min(30,healthStop)
                Vajda.settings.healthStop = healthStop
            }

            if ( Vajda.isNumber(setWearDelayTextfiled.getValue()) ) {
                let setWearDelay = parseInt(setWearDelayTextfiled.getValue())
                setWearDelay = Math.min(10,setWearDelay)
                Vajda.settings.setWearDelay = setWearDelay
            }

            if ( Vajda.isNumber(jobDelayTextFieldMin.getValue()) ) {
                let jobDelayTimeMin = parseInt(jobDelayTextFieldMin.getValue())
                Vajda.settings.jobDelayMin = jobDelayTimeMin
            } else {
                Vajda.settings.jobDelayMin = 0
                Vajda.settings.jobDelayMax = 0
                new UserMessage("Wrong format of delay job min value. Please set a number.", UserMessage.TYPE_ERROR).show()
            }

            if ( Vajda.isNumber(jobDelayTextFieldMax.getValue()) ) {
                let jobDelayTimeMax = parseInt(jobDelayTextFieldMax.getValue())
                Vajda.settings.jobDelayMax = jobDelayTimeMax
            } else {
                Vajda.settings.jobDelayMin = 0
                Vajda.settings.jobDelayMax = 0
                new UserMessage("Wrong format of delay job max value. Please set a number.", UserMessage.TYPE_ERROR).show()
            }
            Vajda.setCookies()
            Vajda.selectTab("settings")
        })

        htmlSkel.append(manualLink)

        htmlSkel.append(checkboxAddEnergy.getMainDiv())
        htmlSkel.append("<br>")
        htmlSkel.append(checkboxAddMotivation.getMainDiv())
        htmlSkel.append("<br>")
        htmlSkel.append(checkboxAddHealth.getMainDiv())
        htmlSkel.append("<br>")
        htmlSkel.append(buttPlugsCheckbox.getMainDiv())
        htmlSkel.append("<br>")
        htmlSkel.append(enableObserverCheckbox.getMainDiv())
        htmlSkel.append("<br>")
        htmlSkel.append(observerDelay)
        htmlSkel.append(saveMoneyCheckbox.getMainDiv())
        htmlSkel.append(depositLimit)
        htmlSkel.append(htmlHealthStop)
        htmlSkel.append("<br>")
        htmlSkel.append(htmlSetWearDelay)
        htmlSkel.append("<br>")
        htmlSkel.append(htmlJobDelay)
        htmlSkel.append("<br>")
        htmlSkel.append(buttonApply.getMainDiv())
        return htmlSkel
    }

    Vajda.createManualGui = function() {
        const listStyle = `'padding-inline-start: 14px'`
        const nestedListStyle = `'padding-inline-start: 30px'`
        const containerStyle = `'
            padding: 0 30px 30px
        '`

        const html = `
            <div style=${containerStyle}>
                <h3>Read Before use</h3>
                <ol style=${listStyle}>
                    <li>
                        <strong>Calculating optimal route</strong> takes time and memory, a lot of it. It might not be noticeable for up to 12-14 jobs,
                        but anything more than that... you will notice a short freeze of the window. I don't recommend selecting more than 18 jobs, as the
                        algorithm will take about a minute (depending on the hardware, of course) to execute, while it takes almost 8 minutes to
                        find the best route for 20 jobs. In these scenarios, your browser may appear unresponsive and prompt you to reload the page 
                        or wait. I recommend being patient and allowing some time for the algorithm to complete. If that doesn't work, remove one
                        or two jobs; that's the cheapest way to solve this problem.
                    </li>

                    <li>
                        <strong>The optimization for butt plugs and cake decorations</strong> setting, which is most likely why you are here:
                        <ul style=${nestedListStyle}>
                            <li>The setting has to be enabled <i>before</i> you start Zdenka, it won't work otherwise</li>
                            <li>The <i>Add motivation</i> setting has to be enabled as well</li>
                            <li>Do not start Zdenka with low energy and consumables on cooldown</li>
                            <li>Do not use consumables yourself while it's running</li>
                            <li>
                                You don't have to select consumables manually with this setting, Zdenka will do it for you. Just make sure
                                you have enough of them butt plugs and decorations
                            </li>
                        </ul>

                        These restrictions are simply the cost of having Zdenka refill energy <i>while</i> doing jobs.
                    </li>

                    <li>
                        Zdenka can <strong>travel</strong> to jobs that you can't do in travel set (unless you are super low level).
                    </li>

                    <li>
                        <strong>Buffs</strong> will be used automatically when selected (and when no buff is active, of course). Only one buff
                        of each type can be selected at once. Keep in mind that consumables with cooldown are <b>not</b> treated as buffs and will
                        only be used to refill whatever your character needs.
                    </li>

                    <li>
                        Zdenka will display <strong>all silver jobs</strong>, not just those you can do and are closest to yoor character. This
                        allows you <i>some</i> control of the route, for instance there might be multiple silver jobs of the same kind and one 
                        of them might simply be in a more advantageous position.
                    </li>

                    <li>
                        When <strong>Auto Refresh</strong> is enabled, Zdenka will refresh the page and start automatically <i>n</i> minutes
                        after starting a job (or walking to one). Starting new jobs will restart this countdown. Do not set the delay to low numbers,
                        Zdenka enforces a minimum of 2.5 minutes anyway, however, I do recommend a slightly longer delay.
                    </li>
                </ol>
            </div>
        `

        const container = new west.gui.Scrollpane().getMainDiv()
        container.style = 'height: 400px; overflow-y: scroll'
        container.innerHTML = html


        return container
    }

    Vajda.createMenuIcon = function() {
        const menuImage = 'data:image/jpeg;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAAZCAYAAADE6YVjAAAAIGNIUk0AAHomAACAhAAA+gAAAIDoAAB1MAAA6mAAADqYAAAXcJy6UTwAAAAGYktHRAD/AP8A/6C9p5MAAAAJcEhZcwAAEnQAABJ0Ad5mH3gAAAAHdElNRQfoAwISKiXgp+5lAAAFKUlEQVRIx4WVy28cWRXGf+dWudvtdrc78TOjJJOHIUOiZDGLGbFAQUKDYMWfwGKGBRIrHmLBgoeGBRI7/gxACGmkEQiQYEZkCBNmnJB4jOPYcWx30u9HdXVVddU9LLr65Qhxpaq6p+rc853vfLfOlQ/vbqsxgjEGJ72MMTiOQUTGlzHTts7YxgjGmfg6jsGYqW8iTJyngo7GyFa1WBsDishsAiP3/2W78DLANHA8CDn6zz28epm18+fI5ZfIn7nAQnE1DQRiJokZI1MAw6crAgJjkJnsgedP7lP++A+4SUJ/UMcun6Vb22f9ypsUVi6C6DBAum56PQgCuIbTLCaOfa/N4f07nD9T4NLVTVDweh0afovKwT/JFZbJ5PII0+t4aW7kFMDESehWj3hxsEsQK5VGGysO7W5I7WmZ2tM9/NbRKf2YmY9MM/1hmrKi9DpVwiDk37snPNh+itdusP2kzO6TFxw93qdTeYZNEv7fMKczGdsKg7CP6whv3LjKGzc/h9rh/Pbr13AFWuXHxKHHJOfJUJ3M3dNbdoIIySAimxU06GK9ObqVDu6Cg9fvMJfL4nt1wm6VbL6EqqKaVkF1JqaZ3m4zlFRRG+InMUf1Fkmzy1Kti+kF1Hp9EjEMogi/U8PaZBxcpyioDt+ZmdRTR2xCMuiTyw5YW8lTqVY48dqEmxsc97t8uv0Y11FElaB1TNRrjbUZBVZVlOFzRhPVIVavXaZx8BGxV4EkZmV5CTeT5dH9BxwfP6ewtEDpTIFCYZFBp0z7ZIdEbRp8CkRBX9JEgDAi8/AzGtt/4oVtU280+fz5K1y6usm5V16h1aqze7zHfDZHoVike3BI6RDcM1fQ9Y2xNtNA7oxIfh/3V7+k9LvfUHQTovVF9FyJ1QubzGfnWSwWyQDB82cMtj6l3gjJ3d9j/aDCwh//jP+Td7HXb55iI8i9rT0VEcR1MXc+pPSddyj0fRwgEqG7kCW4uMb8pQsUvQTb6dIddAm7HsVnDfLAHBABXP8C0U/fJb79FYwIjjPs3mZaEHP3Dk7fRxAcgRzKai9gdecIHjwiONiFw30kDDhX7bIMZAUsQojQf7SN/OD7mI/vYh0Hq2CtTUFEwPeZ2/oXAsQiKIIzFI28tayftMgEMVE+x1K5zXwwGCaT/hs9gZYIrYN99Ec/ZO7997CdLlbBqCqIIPUacniAAj6QpD1UZNi+HYWlapvVozqLfjjuTQ6QCJRVaaqSiBB98gnxt97G/uzH2HZ7wsS0mphuBxA8oD3sLMNjQGX01w4TmmrjCnhiGHzxS8Sv3cAAAxFafo/mr39L+MEHuCMQp9MmE0WYlIWHsILi2CGQIFh00qUEVCBQxXv1Ihs//wVJPyD49jtw+IQI6K2VaFb3UiZRhPz1LzhhiIgwlzLopaULUCIUmyYQAYGCp3AMlC9dRtfWca9dI/nm27TyBZqrywTf+DLNuI2L4yAPH5K8/x4+MI8yQFAUD5gXIatKBkVT1oMUyEc4UYVCCTXO8BT82lsEtceYrJJsrFDthLgqguzvYRpVQqCvUBMoMSrTzG10CqAIMRACa4Uiqoq3/xm9nX+Q3VylHcYEmbNc/+ptXBKLXr5M762vEz64R7/n04oGnI2icdAk1cOkAAkyLtsc4DhKc+tvBCc7hKFPn3kWXn2d1269ycJiEVesRW/cwv/u9wg++j3hcYXk71s42ztYwKYcIgQzLBgJkKDECBnX5bB1jP/wDswtULx8i4tXbrK4soEYQxzH/BeqMKTC7KpG/gAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAyNC0wMy0wMlQxODo0MjozMSswMDowMC8Ty8sAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMjQtMDMtMDJUMTg6NDI6MzErMDA6MDBeTnN3AAAAKHRFWHRkYXRlOnRpbWVzdGFtcAAyMDI0LTAzLTAyVDE4OjQyOjM3KzAwOjAwaotnkgAAAABJRU5ErkJggg=='
        let div = $('<div class="ui_menucontainer" />')
        let link = $('<div id="vajda_menu" class="menulink" onclick=Vajda.loadJobs() title="Zdenka Studenková" />').css('background-image', 'url(' + menuImage + ')')
        $('#ui_menubar').append((div).append(link).append('<div class="menucontainer_bottom" />'))
    }


    Vajda.createWindow = function(isHumanAction = true) {
        const window = wman.open("vajda").setResizeable(false).setMinSize(650, 480).setSize(650, 480).setMiniTitle("Vajda Jožo")
        const tabs = {
            "jobs": "Jobs",
            "chosenJobs": "Chosen Jobs",
            "sets": "Sets",
            "consumables": "Consumables",
            "stats": "Statistics",
            "settings": "Settings",
            "manual": "User manual"
        }

        let tabLogic = function(win,id) {
            const content = $(`<div class='vajda-window'></div>`)
            switch(id) {
                case 'jobs': 
                    Vajda.loadJobData(function(){
                        Vajda.removeActiveTab(this)
                        Vajda.removeWindowContent()
                        Vajda.addActiveTab("jobs",this)
                        content.append(Vajda.createJobsTab())
                        Vajda.window.appendToContentPane(content)
                        Vajda.addJobTableCss()
                        $(".vajda-window .tw2gui_scrollpane_clipper_contentpane").css({"top": Vajda.jobTablePosition.content})
                        $(".vajda-window .tw2gui_scrollbar_pulley").css({"top": Vajda.jobTablePosition.scrollbar})
                        Vajda.addEventsHeader()
                    })
                    break
                case 'chosenJobs': 
                    Vajda.removeActiveTab(this)
                    Vajda.removeWindowContent()
                    Vajda.addActiveTab("chosenJobs", this)
                    content.append(Vajda.createAddedJobsTab())
                    Vajda.window.appendToContentPane(content)
                    $(".vajda-window .tw2gui_scrollpane_clipper_contentpane").css({"top": Vajda.addedJobTablePosition.content})
                    $(".vajda-window .tw2gui_scrollbar_pulley").css({"top": Vajda.addedJobTablePosition.scrollbar})
                    Vajda.addAddedJobsTableCss()
                    break
                case 'sets': 
                    Vajda.loadSets(function() {
                        Vajda.removeActiveTab(this)
                        Vajda.removeWindowContent()
                        Vajda.addActiveTab("sets",this)
                        content.append(Vajda.createSetGui())
                        Vajda.window.appendToContentPane(content)
                    })
                    break
                case 'consumables': 
                    Vajda.removeActiveTab(this)
                    Vajda.removeWindowContent()
                    Vajda.addActiveTab("consumables",this)
                    Vajda.findAllConsumables()
                    content.append(Vajda.createConsumablesTable())
                    Vajda.window.appendToContentPane(content)
                    $(".vajda-window .tw2gui_scrollpane_clipper_contentpane").css({"top": Vajda.consumableTablePosition.content})
                    $(".vajda-window .tw2gui_scrollbar_pulley").css({"top": Vajda.consumableTablePosition.scrollbar})
                    Vajda.addConsumableTableCss()
                    break
                case 'stats':
                    Vajda.removeActiveTab(this)
                    Vajda.removeWindowContent()
                    Vajda.addActiveTab("stats",this)
                    content.append(Vajda.createStatisticsGui())
                    Vajda.window.appendToContentPane(content)
                    break
                case 'settings': 
                    Vajda.removeActiveTab(this)
                    Vajda.removeWindowContent()
                    Vajda.addActiveTab("settings",this)
                    content.append(Vajda.createSettingsGui())
                    Vajda.window.appendToContentPane(content)
                    break
                case 'manual':
                    Vajda.removeActiveTab(this)
                    Vajda.removeWindowContent()
                    Vajda.addActiveTab("manual", this)
                    content.append(Vajda.createManualGui())
                    Vajda.window.appendToContentPane(content)
                    break
            }
        }

        for(let tab in tabs) {
            window.addTab(tabs[tab],tab,tabLogic)
        }

        Vajda.window = window

        if ( !isHumanAction ) wman.close('vajda')

        Vajda.selectTab('jobs')
    }

    Vajda.selectTab = function(key) {
        Vajda.window.tabIds[key].f(Vajda.window,key)
    }

    Vajda.removeActiveTab = function(window) {
        $('div.tw2gui_window_tab', window.divMain).removeClass('tw2gui_window_tab_active')
    }

    Vajda.addActiveTab = function(key, window) {
        $(`div._tab_id_${key}`, window.divMain).addClass('tw2gui_window_tab_active')
    }
    
    Vajda.removeWindowContent = function() {
        $(".vajda-window").remove()
    }

    Vajda.addConsumableTableCss = function() {
        $(".vajda-window .consumIcon").css({"width":"80px"})
        $(".vajda-window .consumCount").css({"width":"60px"})
        $(".vajda-window .consumEnergy").css({"width":"60px"})
        $(".vajda-window .consumMotivation").css({"width":"70px"})
        $(".vajda-window .consumHealth").css({"width":"60px"})
        $(".vajda-window .consumBuffs").css({"width": "150px"})
        $(".vajda-window .row").css({"height":"80px"})
        $('.vajda-window').find('.tw2gui_scrollpane').css('height', '250px')
    }

    Vajda.addJobTableCss = function() {
        $(".vajda-window .jobIcon").css({"width":"80px"})
        $(".vajda-window .jobName").css({"width":"150px"})
        $(".vajda-window .jobXp").css({"width":"40px"})
        $(".vajda-window .jobMoney").css({"width":"40px"})
        $(".vajda-window .jobMotivation").css({"width":"40px"})
        $(".vajda-window .jobDistance").css({"width":"100px"})
        $(".vajda-window .row").css({"height":"60px"})
        $('.vajda-window').find('.tw2gui_scrollpane').css('height', '250px')
    }

    Vajda.addAddedJobsTableCss = function() {
        $(".vajda-window .jobIcon").css({"width":"80px"})
        $(".vajda-window .jobName").css({"width":"130px"})
        $(".vajda-window .jobStopMotivation").css({"width":"110px"})
        $(".vajda-window .jobRemove").css({"width":"105px"})
        $(".vajda-window .jobSet").css({"width":"100px"})
        $(".vajda-window .row").css({"height":"60px"})
        $('.vajda-window').find('.tw2gui_scrollpane').css('height', '250px')
    }

    Vajda.createJobsTab = function() {
        const htmlSkel = $(`<div id='jobs_overview'></div>`)
        const 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>
        `)
        
        const table = new west.gui.Table()
        const xpIcon = '<img src="/images/icons/star.png">'
        const dollarIcon = '<img src="/images/icons/dollar.png">'
        const motivationIcon = '<img src="/images/icons/motivation.png">'
        const arrow_desc = '&nbsp;<img src="../images/window/jobs/sortarrow_desc.png"/>'
        const arrow_asc = '&nbsp;<img src="../images/window/jobs/sortarrow_asc.png"/>'
        const uniqueJobs = Vajda.getAllUniqueJobs()
        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",xpIcon + (Vajda.sortJobTableXp == 1 ? arrow_asc : Vajda.sortJobTableXp == -1 ? arrow_desc : "")).appendToCell("head","jobMoney",dollarIcon).appendToCell("head","jobMotivation",motivationIcon).appendToCell("head","jobDistance","Distance " + (Vajda.sortJobTableDistance == 1 ? arrow_asc : Vajda.sortJobTableDistance == -1 ? arrow_desc : "")).appendToCell("head","jobAdd","")
        for ( let job = 0; job < uniqueJobs.length; job++ ) {
            table
                .appendRow()
                .appendToCell(-1,"jobIcon",Vajda.getJobIcon(uniqueJobs[job].isSilver,uniqueJobs[job].id,uniqueJobs[job].x,uniqueJobs[job].y))
                .appendToCell(-1,"jobName",Vajda.getJobName(uniqueJobs[job].id))
                .appendToCell(-1,"jobXp",uniqueJobs[job].experience)
                .appendToCell(-1,"jobMoney",uniqueJobs[job].money)
                .appendToCell(-1,"jobMotivation",uniqueJobs[job].motivation)
                .appendToCell(-1,"jobDistance",uniqueJobs[job].distance.formatDuration())
                .appendToCell(-1,"jobAdd", Vajda.createAddJobButton(uniqueJobs[job].x,uniqueJobs[job].y,uniqueJobs[job].id))
        }
        const textfield = new west.gui.Textfield("jobsearch").setPlaceholder("Select job name")
        if ( Vajda.jobFilter.filterJob !== "" ) {
            textfield.setValue(Vajda.jobFilter.filterJob)
        }

        const checkboxOnlySilver = new west.gui.Checkbox()
        checkboxOnlySilver.setLabel("Silvers")
        checkboxOnlySilver.setSelected(Vajda.jobFilter.filterOnlySilver)
        checkboxOnlySilver.setCallback(function() {
            if ( this.isSelected() ) {
                Vajda.jobFilter.filterOnlySilver = true
            }else {
                Vajda.jobFilter.filterOnlySilver = false
            }
        })
        const checkboxNoSilver = new west.gui.Checkbox()
        checkboxNoSilver.setLabel("No silvers")
        checkboxNoSilver.setSelected(Vajda.jobFilter.filterNoSilver)
        checkboxNoSilver.setCallback(function() {
            if ( this.isSelected() ) {
                Vajda.jobFilter.filterNoSilver = true
            } else {
                Vajda.jobFilter.filterNoSilver = false
            }
        })
        const checkboxCenterJobs = new west.gui.Checkbox()
        checkboxCenterJobs.setLabel("Center jobs")
        checkboxCenterJobs.setSelected(Vajda.jobFilter.filterCenterJobs)
        checkboxCenterJobs.setCallback(function() {
            if ( this.isSelected() ) {
                Vajda.jobFilter.filterCenterJobs = true
            } else {
                Vajda.jobFilter.filterCenterJobs = false
            }
        })
        const buttonFilter = new west.gui.Button("Filter", function() {
            Vajda.jobFilter.filterJob = textfield.getValue()
            Vajda.jobTablePosition.content = "0px"
            Vajda.jobTablePosition.scrollbar = "0px"
            Vajda.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
    }

    Vajda.createAddJobButton = function(x, y, id) {
        const buttonAdd = new west.gui.Button("Add new job", function() {
            Vajda.addJob(x, y, id)
            Vajda.jobTablePosition.content = $(".vajda-window .tw2gui_scrollpane_clipper_contentpane").css("top")
            Vajda.jobTablePosition.scrollbar = $(".vajda-window .tw2gui_scrollbar_pulley").css("top")
            Vajda.selectTab("jobs")
        })
        buttonAdd.setWidth(100)
        return buttonAdd.getMainDiv()
    }

    Vajda.createMinMotivationTextfield = function(x, y, id, placeholder) {
        const componentId = `x-${x}y-${y}id-${id}`
        const textfield = new west.gui.Textfield()
        textfield.setId(componentId)
        textfield.setWidth(40)
        textfield.setValue(placeholder)
        return textfield.getMainDiv()
    }

    Vajda.createRemoveJobButton = function(x, y, id) {
        const buttonRemove = new west.gui.Button("Remove job", function() {
            Vajda.removeJob(x, y, id)
            Vajda.addedJobTablePosition.content = $(".vajda-window .tw2gui_scrollpane_clipper_contentpane").css("top")
            Vajda.addedJobTablePosition.scrollbar = $(".vajda-window .tw2gui_scrollbar_pulley").css("top")
            Vajda.selectTab("chosenJobs")
        })
        buttonRemove.setWidth(100)
        return buttonRemove.getMainDiv()
    }


    $(document).ready(() => {
        try {
            Vajda.loadLanguage()
            Vajda.loadSets()
            Vajda.createMenuIcon()
            Vajda.getCookies()
            Observer.resumeSession()
        } catch(e) {
            console.log(e)
            console.log("exception occured")
        }
    })
})()