Hentaiverse Monsterbation

M-M-M-MONSTERBATE!!

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Hentaiverse Monsterbation
// @namespace    hvmonsterbate
// @version      1.4.1.1
// @description  M-M-M-MONSTERBATE!!
// @match        *://*.hentaiverse.org/*
// @exclude      *hentaiverse.org/equip/*
// @grant        none
// ==/UserScript==

// vvv SETTINGS vvv //

const settings = {
    cfgInterface: true, //              add link under character to configure settings
    profileAutoswitch: true, //         automatically switch profile when changing persona or equipment set
    isekaiInherit: true, //             use persistent persona/equipment profiles in isekai

    // ui settings
    cfgButton: true, //                 show button to access settings and switch profiles during battle
    mpboost: 100, spboost: 100, //      set to match player stats. add up all bonuses from abilities and capacitor
    mppot: 75, sppot: 75, //            set to percentages restored by mana/spirit potions
    showCooldowns: true, //             show cooldowns on the quickbar
    effectsAboveMonsters: false, //     move player effects to above monsters
    vitalsAboveMonsters: false, //      move vitals and spirit button to above monsters
    quickbarBesideMonsters: false, //   move quickbar to the right of monsters
    riddleRight: false, //              shrink riddlemaster to the right
    condenseLeft: false, //             activate the above four options with everything moved to the left
    compactQuickbar: false, //          eliminate whitespace on quickbar and monster bars
    expireNoblink: false, //            disable blinking of expiring effects
    showDurations: true, //             show effect durations
    stackBorder: true, //               show effect stacks as border thickness rather than numbers
    alertColours: true, //              change background colours according to alert conditions
    alertBackground: false, //          whole background instead of relevant area for alert colours
    alertBuffs: '/(healthpot.png|manapot.png|scroll.png|infusion.png|regen.png|heartseeker.png|arcanemeditation.png)/',
    // change colour of player effects area if any of these buffs has less than 2 turns left
    // add the icon filename of any critical buff. this also affects stopOnBuffsExpiring
    // to get the filename, activate the buff in battle, right click its icon and inspect element
    // some examples: spiritpot.png darkinfusion.png protection_scroll.png flowers.png gum.png
    //                spiritshield.png sparklife.png shadowveil.png haste.png protection.png
    colours: { default:     '#EDEBDF', //           alert colours
               spark:       'magenta', //           set to any valid html colour, or false to disable
               lowhp:       'deeppink',
               lowmp:       'darkslateblue',
               lowsp:       'indigo',
               ocfull:      'mediumspringgreen',
               expiring:    'lightblue',
               channelling: 'aquamarine',
               usable:      'mediumspringgreen',
               miss:        'gray', //              log colours
               damage:      'red',
               item:        '#00B000',
               attack:      'blue',
               spell:       'darkslateblue',
               recovery:    'mediumseagreen',
               effect:      'seagreen',
               spirit:      'indigo',
               proficiency: 'darkolivegreen',
               monster:     'springgreen', //       monsters that match your keywords
               stun:        'darkseagreen', //      stunned monsters, set to false to disable
               imperil:     'lightsteelblue', //    imperilled monsters
               stunimperil: 'cadetblue' }, //       stunned and imperilled monsters
    usableBlink: true, //           mana and spirit gems and potions on the quickbar blink when usable to their full effect
    logColours: false, //           add colour highlights to the battle log
    turnDividers: false, //         add horizontal row between turns
    logPasteover: false, //         add last turn of previous round to new round log. requires ajaxRound
    hideLog: false, //              hide the battle log
    maxVitals: false, //            show maximum player vitals
    showMonsterHP: false, //        display current and max hp of monsters
    shortenHPbars: false, //        shorten monster hp bars relative to their max hp
    monsterNumbers: false, //       show monster numbers instead of letters
    monsterInfo: true, //           show monster data from decondelite's database
    monsterKeywords: false, //      highlight monsters where the name, id or max hp match this expression, set to false to disable
                            //      example: '/(Meiling|MID=70699|HP=243060)/'
    ajaxRound: true, //             advance to next round using ajax. set to false if you use other scripts that do not support this
    ajaxIntervals: 100, //          set to 0 or a higher number if you experience weird flashing of expiring effects
    noPopup: true, //               skip end of round popup
    stopAtBattleEnd: true, //       do not dismiss popup at end of battle
    stopOnEquipDrop: false, //      do not dismiss popup if equipment drops, quality specified by cutoff below
    clickableRiddlemaster: true, // add links to the riddlemaster to directly submit an answer
    edConfirm: false, //            ask for confirmation before using energy drink
    fleeConfirm: false, //          ask for confirmation before fleeing
    raiseGem: false, //             raise gem icon above quickbar
    hoverGem: false, //             activate gem by hovering over the icon
    hoverSpirit: false, //          activate spirit stance by hovering over the icon
    quickbarExtend: [ 1,'ikey_1','ikey_2','ikey_3','ikey_4','ikey_7','ikey_8','ikey_9','ikey_10',
                    'ikey_s1','ikey_s2','ikey_s4','ikey_n1','ikey_n5','ikey_n6','ikey_6','ikey_5' ],
    // ID for skills/spells/items (in quotes), 0 for space, 1 for gem
    // set to [1] if you just want the gem icon or [] to disable
    // IDs:
    // 1001 Flee 1011 Scan 1101 FUS RO DAH 1111 Orbital Friendship Cannon
    // 2201 Shield Bash 2202 Vital Strike 2203 Merciful Blow
    // 2301 Great Cleave 2302 Rending Blow 2303 Shatter Strike
    // 2401 Iris Strike 2402 Backstab 2403 Frenzied Blows
    // 2101 Skyward Sword 2501 Concussive Strike
    // 111 Fiery Blast 112 Inferno 113 Flames of Loki
    // 121 Freeze  122 Blizzard 123 Fimbulvetr
    // 131 Shockblast 132 Chained Lightning 133 Wrath of Thor
    // 141 Gale 142 Downburst 143 Storms of Njord
    // 151 Smite 152 Banishment 153 Paradise Lost
    // 161 Corruption 162 Disintegrate 163 Ragnarok
    // 211 Drain 212 Weaken 213 Imperil
    // 221 Slow 222 Sleep 223 Confuse
    // 231 Blind 232 Silence 233 MagNet
    // 311 Cure 312 Regen 313 Full-Cure
    // 411 Protection 412 Haste 413 Shadow Veil
    // 421 Absorb 422 Spark of Life 423 Spirit Shield
    // 431 Heartseeker 432 Arcane Focus
    // ikey_1-ikey_15 items ikey_s1-ikey_s6 scrolls ikey_n1-ikey_n6 infusions

    // mouse binding and hover settings
    // it is best to use mouse bindings in conjunction with hoverArea, as mouse actions always target the whole monster
    clickEverywhere: false, //  by default, click actions are only performed when the cursor is over a live monster
                            //  setting this to true will enable middle and right click bindings and disable the context menu everywhere
    wheelEverywhere: false, //  same as above, but for the wheel
    mouseEngage: false, //      hold mouse buttons to modify hover behaviour, rather than performing the bound action only once
    clickLeft: false, //        unused. set to "Nothing" to attack with mouseEngage
    clickMiddle: "Cast('Scan')",
    clickRight: "Strongest([Cast('FUS RO DAH'), Cast('Orbital Friendship Cannon'), Cast('Ragnarok'), Cast('Disintegrate'), Cast('Corruption')])",
    wheelUp: "Impulse(Cast('Imperil'))",
    wheelDown: "Impulse(Cast('Weaken'))",
    wheelLeft: "Impulse(Strongest([Cast('Silence'), Cast('Sleep')]))",
    wheelRight: "Impulse(Strongest([Cast('Vital Strike'), Cast('Frenzied Blows'), Cast('MagNet')]))",
    hoverAction: "Nothing", // Attack
    hoverShiftAction: "Strongest([Cast('Ragnarok'), Cast('Disintegrate'), Cast('Corruption')])", // alternate hover action when holding shift
    hoverCtrlAction: "Strongest([Cast('Paradise Lost'), Cast('Banishment'), Cast('Smite')])", // alternate hover action when holding ctrl
    hoverAltAction: "Strongest([Cast('Flames of Loki'), Cast('Inferno'), Cast('Fiery Blast')])", // alternate hover action when holding alt
    // these can be set to any bindable action that can be followed up with targeting a monster,
    // as explained in the keybind section, or to false to disable
    // examples:
    // hoverAction: false, // disable hover
    // hoverAction: "Nothing", // attack
    // hoverAction: "Strongest([Cast('Ragnarok'), Cast('Disintegrate'), Cast('Corruption')])", // dark spell rotation
    // hoverShiftAction: "Strongest([ToggleHover, Cast('Imperil')])", // single cast of imperil
    // with the above example, you can hold shift, hover and hit Z to cast imperil while being protected by the usual hover safeguards
    hoverArea: 6, // part of the monster that activates hover
                  // 1: whole box, 2: icon, 3: name, 4: vitals, 6: status effects

    // hoverplay interrupt settings
    startRoundWithHover: true, //    have hoverplay active at the beginning of each round
                               //    or require a kepress to start, in case you want to imperil first
    hoverAutoresume: false, //       reactivate hover after releasing any key
    minHP: 0.3,
    minMP: 0.1,
    minSP: 'auto', //                formula when set to 'auto': 0.5-0.5*spboost/(spboost+100)
    stopOnEmergency: true, //        sparked or low vitals
    stopOnBuffsExpiring: true, //    critical buff expiring in 1 turn or less

    // mobile settings
    clearRound: false, // clear target and reset hoverAction at round transition, to avoid lingering taps and make monsterBar safer
    spacedBar: false, //  increase spacing of quickbar and monster bar
    monsterBar: [], //    add skill/spell icons next to monsters, for single use or tap to engage/tap elsewhere to disengage, single skills and spell rotations
    // example: first parameter is false for single use or true to engage, followed by skill/spell IDs or leave empty for attack
    // monsterBar: [ [true], //                     engage attack
    //               [false,'213'], //              single cast of imperil
    //               [false,'212'], //              single cast of weaken
    //               [true,'163','162','161'] ], // engage dark spell rotation

    // tracking settings
    trackDrops: true, //          show total numbers of drops and exp at end of battle
    detailedDroplog: true, //     list each drop type individually, excluding crystals and equipment below your quality cutoff
    detailedCrystlog: false, //   list each crystal type individually
    equipmentCutoff: 3, //        0 to track all equipment combined,
                        //        1 to track Peerless separateley,
                        //        2 to track Peerless and Legendary separateley, etc.
    selectLog: false, //          limit end-of-battle onclick to icon, allowing for easier selecting of log
    terseLog: false, //           format log for easier pasting into spreadsheets
    trackProficiency: false, //   show total proficiency gains at end of battle
    proficiencySidebar: false, // show live proficiency gains during battle
    profbarInMainpane: false, //  set to false to avoid overlap with showMonsterHP
    deleteDropLog: 2, //          delete drop log, 0: never, 1: when navigating away from battle section, 2: at end of battle
    dropFontSize: 100, //         adjust font size of drop and proficiency log
    trackSpeed: true, //          show turn count and speed statistic at end of battle
    speedFontSize: 100, //        adjust font size of speed statistic
    trackDamage: true, //         show damage dealt and taken at end of battle
    damageFontSize: 100, //       adjust font size of damage log
    trackUsage: true, //          show attack/skill/spell/item usage at end of battle
    deleteCombatLog: 2, //        delete damage and usage logs, 0: never, 1: when navigating away from battle section, 2: at end of battle
    consoleLog: false, //         output raw machine-readable log data to console when showing the drop log
    showRound: true, //           show the current round number during battle
    bigRoundCounter: false, //    bigger round counter, placed in top right

    // key bindings
    bind: "\
          Bind(KEY_SPACE, Any, Strongest([Cast('Cure'), HoverAction(Cast('Cure'), true)]));\
          Bind(KEY_Z, Any, ToggleHover);\
          Bind(KEY_S, Any, Impulse(Toggle('Spirit')));\
          Bind(KEY_A, Strongest([Use(4), Cast('Full-Cure'), Cast('Cure')]));\
          Bind(KEY_A, Shift, Strongest([Use(7), Use(4), Cast('Full-Cure'), Cast('Cure')]));\
          Bind(KEY_A, Ctrl, Strongest([Use(7), Use(4), Cast('Full-Cure'), Cast('Cure')]));\
          Bind(KEY_A, Alt, Strongest([Use(7), Use(4), Cast('Full-Cure'), Cast('Cure')]));\
          Bind(KEY_X, Strongest([Use('s1'), Use('s4'), Use('s2'), Use(2), Use(1)]));\
          Bind(KEY_X, Shift, Strongest([Use('s1'), Use('s4'), Use('s2'), Use('n6'), Use(2), Use(1)]));\
          Bind(KEY_X, Ctrl, Strongest([Use('s1'), Use('s4'), Use('s2'), Use('n5'), Use(2), Use(1)]));\
          Bind(KEY_X, Alt, Strongest([Use('s1'), Use('s4'), Use('s2'), Use('n1'), Use(2), Use(1)]));\
          Bind(KEY_C, Any, Cast('Regen'));\
          Bind(KEY_V, Any, Cast(damage));\
          Bind(KEY_Q, Impulse(Use(5)));\
          Bind(KEY_Q, Shift, Impulse(Strongest([Use(8), Use(5)])));\
          Bind(KEY_Q, Ctrl, Impulse(Strongest([Use(8), Use(5)])));\
          Bind(KEY_Q, Alt, Impulse(Strongest([Use(8), Use(5)])));\
          Bind(KEY_W, Any, Impulse(Use(3)));\
          Bind(KEY_E, Impulse(Use(6)));\
          Bind(KEY_E, Shift, Impulse(Strongest([Use(9), Use(6)])));\
          Bind(KEY_E, Ctrl, Impulse(Strongest([Use(9), Use(6)])));\
          Bind(KEY_E, Alt, Impulse(Strongest([Use(9), Use(6)])));\
          Bind(KEY_P, Settings);\
          Bind(KEY_1, Any, Strongest([TargetMonster(1), Cast('Imperil')]));\
          Bind(KEY_2, Any, Strongest([TargetMonster(4), Cast('Imperil')]));\
          Bind(KEY_3, Any, Strongest([TargetMonster(7), Cast('Imperil')]));\
          ",
    // Add this for as many bindings as you want:
    // Bind(KeyCode, Modifier, Action);\
    //  KeyCode = From http://www.javascripter.net/faq/keycodes.htm or any of the following:
    //  KEY_A, KEY_B, KEY_C, KEY_D, KEY_E, KEY_F, KEY_G, KEY_H, KEY_I, KEY_J, KEY_K, KEY_L, KEY_M,
    //  KEY_N, KEY_O, KEY_P, KEY_Q, KEY_R, KEY_S, KEY_T, KEY_U, KEY_V, KEY_W, KEY_X, KEY_Y, KEY_Z,
    //  KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8, KEY_9, KEY_0,
    //  KEY_SPACE, KEY_ENTER, KEY_PAGEUP, KEY_PAGEDOWN, KEY_END, KEY_HOME, KEY_LEFT, KEY_UP, KEY_RIGHT, KEY_DOWN,
    //  KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10, KEY_F11, KEY_F12,
    //  KEY_COMMA, KEY_PERIOD, KEY_SLASH, KEY_FORWARDSLASH, KEY_GRAVE, KEY_TILDE, KEY_LBRACKET, KEY_BACKSLASH,
    //  KEY_SEMI, KEY_RBRACKET, KEY_APOSTROPHE, KEY_SHIFT, KEY_CTRL, KEY_ALT
    //  Modifier = This is OPTIONAL. Valid mods are NoMod, Shift, Ctrl, Alt, CtrlShift, AltShift, CtrlAlt, CtrlAltShift, Any
    //  Action = Valid actions:
    //    Cast('Spell Name')
    //      Spell Name.
    //    Use('Item ID')
    //      Valid Item IDs are 'p' for Gem, 1-15 for Items, 's1'-'s6' for Scrolls and 'n1'-'n6' for Infusions.
    //    Toggle('Type')
    //      Attack, Focus, Defend or Spirit.
    //    Nothing
    //      Use this to unbind a default key or use the default attack when targeting a monster.
    //    TargetMonster(Number)
    //      Targets the specified monster, starting at 0 for A up to 9 for J.
    //    NextRound
    //      Enters next round. Using this overrides both Space and Enter for next round. If you still want to use one of those, add it manually.
    //    Strongest([Action Array])
    //      Picks the most desired action.
    //      For targeted spells or skills, put the most desired action first.
    //      For untargeted spells or items, put the most desired action last.
    //    HoverAction(Action, true/false)
    //      Point mouse at target monster, hit key to perform action, Nothing for default attack. Set second parameter to true to respect alerts.
    //    Impulse(Action)
    //      Perform an action once.
    //    ToggleHover
    //      Turn hoverplay on or off.
    //    Drops
    //      Show drop log.
    //    CursorUp, CursorDown
    //      Move the targeting cursor.
    //    CursorTarget
    //      Target selected monster. Use with Strongest to specify action.
    //    CursorHover
    //      Engage hover at cursor location.
    //    ClearTarget
    //      Unset hover target.
    //    Settings.
    //      Access configuration interface.
    //
    // Examples:
    // Bind(KEY_M, Shift, Use(1));\ -- Shift + M = Use Item 1
    // Bind(KEY_LBRACKET, Cast('WRATH OF THOR'));\ -- Case insensitive. Key [ = cast Wrath of Thor.
    // Bind(KEY_A, Nothing);\ -- You can unbind a default key.
    // Bind(KEY_I, Use('p'));\ -- I uses Powerup Gem.
    // Bind(KEY_F, Toggle('Focus'));\ -- Toggle focus.
    // Bind(KEY_S, Toggle('Spirit'));\ -- Toggle spirit stance.
    // Bind(KEY_T, Strongest([Cast('Ragnarok'), Cast('Disintegrate'), Cast('Corruption')]));\ -- Strongest Dark spell.
    // Bind(KEY_B, Strongest([Cast('Full-Cure'), Cast('Cure')]));\ -- Use available Cure spell.
    // Bind(KEY_N, Strongest([Use(3), Use(2), Use(1)]));\ -- Use available item.
    // Bind(KEY_A, HoverAction(Nothing));\ -- Attack selected monster by holding A, to be used with hoverAction = false.
    // Bind(KEY_I, HoverAction(Cast('Imperil')));\ -- Cast Imperil on selected monster, ignore alerts.
    // Bind(KEY_I, HoverAction(Strongest([ToggleHover, Cast('Imperil')]), true));\ -- Single cast of Imperil, respect alerts.
    // Bind(KEY_I, Impulse(Cast('Imperil'))); -- Inject Imperil into hover rotation.
    // Bind(KEY_1, Strongest([TargetMonster(0), Cast('Imperil')]));\ -- Cast Imperil on monster A.
    // Bind(KEY_Z, Any, ToggleHover);\ -- Toggle hoverplay, regardless of modifier keys.
    // Bind(KEY_F, Drops);\ -- Show drops.
    // Bind(KEY_UP, CursorUp); -- Move cursor up.
    // Bind(KEY_DOWN, Strongest([CursorDown, ClearTarget]));\ -- Move cursor down, stop hover.
    // Bind(KEY_LEFT, Strongest([CursorTarget, Cast('Imperil')]));\ -- Cast Imperil on selected monster.
    // Bind(KEY_RIGHT, CursorHover);\ -- Hover on selected monster, recommended use with clearRound = true.

    // custom profiles
    // to override defaults, add elements to 'settings' sections
    name: '[persistent]',
    persona: [ { name: 'persona 1', settings: {}, set: [ { name: 'set 1', settings: {} }, { name: 'set 2', settings: {} }, { name: 'set 3', settings: {} },
               { name: 'set 4', settings: {} }, { name: 'set 5', settings: {} }, { name: 'set 6', settings: {} }, { name: 'set 7', settings: {} } ] },
               { name: 'persona 2', settings: {}, set: [ { name: 'set 1', settings: {} }, { name: 'set 2', settings: {} }, { name: 'set 3', settings: {} },
               { name: 'set 4', settings: {} }, { name: 'set 5', settings: {} }, { name: 'set 6', settings: {} }, { name: 'set 7', settings: {} } ] },
               { name: 'persona 3', settings: {}, set: [ { name: 'set 1', settings: {} }, { name: 'set 2', settings: {} }, { name: 'set 3', settings: {} },
               { name: 'set 4', settings: {} }, { name: 'set 5', settings: {} }, { name: 'set 6', settings: {} }, { name: 'set 7', settings: {} } ] },
               { name: 'persona 4', settings: {}, set: [ { name: 'set 1', settings: {} }, { name: 'set 2', settings: {} }, { name: 'set 3', settings: {} },
               { name: 'set 4', settings: {} }, { name: 'set 5', settings: {} }, { name: 'set 6', settings: {} }, { name: 'set 7', settings: {} } ] },
               { name: 'persona 5', settings: {}, set: [ { name: 'set 1', settings: {} }, { name: 'set 2', settings: {} }, { name: 'set 3', settings: {} },
               { name: 'set 4', settings: {} }, { name: 'set 5', settings: {} }, { name: 'set 6', settings: {} }, { name: 'set 7', settings: {} } ] },
               { name: 'persona 6', settings: {}, set: [ { name: 'set 1', settings: {} }, { name: 'set 2', settings: {} }, { name: 'set 3', settings: {} },
               { name: 'set 4', settings: {} }, { name: 'set 5', settings: {} }, { name: 'set 6', settings: {} }, { name: 'set 7', settings: {} } ] },
               { name: 'persona 7', settings: {}, set: [ { name: 'set 1', settings: {} }, { name: 'set 2', settings: {} }, { name: 'set 3', settings: {} },
               { name: 'set 4', settings: {} }, { name: 'set 5', settings: {} }, { name: 'set 6', settings: {} }, { name: 'set 7', settings: {} } ] },
               { name: 'persona 8', settings: {}, set: [ { name: 'set 1', settings: {} }, { name: 'set 2', settings: {} }, { name: 'set 3', settings: {} },
               { name: 'set 4', settings: {} }, { name: 'set 5', settings: {} }, { name: 'set 6', settings: {} }, { name: 'set 7', settings: {} } ] },
               { name: 'persona 9', settings: {}, set: [ { name: 'set 1', settings: {} }, { name: 'set 2', settings: {} }, { name: 'set 3', settings: {} },
               { name: 'set 4', settings: {} }, { name: 'set 5', settings: {} }, { name: 'set 6', settings: {} }, { name: 'set 7', settings: {} } ] } ],
    isekai: { name: '[isekai]', settings: {},
              persona: [ { name: 'persona 1', settings: {}, set: [ { name: 'set 1', settings: {} }, { name: 'set 2', settings: {} }, { name: 'set 3', settings: {} },
                         { name: 'set 4', settings: {} }, { name: 'set 5', settings: {} }, { name: 'set 6', settings: {} }, { name: 'set 7', settings: {} } ] },
                         { name: 'persona 2', settings: {}, set: [ { name: 'set 1', settings: {} }, { name: 'set 2', settings: {} }, { name: 'set 3', settings: {} },
                         { name: 'set 4', settings: {} }, { name: 'set 5', settings: {} }, { name: 'set 6', settings: {} }, { name: 'set 7', settings: {} } ] },
                         { name: 'persona 3', settings: {}, set: [ { name: 'set 1', settings: {} }, { name: 'set 2', settings: {} }, { name: 'set 3', settings: {} },
                         { name: 'set 4', settings: {} }, { name: 'set 5', settings: {} }, { name: 'set 6', settings: {} }, { name: 'set 7', settings: {} } ] },
                         { name: 'persona 4', settings: {}, set: [ { name: 'set 1', settings: {} }, { name: 'set 2', settings: {} }, { name: 'set 3', settings: {} },
                         { name: 'set 4', settings: {} }, { name: 'set 5', settings: {} }, { name: 'set 6', settings: {} }, { name: 'set 7', settings: {} } ] },
                         { name: 'persona 5', settings: {}, set: [ { name: 'set 1', settings: {} }, { name: 'set 2', settings: {} }, { name: 'set 3', settings: {} },
                         { name: 'set 4', settings: {} }, { name: 'set 5', settings: {} }, { name: 'set 6', settings: {} }, { name: 'set 7', settings: {} } ] },
                         { name: 'persona 6', settings: {}, set: [ { name: 'set 1', settings: {} }, { name: 'set 2', settings: {} }, { name: 'set 3', settings: {} },
                         { name: 'set 4', settings: {} }, { name: 'set 5', settings: {} }, { name: 'set 6', settings: {} }, { name: 'set 7', settings: {} } ] },
                         { name: 'persona 7', settings: {}, set: [ { name: 'set 1', settings: {} }, { name: 'set 2', settings: {} }, { name: 'set 3', settings: {} },
                         { name: 'set 4', settings: {} }, { name: 'set 5', settings: {} }, { name: 'set 6', settings: {} }, { name: 'set 7', settings: {} } ] },
                         { name: 'persona 8', settings: {}, set: [ { name: 'set 1', settings: {} }, { name: 'set 2', settings: {} }, { name: 'set 3', settings: {} },
                         { name: 'set 4', settings: {} }, { name: 'set 5', settings: {} }, { name: 'set 6', settings: {} }, { name: 'set 7', settings: {} } ] },
                         { name: 'persona 9', settings: {}, set: [ { name: 'set 1', settings: {} }, { name: 'set 2', settings: {} }, { name: 'set 3', settings: {} },
                         { name: 'set 4', settings: {} }, { name: 'set 5', settings: {} }, { name: 'set 6', settings: {} }, { name: 'set 7', settings: {} } ] } ] }
    };

// ^^^ SETTINGS ^^^ //

// bindable functions
function Toggle(name) {
    return function() {
        var state;
        if ( (state = document.getElementById('ckey_' + name.toLowerCase())) ) {
            dummy.setAttribute('onclick', state.getAttribute('onmouseover'));
            dummy.click();
            state.click(); }};}

function Cast(name) {
    return function() {
        var spell;
        if ( document.getElementsByClassName('btii')[0].innerHTML != name &&
             (spell = document.querySelector('.bts > div[onclick][onmouseover*="\'' + name + '\'"]')) ) {
            dummy.setAttribute('onclick', spell.getAttribute('onmouseover'));
            dummy.click();
            spell.click(); }};}

function Use(id) {
    return function() {
        var item;
        if ( (item = document.getElementById('ikey_' + id)) ) {
            dummy.setAttribute('onclick', item.getAttribute('onmouseover'));
            dummy.click();
            item.click(); }};}

function Nothing() {}

function NextRound() { var btcp; if ( (btcp = document.getElementById('btcp')) ) btcp.click(); document.querySelector('img[src$="finishbattle.png"]').click(); }

function TargetMonster(num) { return function() { if ( monsters[num] && monsters[num].hasAttribute('onclick') ) monsters[num].click(); };}

function Strongest(actions) { return function() { var n = actions.length; while ( n-- > 0 ) actions[n](); };}

function HoverAction(action, alert) {
    return function() {
        override = action;
        if ( (cfg.hoverAction || override) && !interruptHover && (!alert || !interruptAlert) && monsters[target] && monsters[target].hasAttribute('onclick') ) {
            Hover(); }};}

function Impulse(action) {
    return function() {
        if ( done ) return;
        impulse = action;
        if ( interruptHover || interruptAlert || !monsters[target] || !monsters[target].hasAttribute('onclick') ) {
            action(); done = true; impulse = false; }};}

function ToggleHover() {
    interruptHover = !interruptHover;
    if ( (cfg.hoverAction || override) && !interruptHover && !interruptAlert && monsters[target] && monsters[target].hasAttribute('onclick') ) {
        Hover(); }}

function Drops() { ShowDrops(false); }

function CursorUp() { if ( --cursor < 0 ) cursor = 0; Cursor(); }

function CursorDown() { if ( ++cursor >= monsters.length ) cursor = monsters.length - 1; Cursor(); }

function CursorTarget() { TargetMonster(cursor)(); }

function CursorHover() { if ( cursor >= 0 ) SetTarget(cursor)(); }

function ClearTarget() { target = false; }

function Settings() {
    var mainpane, style, form, div, changed = false, p = 0, s = 0, isk = '', select, option, index = 0;
    if ( !(mainpane = document.getElementById('mainpane')) ) return;
    bindings = [];
    mainpane.innerHTML = '';
    if ( !document.getElementById('hvcfgstyle') ) {
        style = document.head.appendChild(document.createElement('style'));
        style.id = 'hvcfgstyle';
        style.innerHTML =
            '.mbar, #cfgbutton { display: none !important; } #mainpane { height: ' + (document.getElementById('navbar') ? '644' : '671') +
            'px; overflow: auto; } #mbcfg div { display: flex; margin: 2px; } #mbcfg input { margin: 0 2px 0 0; } #mbcfg input[type="text"] { width: 30px; }' +
            '#mbcfgbt { display: flex; position: absolute; bottom: 0; left: 0; height: 27px; width: inherit; } #mbcfgbt select { margin: 0; }' +
            '#mbcfgbt div { margin: 4px 0; } #mbcfgbt input[type="checkbox"] { margin-left: 10px; } .help { cursor: pointer; }' +
            '.help .helptext { visibility: hidden; text-align: left; position: absolute; z-index: 2; bottom: 0; right: 0; background-color: #EDEBDF;' +
            'padding: 4px; border: 1px solid #5C0D11; } .help:hover .helptext { visibility: visible; }'; }
    form = mainpane.appendChild(document.createElement('form'));
    form.id = 'mbcfg';
    const Change = function(i, e) {
        return function() {
            changed = true;
            var prf = s ? (isk ? cfg.isekai : cfg).persona[p-1].set[s-1].settings : (p ? (isk ? cfg.isekai : cfg).persona[p-1].settings : (isk ? cfg.isekai.settings : cfg)),
                setting = settingsData[i][0], input = form.querySelector('[name="' + setting + (e ? '-' + e : '') + '"]'), value, inputs, j, above = Above(setting);
            switch ( settingsData[i][1][0] ) {
                case 'b': prf[setting] = input.checked; break;
                case 'o': if ( !prf[setting] ) {
                              prf[setting] = JSON.parse(JSON.stringify(above)); }
                              prf[setting][e] = (value = input.value) == '' ? above[e] : (value == 'false' ? false : value); break;
                case 'a': prf[setting] = (value = input.value) == '' ? above : JSON.parse(value); break;
                case 'i': prf[setting] = (value = parseInt(input.value)) || value === 0 ? value : above; break;
                case 'f': prf[setting] = parseFloat(value = input.value) || parseFloat(value) === 0 ? parseFloat(value) :
                                         settingsData[i][1][1] == 's' && value != '' ? 'auto' : above; break;
                case 's': if ( setting.indexOf('Action') > -1 && regexp.itemuse.test(input.value) ) {
                              alert('Fearsome powers thrust Laputa into orbit. Their dreaded empire once ruled the earth!'); return; }
                case 't': if ( regexp.hoveruse.test(input.value) ) {
                              alert('Fearsome powers thrust Laputa into orbit. Their dreaded empire once ruled the earth!'); return; }
                          prf[setting] = (value = input.value.replace(regexp.whitespace,'')) == '' ? above : value; break; }
            if ( (isk || p) && JSON.stringify(above) === JSON.stringify(prf[setting]) ) {
                if ( e ) {
                    form.querySelector('#' + setting).parentNode.style.opacity = '0.5';
                    inputs = form.querySelectorAll('[name*="' + setting + '-"]');
                    for ( j = 0; j < inputs.length; j++ ) {
                        inputs[j].parentNode.style.opacity = '0.5'; }}
                else {
                    input.parentNode.style.opacity = '0.5'; }
                delete prf[setting]; }
            else {
                if ( e ) {
                    form.querySelector('#' + setting).parentNode.style.opacity = '1';
                    inputs = form.querySelectorAll('[name*="' + setting + '-"]');
                    for ( j = 0; j < inputs.length; j++ ) {
                        inputs[j].parentNode.style.opacity = '1'; }}
                else {
                    input.parentNode.style.opacity = '1'; }}};},
        Load = function(d) {
            LoadCfg();
            form.innerHTML = '';
            var input, value;
            if ( d ) {
                input = form.appendChild(document.createElement('div')).appendChild(document.createElement('textarea'));
                input.name = 'dump';
                input.style.width = '1205px';
                input.style.height = '631px';
                input.style.resize = 'none';
                input.value = JSON.stringify(cfg);
                input.onchange = function() {
                    changed = true;
                    var dump = JSON.parse(input.value);
                    for ( var setting in settings ) {
                        cfg[setting] = (value = dump[setting]) || value === false || value === 0 ? value : settings[setting]; }
                    var option = select.querySelector('[value="00"]');
                    option.innerHTML = cfg.name;
                    for ( var i = 0; i < cfg.persona.length; i++ ) {
                        option = select.querySelector('[value="' + (i+1) + '0"]');
                        option.innerHTML = '- ' + cfg.persona[i].name;
                        for ( var j = 0; j < cfg.persona[i].set.length; j++ ) {
                            option = select.querySelector('[value="' + (i+1) + (j+1) + '"]');
                            option.innerHTML = '-- ' + cfg.persona[i].set[j].name; }}};
                return; }
            for ( var i = 0; i < settingsData.length; i++ ) {
                value = (s ? (isk ? cfg.isekai : cfg).persona[p-1].set[s-1].settings :
                        (p ? (isk ? cfg.isekai : cfg).persona[p-1].settings :
                        (isk ? cfg.isekai.settings : cfg)))[settingsData[i][0]];
                var none = !(value || value === false || value === 0), above = Above(settingsData[i][0]);
                if ( none ) {
                    value = above; }
                var grey = (isk || p) && (none || JSON.stringify(value) === JSON.stringify(above)),
                    div = form.appendChild(document.createElement('div'));
                div.style.opacity = grey ? '0.5' : '1';
                switch ( settingsData[i][1][0] ) {
                    case 'h': div.appendChild(document.createElement('div')).innerHTML = settingsData[i][0];
                              div.style.opacity = '1'; break;
                    case 'b': input = div.appendChild(document.createElement('input'));
                              input.name = settingsData[i][0];
                              input.type = 'checkbox';
                              input.checked = value;
                              input.onchange = Change(i);
                              div.appendChild(document.createElement('div')).innerHTML = settingsData[i][2]; break;
                    case 'o': input = div.appendChild(document.createElement('div'));
                              input.id = settingsData[i][0];
                              input.innerHTML = settingsData[i][2];
                              for ( var element in value ) {
                                  var dv = form.appendChild(document.createElement('div'));
                                  dv.style.opacity = grey ? '0.5' : '1';
                                  input = dv.appendChild(document.createElement('input'));
                                  input.name = settingsData[i][0] + '-' + element;
                                  input.type = 'text';
                                  input.value = value[element];
                                  if ( settingsData[i][4] ) {
                                      input.style.width = settingsData[i][4] + 'px'; }
                                  input.onchange = Change(i,element);
                                  dv.appendChild(document.createElement('div')).innerHTML = element; } break;
                    case 'a': input = div.appendChild(document.createElement('input'));
                              input.name = settingsData[i][0];
                              input.type = 'text';
                              input.value = JSON.stringify(value);
                              input.onchange = Change(i);
                              div.appendChild(document.createElement('div')).innerHTML = settingsData[i][2]; break;
                    case 't': input = div.appendChild(document.createElement('textarea'));
                              input.name = settingsData[i][0];
                              input.rows = 30;
                              input.style.resize = 'none';
                              input.value = value.replace(regexp.break,';\n');
                              input.onchange = Change(i); break;
                     default: input = div.appendChild(document.createElement('input'));
                              input.name = settingsData[i][0];
                              input.type = 'text';
                              input.value = value;
                              input.onchange = Change(i);
                              div.appendChild(document.createElement('div')).innerHTML = settingsData[i][2]; break; }
                if ( settingsData[i][4] ) {
                    input.style.width = settingsData[i][4] + 'px'; }
                if ( p == 0 && settingsData[i][3] ) {
                    var help = div.appendChild(document.createElement('div'));
                    help.className = 'help';
                    help.innerHTML = '?';
                    var helptext = help.appendChild(document.createElement('span'));
                    helptext.className = 'helptext';
                    helptext.innerHTML = settingsData[i][3]; }}},
    Above = function(setting) {
        var value;
        if ( !isk ) {
            return s && ((value = cfg.persona[p-1].settings[setting]) || value === false || value === 0) ? value : (p ? cfg[setting] : settings[setting]); }
        else if ( !cfg.isekaiInherit ) {
            return s && ((value = cfg.isekai.persona[p-1].settings[setting]) || value === false || value === 0) ? value :
                  (p && ((value = cfg.isekai.settings[setting]) || value === false || value === 0) ? value : cfg[setting]); }
        else {
            return s && ((value = cfg.persona[p-1].set[s-1].settings[setting]) || value === false || value === 0) ? value :
                  (s && ((value = cfg.isekai.persona[p-1].settings[setting]) || value === false || value === 0) ? value :
                  (p && ((value = cfg.persona[p-1].settings[setting]) || value === false || value === 0) ? value :
                  (p && ((value = cfg.isekai.settings[setting]) || value === false || value === 0) ? value : cfg[setting]))); }},
    Save = function() { if ( changed ) { changed = false; localStorage.HVmbcfg = JSON.stringify(cfg); }},
    Exit = function() { location.href = location.href; },
    Reset = function() {
        if ( p == 0 && s == 0 && !isk && confirm('Reset to defaults?') ) {
            localStorage.removeItem('HVmbcfg');
            cfg = JSON.parse(JSON.stringify(settings));
            Settings(); }
        else if ( p == 0 && s == 0 && isk && confirm('Reset this profile?') ) {
            cfg.isekai.settings = {};
            Save();
            Load(); }
        else if ( p != 0 && confirm('Reset this profile?') ) {
            (s ? (isk ? cfg.isekai : cfg).persona[p-1].set[s-1] : (isk ? cfg.isekai : cfg).persona[p-1]).settings = {};
            Save();
            Load(); }},
    Switch = function(persona, set, ise) {
        if ( p === persona && s === set && isk === ise ) return true;
        if ( !changed || confirm('Save and change profile?') ) {
            Save();
            p = persona;
            s = set;
            isk = ise;
            Load();
            return true; }
        return false; };
    Load();
    if ( (div = document.getElementById('mbcfgbt')) ) {
        div.parentNode.removeChild(div); }
    div = mainpane.appendChild(document.createElement('div'));
    div.id = 'mbcfgbt';
    var button = div.appendChild(document.createElement('button'));
    button.innerHTML = 'Save';
    button.onclick = function() { Save(); Load(); };
    button = div.appendChild(document.createElement('button'));
    button.innerHTML = 'S&E';
    button.onclick = function() { if ( !changed || confirm('Save and exit?') ) { Save(); Exit(); }};
    button = div.appendChild(document.createElement('button'));
    button.innerHTML = 'Exit';
    button.onclick = function() { if ( !changed || confirm('Exit without saving?') ) { Exit(); }};
    button = div.appendChild(document.createElement('button'));
    button.innerHTML = 'Dump';
    button.onclick = function() { if ( !changed || confirm('Save and dump to text?') ) { Save(); Load(true); }};
    button = div.appendChild(document.createElement('button'));
    button.innerHTML = 'Reset';
    button.onclick = Reset;
    select = div.appendChild(document.createElement('select'));
    select.onchange = function() {
        if ( Switch(parseInt(select.value[0]), parseInt(select.value[1]), select.value[2] ? 'i' : '') ) {
            index = select.selectedIndex; }
        else {
            select.selectedIndex = index; }};
    option = select.appendChild(document.createElement('option'));
    option.value = '00';
    option.innerHTML = cfg.name;
    if ( '' + profile.p + (profile.p ? profile['s' + profile.p] : '0') == option.value ) {
        option.style.color = 'blue'; }
    for ( var i = 0; i < cfg.persona.length; i++ ) {
        option = select.appendChild(document.createElement('option'));
        option.value = (i+1) + '0';
        option.innerHTML = '- ' + cfg.persona[i].name;
        if ( '' + profile.p + (profile.p ? profile['s' + profile.p] : '0') == option.value ) {
            option.style.color = 'blue'; }
        for ( var j = 0; j < cfg.persona[i].set.length; j++ ) {
            option = select.appendChild(document.createElement('option'));
            option.value = '' + (i+1) + (j+1);
            option.innerHTML = '-- ' + cfg.persona[i].set[j].name;
            if ( '' + profile.p + (profile.p ? profile['s' + profile.p] : '0') == option.value ) {
                option.style.color = 'blue'; }}}
    option = select.appendChild(document.createElement('option'));
    option.value = '00i';
    option.innerHTML = cfg.isekai.name;
    if ( '' + profile.ip + (profile.ip ? profile['is' + profile.ip] : '0') + 'i' == option.value ) {
        option.style.color = 'red'; }
    for ( i = 0; i < cfg.isekai.persona.length; i++ ) {
        option = select.appendChild(document.createElement('option'));
        option.value = (i+1) + '0i';
        option.innerHTML = '- ' + cfg.isekai.persona[i].name;
        if ( '' + profile.ip + (profile.ip ? profile['is' + profile.ip] : '0') + 'i' == option.value ) {
            option.style.color = 'red'; }
        for ( j = 0; j < cfg.isekai.persona[i].set.length; j++ ) {
            option = select.appendChild(document.createElement('option'));
            option.value = '' + (i+1) + (j+1) + 'i';
            option.innerHTML = '-- ' + cfg.isekai.persona[i].set[j].name;
            if ( '' + profile.ip + (profile.ip ? profile['is' + profile.ip] : '0') + 'i' == option.value ) {
                option.style.color = 'red'; }}}
    button = div.appendChild(document.createElement('button'));
    button.innerHTML = 'Name';
    button.onclick = function() {
        var prf = s ? (isk ? cfg.isekai : cfg).persona[p-1].set[s-1] : (p ? (isk ? cfg.isekai : cfg).persona[p-1] : (isk ? cfg.isekai : cfg)),
            opt = select.options[index], name;
        if ( (name = prompt('Name this profile?', prf.name)) && name != prf.name ) {
            changed = true;
            prf.name = name;
            opt.innerHTML = (p ? (s ? '-- ' : '- ') : '') + prf.name; }};
    button = div.appendChild(document.createElement('button'));
    button.innerHTML = 'Set';
    button.onclick = function() {
        select.querySelector('[style*="' + (isk ? 'red' : 'blue') + '"]').style.color = '';
        profile[isk + p] = p;
        if (p) {
            profile[isk + 's' + p] = s; }
        if ( JSON.stringify(profile) != localStorage.HVmbp ) {
            localStorage.HVmbp = JSON.stringify(profile); }
        select.querySelector('[value="' + p + s + isk + '"]').style.color = isk ? 'red' : 'blue'; };
    var auto = div.appendChild(document.createElement('input'));
    auto.name = 'profileAutoswitch';
    auto.type = 'checkbox';
    auto.checked = cfg.profileAutoswitch;
    auto.onchange = function() {
        changed = true;
        cfg.profileAutoswitch = auto.checked; };
    var help = div.appendChild(document.createElement('div'));
    help.className = 'help';
    help.innerHTML = 'auto';
    var helptext = help.appendChild(document.createElement('span'));
    helptext.className = 'helptext';
    helptext.innerHTML = 'automatically switch profile when changing persona or equipment set';
    var inherit = div.appendChild(document.createElement('input'));
    inherit.name = 'isekaiInherit';
    inherit.type = 'checkbox';
    inherit.checked = cfg.isekaiInherit;
    inherit.onchange = function() {
        changed = true;
        cfg.isekaiInherit = inherit.checked; };
    help = div.appendChild(document.createElement('div'));
    help.className = 'help';
    help.innerHTML = 'inherit';
    helptext = help.appendChild(document.createElement('span'));
    helptext.className = 'helptext';
    helptext.innerHTML = 'use persistent persona/equipment profiles in isekai'; }

// keybind helper functions
function handleKeys(e) {
    if ( release ) {
        done = false;
        release = false; }
	saveKeyDown();
    shiftHeld = e.shiftKey;
    ctrlHeld = e.ctrlKey;
    altHeld = e.altKey;
    var bind;
	for ( var i = 0; i < bindings.length; i++ ) {
		bind = bindings[i];
		if ( e.keyCode == bind.keyCode && bind.modifier(e) ) {
			bind.action();
			return; }}
	loadKeyDown(); }

function handleKeyup(e) {
    shiftHeld = e.shiftKey;
    ctrlHeld = e.ctrlKey;
    altHeld = e.altKey;
    override = false;
    release = true;
    if ( cfg.hoverAutoresume ) {
        interruptHover = false;
        if ( (cfg.hoverAction || override) && !interruptAlert && monsters[target] && monsters[target].hasAttribute('onclick') ) {
            Hover(); }}
}

function saveKeyDown() { runScript('var oldkeydown = document.onkeydown ? document.onkeydown : oldkeydown; document.onkeydown = null;'); }

function loadKeyDown() { runScript('document.onkeydown = oldkeydown;'); }

function runScript(code) {
	var scriptElement = document.createElement('script');
	scriptElement.type = 'text/javascript';
	scriptElement.textContent = code;
	document.body.appendChild(scriptElement);
	scriptElement.remove(); }

function Bind(key, mod, command) {
    if ( !command ) {
	      command = mod;
	      mod = NoMod; }
    if ( command ) {
        bindings.push(new Keybind(key,mod,command)); }}

function Keybind(key, mod, action) {
    this.keyCode = key;
    this.modifier = mod;
    this.action = action; }

function NoMod(e) { return !e.shiftKey && !e.altKey && !e.ctrlKey && !e.metaKey; }
function CtrlAltShift(e) { return e.shiftKey && e.altKey && e.ctrlKey && !e.metaKey; }
function CtrlShift(e) { return !e.altKey && e.shiftKey && e.ctrlKey && !e.metaKey; }
function AltShift(e) { return !e.ctrlKey && e.altKey && e.shiftKey && !e.metaKey; }
function CtrlAlt(e) { return !e.shiftKey && e.ctrlKey && e.altKey && !e.metaKey; }
function Ctrl(e) { return e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey; }
function Shift(e) { return e.shiftKey && !e.ctrlKey && !e.altKey && !e.metaKey; }
function Alt(e) { return e.altKey && !e.shiftKey && !e.ctrlKey && !e.metaKey; }
function Any(e) { return !e.metaKey; }

// global variables and constants
var cfg = {}, profile = JSON.parse(localStorage.HVmbp || '{}'), isekai = document.URL.indexOf('isekai') > -1 ? 'i' : '',
    bindings = [], gem = [], target = false, cursor = localStorage['HVcursor' + isekai] ? parseInt(localStorage['HVcursor' + isekai]) : -1,
    interruptHover, interruptAlert = false, hovering = false, override = false, impulse = false, done = false,
    release = false, shiftHeld = false, ctrlHeld = false, altHeld = false, bg, dummy = document.createElement('div'),
    mp = 1, sp = 1, monsters = [], monsterData = {}, log, turn, timelog = JSON.parse(localStorage['HVtimelog' + isekai] || '{ "turn":0,"action":0,"round":1,"lastuse":{} }'),
    combatlog = JSON.parse(localStorage['HVcombatlog' + isekai] || '{ "pdealt":{"hit":0,"crit":0,"miss":0,"evade":0,"parry":0,"resist":0,"r50":0,"r75":0,"r90":0},' +
                                                         '"mdealt":{"hit":0,"crit":0,"miss":0,"evade":0,"parry":0,"resist":0,"r50":0,"r75":0,"r90":0},' +
                                                         '"ptaken":{"hit":0,"shit":0,"crit":0,"scrit":0,"miss":0,"evade":0,"parry":0,"block":0,"r50":0,"r75":0,"r90":0},' +
                                                         '"mtaken":{"hit":0,"shit":0,"crit":0,"scrit":0,"miss":0,"evade":0,"parry":0,"block":0,"r50":0,"r75":0,"r90":0},' +
                                                         '"used":{} }'),
    vitals = JSON.parse(localStorage['HVvitals' + isekai] || '{ "hp":0,"mp":0,"sp":0 }'),
    droplog = JSON.parse(localStorage['HVtrackdrops' + isekai] ||
              '{ "Crystals":{}, "Equips":{}, "Mats":{}, "Artifacts":{}, "Figurines":{}, "Trophies":{}, "Consumables":{}, "Foods":{}, "proficiency":{} }'),
    csp = false;
const damage = document.getElementById('2501') ? 'Arcane Focus' : 'Heartseeker',
    KEY_A = 65, KEY_B = 66, KEY_C = 67, KEY_D = 68, KEY_E = 69, KEY_F = 70, KEY_G = 71, KEY_H = 72, KEY_I = 73, KEY_J = 74, KEY_K = 75, KEY_L = 76, KEY_M = 77,
    KEY_N = 78, KEY_O = 79, KEY_P = 80, KEY_Q = 81, KEY_R = 82, KEY_S = 83, KEY_T = 84, KEY_U = 85, KEY_V = 86, KEY_W = 87, KEY_X = 88, KEY_Y = 89, KEY_Z = 90,
    KEY_1 = 49, KEY_2 = 50, KEY_3 = 51, KEY_4 = 52, KEY_5 = 53, KEY_6 = 54, KEY_7 = 55, KEY_8 = 56, KEY_9 = 57, KEY_0 = 48,
    KEY_SPACE = 32, KEY_ENTER = 13, KEY_PAGEUP = 33, KEY_PAGEDOWN = 34, KEY_END = 35, KEY_HOME = 36, KEY_LEFT = 37, KEY_UP = 38, KEY_RIGHT = 39, KEY_DOWN = 40,
    KEY_F1 = 112, KEY_F2 = 113, KEY_F3 = 114, KEY_F4 = 115, KEY_F5 = 116, KEY_F6 = 117, KEY_F7 = 118, KEY_F8 = 119, KEY_F9 = 120, KEY_F10 = 121, KEY_F11 = 122, KEY_F12 = 123,
    KEY_COMMA = 188, KEY_PERIOD = 190, KEY_SLASH = 191, KEY_FORWARDSLASH = 191, KEY_GRAVE = 192, KEY_TILDE = 192, KEY_LBRACKET = 219, KEY_BACKSLASH = 220,
    KEY_SEMI = 186, KEY_RBRACKET = 221, KEY_APOSTROPHE = 222, KEY_SHIFT = 16, KEY_CTRL = 17, KEY_ALT = 18,
    regexp = { gem:/\w(\w+) Gem/, defaultgem:/\w(\w+).gem/, duration:/(x(\d))*[^\(]*, (\d+)/,
        monsters:/MID=\d+[^<>]+HP=\d+/g, monster:/MID=(\d+) \((.+)\) .+ HP=(\d+)/, spellicon:/, '(\w+)'/, defaultfont:/"c\d\w"/,
        turn:/(.+?)<tr><td class="tls">/, action:/>([^<>]+)<\/td><\/tr>(<tr><td class="tlb">Spirit Stance Exhausted<\/td><\/tr>)*<tr><td class="tls"/,
        zeroturn:/You use\s*\w* (Gem|Draught|Potion|Elixir|Drink|Candy|Infusion|Scroll|Vase|Gum)/, use:/You (cast|use) ([\w\s-]+)/,
        damage:/[^<>]+damage( \([^<>]+\))*(<\/td><\/tr><tr><td class="tlb">Your spirit shield absorbs \d+ |<|\.)/g,
        type:/for (\d+) (\w+) damage/, shield:/absorbs (\d+)/, strike:/(Fire|Cold|Wind|Elec|Holy|Dark|Void) Strike hits/,
        dot:/for (\d+) damage/, pdot:/(Bleeding Wound|Spreading Poison)/, points:/for (\d+) points of (\w+) damage/,
        mdmiss:/to connect./g, mdevade:/evades your spell./g, md50:/ hits [^y][^<>]+50%/g, md75:/ hits [^y][^<>]+75%/g, md90:/ hits [^y][^<>]+90%/g,
        mdresist:/resists your spell./g, pdmiss:/its mark./g, pdevade:/evades your attack./g, pdparry:/parries your attack./g, mtevade:/ casts [^<>]+evade the attack./g,
        mtblock:/ casts [^<>]+block the attack./g, mt50:/ hits y[^<>]+50%/g, mt75:/ hits y[^<>]+75%/g, mt90:/ hits y[^<>]+90%/g, ptmiss:/misses the attack against you./g,
        ptevade:/(>You evade| uses [^<>]+evade the attack.)/g, ptparry:/(>You parry| uses [^<>]+parry the attack.)/g, ptblock:/(>You block| uses [^<>]+block the attack.)/g,
        counter:/>You counter/g, spellinfo:/\('([\w\s-]+)'.*, (\d+)\)/, crit:/(You crit| crits | blasts )/,
        miss:/(You evade|You block|You parry| evades | parries | resists | misses | fails )/, attack:/(You hit|You crit|Arcane Blow)/,
        spell:/(You cast| hits | crits | blasts |gains the effect)/, recovery:/(You are healed|Recovered | restores |You drain)/,
        round:/Round (\d+) \/ (\d+)/, drops: /\S+ <span style="color:.{7}">\[[^<>]+\](<\/span><\/td><\/tr><tr><td class="tlb">A t)*/g,
        drop:/(\S+) \<.*#(.{6}).*\[(.*)\](.)*/, crystal:/(?:(\d+)x )?Crystal/, crystals:/Crystal of (\w+)/, credit:/(\d+) Credit/, reward: /gain (\d+) Credit/,
        exp:/You gain (\d+) EXP/, proficiencies:/\d+\.\d+ points of [^<>]+ proficiency/g, proficiency:/(\d+\.\d+) points of ([^<>]+) proficiency/,
        quality:[/\[\]/,/\[Peerless/,/\[(Legendary|Peerless)/,/\[(Magnificent|Legendary|Peerless)/,/\[(Exquisite|Magnificent|Legendary|Peerless)/,
                 /\[(Superior|Exquisite|Magnificent|Legendary|Peerless)/,/\[(Average|Superior|Exquisite|Magnificent|Legendary|Peerless)/,
                 /\[(Fair|Average|Superior|Exquisite|Magnificent|Legendary|Peerless)/,/\[(Crude|Fair|Average|Superior|Exquisite|Magnificent|Legendary|Peerless)/],
        defaultstring:/"c\d\w"/g, defaultletter:/"c\d(\w)"/, whitespace:/(\s{2,}|\n)/g, break:/;/g, number:/\d+/, profile:/settings":{"/,
        itemuse:/Use\(/, hoveruse:/HoverAction\([^;]*Use\(/ },
    settingsData = [
        ['ui settings','h'],
        ['cfgButton','b','show button to access settings and switch profiles during battle'],
        ['mpboost','f','mp boost from abilities and capacitor'],
        ['spboost','f','sp boost from sp tank'],
        ['mppot','i','percentage of base mp restored by mana potion'],
        ['sppot','i','percentage of base sp restored by spirit potion'],
        ['showCooldowns','b','show cooldowns on the quickbar'],
        ['effectsAboveMonsters','b','move player effects to above monsters'],
        ['vitalsAboveMonsters','b','move vitals and spirit button to above monsters'],
        ['quickbarBesideMonsters','b','move quickbar to the right of monsters'],
        ['riddleRight','b','shrink riddlemaster to the right'],
        ['condenseLeft','b','activate the above four options with everything moved to the left'],
        ['compactQuickbar','b','eliminate whitespace on quickbar and monster bars'],
        ['expireNoblink','b','disable blinking of expiring effects'],
        ['showDurations','b','show effect durations'],
        ['stackBorder','b','show effect stacks as border thickness rather than numbers'],
        ['alertColours','b','change background colours according to alert conditions'],
        ['alertBackground','b','whole background instead of relevant area for alert colours'],
        ['alertBuffs','s','critical buffs',
         'change colour of player effects area if any of these buffs has less than 2 turns left<br>' +
         'add the icon filename of any critical buff. this also affects interrupting hover on expiring buffs<br>' +
         'to get the filename, activate the buff in battle, right click its icon and inspect element',750],
        ['colours','o','alert and log colours','set to any valid html colour, or false to disable',120],
        ['usableBlink','b','mana and spirit gems and potions on the quickbar blink when usable to their full effect'],
        ['logColours','b','add colour highlights to the battle log'],
        ['turnDividers','b','add horizontal row between turns'],
        ['logPasteover','b','add last turn of previous round to new round log. requires round advance via ajax'],
        ['hideLog','b','hide the battle log'],
        ['maxVitals','b','show maximum player vitals'],
        ['showMonsterHP','b','display current and max hp of monsters'],
        ['shortenHPbars','b','shorten monster hp bars relative to their max hp'],
        ['monsterNumbers','b','show monster numbers instead of letters'],
        ['monsterKeywords','s','monster keywords',
         'highlight monsters where the name, id or max hp match this expression, set to false to disable<br>' +
         'example: /(Meiling|MID=70699|HP=243060)/',750],
        ['ajaxRound','b','advance to next round using ajax. set to false if you use other scripts that do not support this'],
        ['ajaxIntervals','i','set to 0 or a higher number if you experience weird flashing of expiring effects'],
        ['noPopup','b','skip end of round popup'],
        ['stopAtBattleEnd','b','do not dismiss popup at end of battle'],
        ['stopOnEquipDrop','b','do not dismiss popup if equipment drops, quality specified by cutoff below'],
        ['clickableRiddlemaster','b','add links to the riddlemaster to directly submit an answer'],
        ['edConfirm','b','ask for confirmation before using energy drink'],
        ['fleeConfirm','b','ask for confirmation before fleeing'],
        ['raiseGem','b','raise gem icon above quickbar'],
        ['hoverGem','b','activate gem by hovering over the icon'],
        ['hoverSpirit','b','activate spirit stance by hovering over the icon'],
        ['quickbarExtend','a','extend quickbar',
         'id for skills/spells/items (in quotes), 0 for space, 1 for gem<br>' +
         'set to [1] if you just want the gem icon or [] to disable<br>' +
         'IDs:<br>' +
         '1001 Flee 1011 Scan 1101 FUS RO DAH 1111 Orbital Friendship Cannon<br>' +
         '2201 Shield Bash 2202 Vital Strike 2203 Merciful Blow<br>' +
         '2301 Great Cleave 2302 Rending Blow 2303 Shatter Strike<br>' +
         '2401 Iris Strike 2402 Backstab 2403 Frenzied Blows<br>' +
         '2101 Skyward Sword 2501 Concussive Strike<br>' +
         '111 Fiery Blast 112 Inferno 113 Flames of Loki<br>' +
         '121 Freeze  122 Blizzard 123 Fimbulvetr<br>' +
         '131 Shockblast 132 Chained Lightning 133 Wrath of Thor<br>' +
         '141 Gale 142 Downburst 143 Storms of Njord<br>' +
         '151 Smite 152 Banishment 153 Paradise Lost<br>' +
         '161 Corruption 162 Disintegrate 163 Ragnarok<br>' +
         '211 Drain 212 Weaken 213 Imperil<br>' +
         '221 Slow 222 Sleep 223 Confuse<br>' +
         '231 Blind 232 Silence 233 MagNet<br>' +
         '311 Cure 312 Regen 313 Full-Cure<br>' +
         '411 Protection 412 Haste 413 Shadow Veil<br>' +
         '421 Absorb 422 Spark of Life 423 Spirit Shield<br>' +
         '431 Heartseeker 432 Arcane Focus<br>' +
         'ikey_1-ikey_15 items ikey_s1-ikey_s6 scrolls ikey_n1-ikey_n6 infusions',750],
        ['mouse binding and hover settings','h'],
        ['clickEverywhere','b','enable middle and right click bindings and disable the context menu everywhere'],
        ['wheelEverywhere','b','enable wheel bindings everywhere'],
        ['mouseEngage','b','hold mouse buttons to modify hover behaviour'],
        ['clickLeft','s','left click',
         'set to false to leave at default, or Nothing to attack with the above option',750],
        ['clickMiddle','s','middle click',,750],
        ['clickRight','s','right click',,750],
        ['wheelUp','s','wheel up',,750],
        ['wheelDown','s','wheel down',,750],
        ['wheelLeft','s','wheel left',,750],
        ['wheelRight','s','wheel right',,750],
        ['hoverAction','s','hover action',,750],
        ['hoverShiftAction','s','shift hover action',,750],
        ['hoverCtrlAction','s','ctrl hover action',,750],
        ['hoverAltAction','s','alt hover action',
         'these can be set to any bindable action that can be followed up with targeting a monster,<br>' +
         'as explained in the keybind section, or to false to disable<br>' +
         'examples:<br>' +
         'hover action: false - disable hover<br>' +
         'hover action: Nothing - attack<br>' +
         "hover action: Strongest([Cast('Ragnarok'), Cast('Disintegrate'), Cast('Corruption')]) - dark spell rotation<br>" +
         "shift hover action: Strongest([ToggleHover, Cast('Imperil')]) - single cast of imperil<br>" +
         'with the above example, you can hold shift, hover and hit Z to cast imperil while being protected by the usual hover safeguards',750],
        ['hoverArea','i','part of the monster that activates hover',
         '1: whole box, 2: icon, 3: name, 4: vitals, 6: status effects'],
        ['hoverplay interrupt settings','h'],
        ['startRoundWithHover','b','have hoverplay active at the beginning of each round'],
        ['hoverAutoresume','b','reactivate hover after releasing any key'],
        ['minHP','f','hp threshold'],
        ['minMP','f','mp threshold'],
        ['minSP','fs','sp threshold',
         "formula when set to auto: 0.5-0.5*spboost/(spboost+100)"],
        ['stopOnEmergency','b','stop on spark or low vitals'],
        ['stopOnBuffsExpiring','b','stop on critical buff expiring in 1 turn or less'],
        ['mobile settings','h'],
        ['clearRound','b','clear target and reset hover action at round transition, to avoid lingering taps and make monster bar safer'],
        ['spacedBar','b','increase spacing of quickbar and monster bar'],
        ['monsterBar','a','monster bar',
         'add skill/spell icons next to monsters, for single use or tap to engage/tap elsewhere to disengage, single skills and spell rotations<br>' +
         'leave empty to disable. example: first parameter is false for single use or true to engage, followed by skill/spell IDs or leave empty for attack<br>' +
         '[ [true],                    - engage attack<br>' +
         "  [false,'213'],             - single cast of imperil<br>" +
         "  [false,'212'],             - single cast of weaken<br>" +
         "  [true,'163','162','161'] ] - engage dark spell rotation",750],
        ['tracking settings','h'],
        ['trackDrops','b','show total numbers of drops and exp at end of battle'],
        ['detailedDroplog','b','list each drop type individually, excluding crystals and equipment below your quality cutoff'],
        ['detailedCrystlog','b','list each crystal type individually'],
        ['equipmentCutoff','i','equipment quality cutoff',
         '0 to track all equipment combined,<br>' +
         '1 to track Peerless separateley,<br>' +
         '2 to track Peerless and Legendary separateley, etc.'],
        ['selectLog','b','limit end-of-battle onclick to icon, allowing for easier selecting of log'],
        ['terseLog','b','format log for easier pasting into spreadsheets'],
        ['trackProficiency','b','show total proficiency gains at end of battle'],
        ['proficiencySidebar','b','show live proficiency gains during battle'],
        ['profbarInMainpane','b','proficiency sidebar in mainpane'],
        ['deleteDropLog','i','delete drop log, 0: never, 1: when navigating away from battle section, 2: at end of battle'],
        ['dropFontSize','i','adjust font size of drop and proficiency log'],
        ['trackSpeed','b','show turn count and speed statistic at end of battle'],
        ['speedFontSize','i','adjust font size of speed statistic'],
        ['trackDamage','b','show damage dealt and taken at end of battle'],
        ['damageFontSize','i','adjust font size of damage log'],
        ['trackUsage','b','show attack/skill/spell/item usage at end of battle'],
        ['deleteCombatLog','i','delete damage and usage logs, 0: never, 1: when navigating away from battle section, 2: at end of battle'],
        ['consoleLog','b','output raw machine-readable log data to console when showing the drop log'],
        ['showRound','b','show the current round number during battle'],
        ['bigRoundCounter','b','bigger round counter, placed in top right'],
        ['key bindings','h',,
         'Add this for as many bindings as you want:<br>' +
         'Bind(KeyCode, Modifier, Action);<br>' +
         ' KeyCode = From http://www.javascripter.net/faq/keycodes.htm or any of the following:<br>' +
         ' KEY_A, KEY_B, KEY_C, KEY_D, KEY_E, KEY_F, KEY_G, KEY_H, KEY_I, KEY_J, KEY_K, KEY_L, KEY_M,<br>' +
         ' KEY_N, KEY_O, KEY_P, KEY_Q, KEY_R, KEY_S, KEY_T, KEY_U, KEY_V, KEY_W, KEY_X, KEY_Y, KEY_Z,<br>' +
         ' KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8, KEY_9, KEY_0,<br>' +
         ' KEY_SPACE, KEY_ENTER, KEY_PAGEUP, KEY_PAGEDOWN, KEY_END, KEY_HOME, KEY_LEFT, KEY_UP, KEY_RIGHT, KEY_DOWN,<br>' +
         ' KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10, KEY_F11, KEY_F12,<br>' +
         ' KEY_COMMA, KEY_PERIOD, KEY_SLASH, KEY_FORWARDSLASH, KEY_GRAVE, KEY_TILDE, KEY_LBRACKET, KEY_BACKSLASH,<br>' +
         ' KEY_SEMI, KEY_RBRACKET, KEY_APOSTROPHE, KEY_SHIFT, KEY_CTRL, KEY_ALT<br>' +
         ' Modifier = This is OPTIONAL. Valid mods are NoMod, Shift, Ctrl, Alt, CtrlShift, AltShift, CtrlAlt, CtrlAltShift, Any<br>' +
         ' Action = Valid actions:<br>' +
         "   Cast('Spell Name')<br>" +
         '     Spell Name.<br>' +
         "   Use('Item ID')<br>" +
         "     Valid Item IDs are 'p' for Gem, 1-15 for Items, 's1'-'s6' for Scrolls and 'n1'-'n6' for Infusions.<br>" +
         "   Toggle('Type')<br>" +
         '     Attack, Focus, Defend or Spirit.<br>' +
         '   Nothing<br>' +
         '     Use this to unbind a default key or use the default attack when targeting a monster.<br>' +
         '   TargetMonster(Number)<br>' +
         '     Targets the specified monster, starting at 0 for A up to 9 for J.<br>' +
         '   NextRound<br>' +
         '     Enters next round. Using this overrides both Space and Enter for next round. If you still want to use one of those, add it manually.<br>' +
         '   Strongest([Action Array])<br>' +
         '     Picks the most desired action.<br>' +
         '     For targeted spells or skills, put the most desired action first.<br>' +
         '     For untargeted spells or items, put the most desired action last.<br>' +
         '   HoverAction(Action, true/false)<br>' +
         '     Point mouse at target monster, hit key to perform action, Nothing for default attack. Set second parameter to true to respect alerts.<br>' +
         '    Impulse(Action)<br>' +
         '      Perform an action once.<br>' +
         '   ToggleHover<br>' +
         '     Turn hoverplay on or off.<br>' +
         '   Drops<br>' +
         '     Show drop log.<br>' +
         '   CursorUp, CursorDown<br>' +
         '     Move the targeting cursor.<br>' +
         '   CursorTarget<br>' +
         '     Target selected monster. Use with Strongest to specify action.<br>' +
         '   CursorHover<br>' +
         '     Engage hover at cursor location.<br>' +
         '   ClearTarget<br>' +
         '     Unset hover target.<br>' +
         '   Settings.<br>' +
         '     Access configuration interface.'],
        ['bind','t',,,750]
    ];

// main function triggered on page load
function Enhance() {
    if ( document.getElementById('gay_sex') ) return;
    Riddlemaster();
    if ( !(log = document.getElementById('textlog')) ) return;
    if ( cfg.logPasteover && turn ) {
        log.firstChild.innerHTML += '<tr><td class="tls"></td></tr>' + turn; }
    monsters = document.getElementsByClassName('btm1');
    if ( cfg.alertBackground ) {
        csp = document.getElementById('csp'); }
    interruptHover = !cfg.startRoundWithHover;
    if ( cursor >= 0 ) ClearTarget();
    if ( cursor >= monsters.length ) cursor = monsters.length - 1;
    document.addEventListener('keydown', handleKeys, true);
    document.addEventListener('keyup', handleKeyup, true);
    if ( cfg.clearRound ) {
        Disengage(); }
    document.addEventListener('mouseover', ClearTarget, true);
    if ( cfg.clickEverywhere ) {
        document.addEventListener('mousedown', HandleClick(), true);
        document.addEventListener('contextmenu', function(e) { e.preventDefault(); }); }
    if ( cfg.wheelEverywhere ) {
        document.addEventListener('wheel', HandleWheel(), true); }
    if ( cfg.mouseEngage ) {
        document.addEventListener('mousedown', MouseEngage, true);
        document.addEventListener('mouseup', function() { override = false; release = true; }, true); }
    else {
        document.addEventListener('mouseup', function() { release = true; }, true); }
    var obs = new MutationObserver(Observe);
    obs.observe(log.firstChild, {childList: true});
    if ( !document.getElementById('homosex') ) {
        if ( (cfg.mouseEngage && (regexp.itemuse.test(cfg.clickLeft) || regexp.itemuse.test(cfg.clickMiddle) || regexp.itemuse.test(cfg.clickRight))) ||
             regexp.itemuse.test(cfg.hoverAction) || regexp.itemuse.test(cfg.hoverShiftAction) || regexp.itemuse.test(cfg.hoverCtrlAction) ||
             regexp.itemuse.test(cfg.hoverAltAction) || regexp.hoveruse.test(cfg.bind) ) {
            alert('You have transgressed against your God and your fellow Man. God has charged me with your redemption. ' +
                  'You are hereby Exiled to Wraeclast where, it is hoped, you shall come to repent your Sins, and make your peace with your beloved Father.'); return; }
        cfg.minSP = cfg.minSP == 'auto' ? 0.5-0.5*cfg.spboost/(cfg.spboost+100) : cfg.minSP;
        var evals = ['alertBuffs', 'monsterKeywords', 'clickLeft', 'clickMiddle', 'clickRight', 'wheelUp', 'wheelDown', 'wheelLeft', 'wheelRight',
                     'hoverAction', 'hoverShiftAction', 'hoverCtrlAction', 'hoverAltAction'];
        for ( var i = 0; i < evals.length; i++ ) {
            try {
                cfg[evals[i]] = eval(cfg[evals[i]]); }
            catch ( error ) {
                cfg[evals[i]] = false;
                alert('invalid ' + evals[i] + ' format. option disabled'); }}
        try {
            eval(cfg.bind); }
        catch ( error ) {
            bindings = [];
            alert('invalid key bindings. option disabled'); }
        var style = document.createElement('style');
        style.id = 'homosex';
        style.innerHTML = '.btm6 { min-width: 195px; top: 0 !important; padding: 18px 3px 3px 1px !important; }' +
            (cfg.condenseLeft ? '#pane_effects { position: relative; left: -92px; } #pane_completion { left: -688px; } #battle_left { left: 550px; }' +
            '#pane_vitals { position: absolute; top: 3px; left: ' + (document.querySelector('img[src$="bar_orange.png"]') ?
            '-556px; } #ckey_spirit { left: -411px; top: 45px;' : '-548px; } #ckey_spirit { left: -459px; top: 2px;') +
            'position: absolute; z-index: 1; } #battle_right { left: -7px !important; top: 107px !important; }' +
            '#pane_quickbar { display: flex; position: absolute; left: -212px; top: 69px; width: 203px; }' :
            (cfg.effectsAboveMonsters ? '#pane_effects { max-width: 534px; position: relative; left: 596px; }' : '') +
            (cfg.vitalsAboveMonsters ? '#pane_vitals { position: absolute; top: 3px; left: ' + (document.querySelector('img[src$="bar_orange.png"]') ?
            '672px; } #ckey_spirit { left: 817px; top: 45px;' : '690px; } #ckey_spirit { left: 779px; top: 2px;') +
            'position: absolute; z-index: 1; } #battle_right { top: 107px !important; }' : '') +
            (cfg.quickbarBesideMonsters ? '#pane_quickbar { display: flex; position: absolute; left: 1026px; top: ' + (cfg.vitalsAboveMonsters ? '69' : '4') +
            'px; width: 203px; }' : '')) + (cfg.compactQuickbar ? '.btqs { width: 30px !important; height: 32px !important; padding: 0 !important;' +
            'border: 1px solid black; } .btqs:not(.mbs):not([onmouseover]) { opacity: 0; } .btqi { left: 0 !important; top: 0 !important; }' +
            '.btqs:not(.extend):not(.mbs):not([onmouseover]), .btqb { display:none; }' : '.btqi { top: 3px !important; }') +
            (cfg.expireNoblink ? '#pane_effects > img, .btm6 > img { opacity: 1 !important; }' : '') +
            '.effect_duration { display: inline-block; width: 30px; margin-right: -30px; position: relative; text-align: center; z-index: 1; }' +
            '.effect_duration > div { display: inline-block; min-width: 16px; padding: 0 2px; background: #EDEBDF; color: black; font-weight: bold; }' +
            '.effect_duration > div { border: 1px solid black; } .cooldown {' + (cfg.compactQuickbar ? 'width: 30px; margin-top: 4px;' :
            'width: 37px; margin-top: 7px;') + 'position: relative; z-index: 3; color: black; font-size: 20px; font-weight: bold; }' +
            '#infopane, .bttp, .btm1 { background: ' + cfg.colours.default + '; }' + (cfg.hideLog ? '#textlog { display: none; }' : '') +
            (cfg.noPopup ? '#btcp { display: none; }' : '') + '.drop { font-size: ' + cfg.dropFontSize + '%; } .speed { font-size: ' + cfg.speedFontSize + '%; }' +
            '.crystal { color: #BA05B4; } .credit { color: #A89000; } .equipment { color: #FF0000; } .token { color: #254117; }' +
            '.artifact { color: #0000FF; } .trophy { color: #461B7E; } .consumable { color: #00B000; } .food { color: #489EFF; } ' +
            '#pane_effects, #ckey_spirit { border-radius: 5px; }' + (cfg.spacedBar ? '.btqs { margin: 0 20px 20px 0 !important; }' : '') +
            (cfg.colours.usable ? '.usable { background: ' + cfg.colours.usable + ';' + (cfg.compactQuickbar ? 'border: 1px solid ' + cfg.colours.usable :
            'border-radius: 5px') + '; } .usable > .btqb { display: none; }' : '') + '.monsterhp { display: inline-block; position: relative; left: 204px; top: -20px; }' +
            (cfg.usableBlink ? '@keyframes blink { 50% { opacity: 0; }} .usable > .btqi { animation: blink 1s linear infinite; }' : '') +
            '#battle_right { position: absolute; left: 681px; top: 42px; width: 364px; overflow: visible; }' +
            (cfg.monsterNumbers ? '.btm2 img { display: none; } .btm2 > div:nth-child(1) { font-size: 250%; }' +
            '#mkey_1 > .btm2 > div:nth-child(1):after { content: "1"; } #mkey_2 > .btm2 > div:nth-child(1):after { content: "2"; }' +
            '#mkey_3 > .btm2 > div:nth-child(1):after { content: "3"; } #mkey_4 > .btm2 > div:nth-child(1):after { content: "4"; }' +
            '#mkey_5 > .btm2 > div:nth-child(1):after { content: "5"; } #mkey_6 > .btm2 > div:nth-child(1):after { content: "6"; }' +
            '#mkey_7 > .btm2 > div:nth-child(1):after { content: "7"; } #mkey_8 > .btm2 > div:nth-child(1):after { content: "8"; }' +
            '#mkey_9 > .btm2 > div:nth-child(1):after { content: "9"; } #mkey_0 > .btm2 > div:nth-child(1):after { content: "10"; }' : '') +
            (cfg.colours.miss ? '.miss { color: ' + cfg.colours.miss + '; }' : '') + (cfg.colours.damage ? '.damage { color: ' + cfg.colours.damage + '; }' : '') +
            (cfg.colours.item ? '.item { color: ' + cfg.colours.item + '; }' : '') + (cfg.colours.attack ? '.attack { color: ' + cfg.colours.attack + '; }' : '') +
            (cfg.colours.spell ? '.spell { color: ' + cfg.colours.spell + '; }' : '') + (cfg.colours.recovery ? '.recovery { color: ' + cfg.colours.recovery + '; }' : '') +
            (cfg.colours.effect ? '.effect { color: ' + cfg.colours.effect + '; }' : '') + (cfg.colours.spirit ? '.spirit { color: ' + cfg.colours.spirit + '; }' : '') +
            (cfg.colours.proficiency ? '.proficiency { color: ' + cfg.colours.proficiency + '; }' : '') +
            '#profbar { position: absolute; left: ' + (cfg.profbarInMainpane ? '1140' : '1240') + 'px; top: 50px; z-index: 1; }' +
            '#profbar td:nth-child(1) { text-align: right; } #profbar td:nth-child(2) { text-align: left; } .mbar { display: flex; position: absolute; left: 1075px; }' +
            '.mbar > div { cursor: pointer; } #cfgbutton { position: absolute; top: 686px; left: ' + (cfg.condenseLeft ? '529' : '1220') +
            'px; cursor: pointer; background: ' + cfg.colours.default + '; z-index: 3; width: 18px; height: 18px; font-size: 17px; }' +
            '#cfgbutton:hover #mbprofile { visibility: visible; } #mbprofile { visibility: hidden; position: absolute; right: 0; bottom: 18px;' +
            'width: max-content; height: max-content; padding: 5px; background: ' + cfg.colours.default + '; border: 1px solid #5C0D11; }' +
            '#mbar_0 { top: ' + ((cfg.showMonsterHP ? 61 : 58)+(cfg.vitalsAboveMonsters ? 65 : 0)) + 'px; }' +
            '#mbar_1 { top: ' + ((cfg.showMonsterHP ? 119 : 116)+(cfg.vitalsAboveMonsters ? 65 : 0)) + 'px; }' +
            '#mbar_2 { top: ' + ((cfg.showMonsterHP ? 177 : 174)+(cfg.vitalsAboveMonsters ? 65 : 0)) + 'px; }' +
            '#mbar_3 { top: ' + ((cfg.showMonsterHP ? 235 : 232)+(cfg.vitalsAboveMonsters ? 65 : 0)) + 'px; }' +
            '#mbar_4 { top: ' + ((cfg.showMonsterHP ? 293 : 290)+(cfg.vitalsAboveMonsters ? 65 : 0)) + 'px; }' +
            '#mbar_5 { top: ' + ((cfg.showMonsterHP ? 351 : 348)+(cfg.vitalsAboveMonsters ? 65 : 0)) + 'px; }' +
            '#mbar_6 { top: ' + ((cfg.showMonsterHP ? 409 : 406)+(cfg.vitalsAboveMonsters ? 65 : 0)) + 'px; }' +
            '#mbar_7 { top: ' + ((cfg.showMonsterHP ? 467 : 464)+(cfg.vitalsAboveMonsters ? 65 : 0)) + 'px; }' +
            '#mbar_8 { top: ' + ((cfg.showMonsterHP ? 525 : 522)+(cfg.vitalsAboveMonsters ? 65 : 0)) + 'px; }' +
            '#mbar_9 { top: ' + ((cfg.showMonsterHP ? 583 : 580)+(cfg.vitalsAboveMonsters ? 65 : 0)) + 'px; }' +
            (cfg.maxVitals ? '#dvrhd, #dvrhb, #dvrm, #dvrs { left: -45px; width: 100px; } #vrm { left: -63px; width: 100px; }' : '') +
            '#table_magic .fc2 { font-size: 7pt; } #dropbox { position: absolute; top: 35px; left: 583px; width: 360px;' +
            'margin: 2px 0 0 15px; padding: 5px 1px 1px; border: 2px ridge #5C0D12; background: #F2EFDF; opacity: 0.95; z-index: 9; }' +
            '#damagelog { border: 1px solid; border-collapse: collapse; } #damagelog tr:nth-child(1) > td, #damagelog tr:nth-child(2) > td { text-align: center; }' +
            '#damagelog td:nth-child(1) { text-align: left; } #damagelog td { min-width: 50px; padding: 2px 4px; border-left: 1px solid; text-align: right; font-size: ' +
            cfg.damageFontSize + '%; }';
        document.head.appendChild(style); }
    Gems();
    Alerts();
    Durations();
    GetMonsterData();
    Monsters();
    Confirm();
    ExtendQuickbar();
    MonsterBar();
    ShowCooldowns();
    MaxVitals();
    Profbar();
    ShowRound();
    CfgButton(); }

// main function triggered on new turn
function Observe() {
    // check for battle end
    if ( document.querySelector('img[src$="finishbattle.png"]') ) {
        if ( cfg.alertColours ) {
            document.getElementById(cfg.alertBackground ? 'csp' : 'pane_vitals').style.background = cfg.colours.default;
            document.getElementById('pane_effects').style.background = cfg.colours.default;
            document.getElementById('ckey_spirit').style.background = cfg.colours.default; }
        ProcessLog();
        FormatLog();
        TrackDrops();
        Profbar();
        ShowDrops(true);
        ShowUsage();
        ShowDamage();
        window.dispatchEvent(new CustomEvent("battleEnd", {"detail":{timelog,combatlog,droplog}}));
        window.removeEventListener('beforeunload', StoreTmp);
        var btcp, equip = document.querySelector('span[style$="#FF0000"]');
        localStorage.removeItem('HVmonsterData' + isekai);
        localStorage.removeItem('HVtimelog' + isekai);
        localStorage.removeItem('HVvitals' + isekai);
        localStorage.removeItem('HVcursor' + isekai);
        if ( cfg.deleteDropLog == 2 ) {
            localStorage.removeItem('HVtrackdrops' + isekai); }
        else if ( cfg.trackDrops || cfg.trackProficiency || cfg.proficiencySidebar ) {
            localStorage['HVtrackdrops' + isekai] = JSON.stringify(droplog); }
        if ( cfg.deleteCombatLog == 2 ) {
            localStorage.removeItem('HVcombatlog' + isekai); }
        else if ( cfg.trackDamage || cfg.trackUsage ) {
            localStorage['HVcombatlog' + isekai] = JSON.stringify(combatlog); }
        document.getElementById('homosex').innerHTML += '.mbar, #cfgbutton { display: none !important; }';
        if ( cfg.noPopup && (btcp = document.getElementById('btcp')) && !cfg.stopAtBattleEnd &&
             (!cfg.stopOnEquipDrop || !equip) ) {
            btcp.click();
            document.querySelector('img[src$="finishbattle.png"]').click(); }
        return; }
    hovering = false;
    Gems();
    Alerts();
    Durations();
    Monsters();
    Confirm();
    ExtendQuickbar();
    ProcessLog();
    ShowCooldowns();
    MaxVitals();
    FormatLog();
    TrackDrops();
    Profbar();
    NoPopup(); }

// combat helper functions
function Riddlemaster() {
    var bot;
    if ( !cfg.clickableRiddlemaster || !(bot = document.getElementById('riddlebot')) ) return;
    if ( cfg.trackSpeed ) {
        timelog.horse = (timelog.horse ? timelog.horse : 0) + 1;}
    if ( !document.getElementById('buttsex') ) {
        var style = document.createElement('style');
        style.id = 'buttsex';
        style.innerHTML =
            (cfg.condenseLeft || cfg.riddleRight ? '#riddlemaster { left: ' + (cfg.condenseLeft ? '-370' : '340') + 'px; }' +
            '#riddlebot > img { width: 500px; } .riddlelink { display: inline-block; position: absolute; top: 44px; }' +
            '.riddlelink:nth-child(2) { left: 128px; } .riddlelink:nth-child(3) { left: 288px; } .riddlelink:nth-child(4) { left: 448px; }' :
            '.riddlelink { display: inline-block; position: relative; top: -585px; }' +
            '.riddlelink:nth-child(2) { left: -80px; } .riddlelink:nth-child(4) { left: 80px; }') +
            '.riddlelink > div { width: 0; height: 0; border-left: 20px solid transparent;' +
            'border-right: 20px solid transparent; border-bottom: 20px solid #5C0D11; margin: 20px 50px 0; }';
        document.head.appendChild(style); }
    var a = document.createElement('a');
    a.id = 'gay_sex';
    a.className = 'riddlelink';
    a.href = 'javascript:void(0)';
    a.appendChild(document.createElement('div'));
    a.setAttribute('onclick', 'document.getElementById("riddleanswer").value="A";document.getElementById("riddleform").submit()');
    bot.appendChild(a);
    a = document.createElement('a');
    a.className = 'riddlelink';
    a.href = 'javascript:void(0)';
    a.appendChild(document.createElement('div'));
    a.setAttribute('onclick', 'document.getElementById("riddleanswer").value="B";document.getElementById("riddleform").submit()');
    bot.appendChild(a);
    a = document.createElement('a');
    a.className = 'riddlelink';
    a.href = 'javascript:void(0)';
    a.appendChild(document.createElement('div'));
    a.setAttribute('onclick', 'document.getElementById("riddleanswer").value="C";document.getElementById("riddleform").submit()');
    bot.appendChild(a); }

function Gems() {
    var ikeyp;
    if ( (ikeyp = document.getElementById('ikey_p')) ) {
        gem[0] = ikeyp.getAttribute('onclick');
        gem[1] = ikeyp.getAttribute('onmouseover');
        var name = ikeyp.innerHTML.match(regexp.gem) || ParseDefault(ikeyp).match(regexp.defaultgem);
        switch ( name[1] ) {
            case 'ystic': gem[2] = '/y/e/channeling.png'; break;
            case 'ealth': gem[2] = '/y/e/healthpot.png'; break;
            case 'ana' : gem[2] = '/y/e/manapot.png'; gem[3] = 0; break;
            case 'pirit': gem[2] = '/y/e/spiritpot.png'; gem[3] = 1; break; }}
    else gem = []; }

function Alerts() {
    var alert = false, ocfull = false, omo, oc, vcp,
        spirit = document.querySelector('img[src$="spirit_n.png"]') || document.querySelector('img[src$="spirit_s.png"]');
    bg = cfg.colours.default;
    if ( cfg.hoverSpirit && spirit && (omo = spirit.getAttribute('onmouseover')) && omo.indexOf('lock') < 0 ) {
        spirit.setAttribute('onmouseover', omo + ';' + spirit.getAttribute('onclick')); }
    if ( (oc = document.querySelector('img[src$="bar_orange.png"]')) ) {
        mp = parseInt(document.querySelector('img[src$="bar_blue.png"]').style.width)/414;
        sp = parseInt(document.querySelector('img[src$="bar_red.png"]').style.width)/414;
        if ( spirit && parseInt(oc.style.width) >= 414 ) {
            ocfull = true; }
        if ( (gem[3] === 0 && mp < 0.5+0.5*cfg.mpboost/(cfg.mpboost+100)) ||
             (gem[3] === 1 && sp < 0.75+0.25*cfg.spboost/(cfg.spboost+100)) ) {
            gem[4] = true; }
        else gem[4] = false;
        if ( sp <= cfg.minSP && cfg.colours.lowsp ) {
            bg = cfg.colours.lowsp; if ( cfg.stopOnEmergency ) alert = true; }
        if ( mp <= cfg.minMP && cfg.colours.lowmp ) {
            bg = cfg.colours.lowmp; if ( cfg.stopOnEmergency ) alert = true; }
        if ( parseInt(document.querySelector('img[src$="green.png"]').style.width) <= 414 * cfg.minHP && cfg.colours.lowhp ) {
            bg = cfg.colours.lowhp; if ( cfg.stopOnEmergency ) alert = true; }
        if ( !document.querySelector('img[src$="bar_dgreen.png"]') && document.querySelector('img[src$="fallenshield.png"]') && cfg.colours.spark ) {
            bg = cfg.colours.spark; if ( cfg.stopOnEmergency ) alert = true; }}
    else {
        mp = parseInt(document.querySelector('img[src$="bar_blue.png"]').style.width)/207;
        sp = parseInt(document.querySelector('img[src$="bar_red.png"]').style.width)/207;
        if ( spirit && (vcp = document.querySelector('#vcp > div')) && parseInt(vcp.style.width) >= 190 ) {
            ocfull = true; }
        if ( (gem[3] === 0 && mp < 0.5+0.5*cfg.mpboost/(cfg.mpboost+100)) ||
             (gem[3] === 1 && sp < 0.75+0.25*cfg.spboost/(cfg.spboost+100)) ) {
            gem[4] = true; }
        else gem[4] = false;
        if ( sp <= cfg.minSP && cfg.colours.lowsp ) {
            bg = cfg.colours.lowsp; if ( cfg.stopOnEmergency ) alert = true; }
        if ( mp <= cfg.minMP && cfg.colours.lowmp ) {
            bg = cfg.colours.lowmp; if ( cfg.stopOnEmergency ) alert = true; }
        if ( parseInt(document.querySelector('img[src$="green.png"]').style.width) <= 496 * cfg.minHP && cfg.colours.lowhp ) {
            bg = cfg.colours.lowhp; if ( cfg.stopOnEmergency ) alert = true; }
        if ( !document.querySelector('img[src$="bar_dgreen.png"]') && document.querySelector('img[src$="fallenshield.png"]') && cfg.colours.spark ) {
            bg = cfg.colours.spark; if ( cfg.stopOnEmergency ) alert = true; }}
    interruptAlert = alert;
    if ( cfg.alertColours ) {
        if ( csp ) {
            if ( bg == cfg.colours.default && ocfull && cfg.colours.ocfull ) {
                bg = cfg.colours.ocfull; }
            csp.style.background = bg; }
        else {
            if ( spirit && cfg.colours.ocfull ) {
                spirit.style.background = ocfull ? cfg.colours.ocfull : cfg.colours.default; }
            document.getElementById('pane_vitals').style.background = bg; }}}

function Durations() {
    if ( !cfg.showDurations && !cfg.alertColours && !cfg.stopOnBuffsExpiring ) return;
    var pane = document.getElementById('pane_effects'), alert = false, div, dur, duration, stack,
        effects = pane.getElementsByTagName('img'), n = effects.length;
    while ( n-- > 0 ) {
        if ( (duration = effects[n].getAttribute('onmouseover').match(regexp.duration)) ) {
            if ( cfg.alertBuffs && duration[3] < 2 && effects[n].src.indexOf('channeling.png') < 0 && cfg.alertBuffs.test(effects[n].src) ) {
                alert = true; }
            if ( cfg.showDurations ) {
                div = document.createElement('div');
                dur = document.createElement('div');
                div.className = 'effect_duration';
                if ( duration[3] < 9 ) {
                    dur.style.background = duration[3] < 4 ? 'aquamarine' : 'lavender'; }
                dur.innerHTML = duration[3];
                if ( (stack = duration[2]) ) {
                    if ( cfg.stackBorder ) {
                        dur.style.border = Math.ceil(stack/2)+1 + 'px solid black'; }
                    else {
                        dur.innerHTML += ' x' + stack; }}
                div.appendChild(dur);
                pane.insertBefore(div, effects[n]); }}}
    if ( cfg.alertColours ) {
        var colour = alert && cfg.colours.expiring ? cfg.colours.expiring :
            (document.querySelector('#pane_effects > img[src$="channeling.png"]') && cfg.colours.channelling ? cfg.colours.channelling : cfg.colours.default);
        if ( csp ) {
            csp.style.background = bg != cfg.colours.default ? bg : colour; }
        else {
            pane.style.background = colour; }}
    interruptAlert = interruptAlert || (cfg.stopOnBuffsExpiring && alert);
    if ( cfg.showDurations ) {
        effects = document.querySelectorAll('.btm6 > img[onmouseover]'); n = effects.length;
        while ( n-- > 0 ) {
            if ( (duration = effects[n].getAttribute('onmouseover').match(regexp.duration)) ) {
                div = document.createElement('div');
                dur = document.createElement('div');
                div.className = 'effect_duration';
                if ( duration[3] < 9 ) {
                    dur.style.background = duration[3] < 4 ? 'aquamarine' : 'lavender'; }
                dur.innerHTML = duration[3];
                if ( (stack = duration[2]) ) {
                    if ( cfg.stackBorder ) {
                        dur.style.border = Math.ceil(stack/2)+1 + 'px solid black'; }
                    else {
                        dur.innerHTML += ' x' + stack; }}
                div.appendChild(dur);
                effects[n].parentNode.insertBefore(div, effects[n]); }}}}

function GetMonsterData() {
    if ( !cfg.showMonsterHP && !cfg.shortenHPbars && !cfg.monsterKeywords ) return;
    var local, data, monster;
    if ( (local = localStorage['HVmonsterData' + isekai]) ) {
        monsterData = JSON.parse(local); }
    else {
        monsterData.info = [];
        monsterData.id = [];
        monsterData.name = [];
        monsterData.hp = [];
        monsterData.highlight = [];
        monsterData.hp[10] = 0;
        if ( (data = log.innerHTML.match(regexp.monsters)) ) {
            for ( var i = 0; i < monsters.length; i++ ) {
                monster = data[monsters.length-i-1].match(regexp.monster);
                monsterData.info[i] = monster[0];
                monsterData.id[i] = parseInt(monster[1]);
                monsterData.name[i] = monster[2];
                monsterData.hp[i] = parseInt(monster[3]);
                monsterData.highlight[i] = cfg.monsterKeywords && monsters[i].hasAttribute('onclick') && cfg.monsterKeywords.test(monster[0]);
                if ( !monsters[i].querySelector('.btm2[style]') ) {
                    monsterData.hp[10] = monsterData.hp[i] > monsterData.hp[10] ? monsterData.hp[i] : monsterData.hp[10]; }}}}}

function Monsters() {
    var monster;
    for ( var i = 0; i < monsters.length; i++ ) {
        if ( (monster = monsters[i]) && monster.hasAttribute('onclick') ) {
            var area = monster.querySelector('.btm' + cfg.hoverArea) || monster;
            area.addEventListener('mouseout', ClearTarget, true);
            area.addEventListener('mouseover', SetTarget(i), true);
            monster.addEventListener('mousedown', HandleClick(i), true);
            monster.addEventListener('contextmenu', function(e) { e.preventDefault(); });
            monster.addEventListener('wheel', HandleWheel(i), true);
            if ( cursor >= 0 ) Cursor();
            if ( target === i && (cfg.hoverAction || override) && !interruptHover && !interruptAlert ) {
                Hover(); }
            if ( monsterData.highlight && monsterData.highlight[i] ) {
                monster.querySelector('.btm2').style.background = cfg.colours.monster; }
            if ( (cfg.showMonsterHP || cfg.shortenHPbars) && monsterData.hp[i] ) {
                var bar = monster.querySelector('img[src$="nbargreen.png"]'),
                    ratio = parseInt(bar.style.width) / 120;
                if ( cfg.showMonsterHP && !cfg.quickbarBesideMonsters && !cfg.condenseLeft ) {
                    var div = monster.appendChild(document.createElement('div'));
                    div.className = 'monsterhp';
                    div.innerHTML = 'HP: ' + Math.round(ratio * monsterData.hp[i]).toLocaleString() + ' / ' + monsterData.hp[i].toLocaleString(); }
                if ( cfg.shortenHPbars && monsterData.hp[i] < monsterData.hp[10] ) {
                    var factor = monsterData.hp[i] / monsterData.hp[10],
                        border = monster.querySelector('img[src$="nbarfg.png"]'),
                        bg = monster.querySelector('.chbd');
                    bar.style.width = Math.round(ratio * factor * 120) + 'px';
                    border.style.width = Math.round(factor * 120) + 'px';
                    border.style.height = '12px';
                    bg.style.width = Math.round(factor * 120) + 'px'; }}
            if ( cfg.colours.stun || cfg.colours.imperil || cfg.colours.stunimperil ) {
                var status = monster.querySelector('.btm6'),
                    stun = status.innerHTML.indexOf('wpn_stun.png') > -1,
                    imperil = status.innerHTML.indexOf('imperil.png') > -1;
                if ( cfg.colours.stunimperil && stun && imperil ) {
                    monster.style.background = cfg.colours.stunimperil; }
                else if ( cfg.colours.stun && stun ) {
                    monster.style.background = cfg.colours.stun; }
                else if ( cfg.colours.imperil && imperil ) {
                    monster.style.background = cfg.colours.imperil; }}}}}

function Hover() {
    if ( hovering ) return;
    hovering = true;
    if ( override ) {
        override(); }
    else if ( shiftHeld && cfg.hoverShiftAction ) {
        cfg.hoverShiftAction(); }
    else if ( ctrlHeld && cfg.hoverCtrlAction ) {
        cfg.hoverCtrlAction(); }
    else if ( altHeld && cfg.hoverAltAction ) {
        cfg.hoverAltAction(); }
    else {
        cfg.hoverAction(); }
    if ( impulse ) {
        impulse();
        done = true;
        impulse = false; }
    monsters[target].click(); }

function SetTarget(i) { return function() {
    target = i;
    if ( (cfg.hoverAction || override) && !interruptHover && !interruptAlert && monsters[target].hasAttribute('onclick') ) {
        Hover(); }};}

function HandleClick(i) {
    return function(e) {
        var action;
        if ( (i || i === 0) && e.which == 1 ) action = cfg.clickLeft;
        else if ( e.which == 2 ) action = cfg.clickMiddle;
        else if ( e.which == 3 ) action = cfg.clickRight;
        if ( action ) {
            e.preventDefault();
            if ( release ) {
                done = false;
                release = false; }
            action();
            if ( i || i === 0 ) {
                monsters[i].click(); }}};}

function HandleWheel(i) {
    return function(e) {
        var action;
        if ( e.deltaY < 0 ) action = cfg.wheelUp;
        else if ( e.deltaY > 0 ) action = cfg.wheelDown;
        else if ( e.deltaX < 0 ) action = cfg.wheelLeft;
        else if ( e.deltaX > 0 ) action = cfg.wheelRight;
        if ( action ) {
            e.preventDefault();
            done = false;
            action();
            if ( i || i === 0 ) {
                monsters[i].click(); }}};}

function MouseEngage(e) {
    if ( e.which == 1 ) override = cfg.clickLeft;
    else if ( e.which == 2 ) override = cfg.clickMiddle;
    else if ( e.which == 3 ) override = cfg.clickRight; }

function Cursor() {
    this.battle.hover_target(monsters[cursor]); }

function Confirm() {
    var ed, flee;
    if ( cfg.edConfirm && (ed = document.querySelector('div[onclick][onmouseover*="item(11401)"]')) ) {
        ed.setAttribute('onclick', 'if ( confirm("Are you sure you want to use Energy Drink?") ) {' + ed.getAttribute('onclick') + '}'); }
    if ( cfg.fleeConfirm && (flee = document.getElementById('1001')) ) {
        flee.setAttribute('onclick', 'if ( confirm("Tired already? I guess we can play forever some other time...") ) {' + flee.getAttribute('onclick') + '}'); }
    if ( cfg.fleeConfirm && (flee = document.querySelector('.btqs[onclick][onmouseover*="Flee"]')) ) {
        flee.setAttribute('onclick', 'if ( confirm("Tired already? I guess we can play forever some other time...") ) {' + flee.getAttribute('onclick') + '}'); }}

function ExtendQuickbar() {
    var quickbar = document.getElementById('quickbar'), div, img, spell, action;
    for ( var i = 0; i < cfg.quickbarExtend.length; i++ ) {
        div = document.createElement('div');
        div.className = 'btqs extend';
        switch ( cfg.quickbarExtend[i] ) {
            case 0: break;
            case 1: if ( gem[0] ) {
                        if ( cfg.raiseGem ) {
                            div.setAttribute('style', 'position: absolute;' + (cfg.condenseLeft ? 'top: -55px; left: 165px;' :
                                                                              (cfg.vitalsAboveMonsters ? 'top: 15px; left: 1191px;' :
                                                                              'top: 50px; left: 622px;'))); }
                        div.setAttribute('onclick', gem[0]);
                        if ( cfg.hoverGem ) {
                            div.setAttribute('onmouseover', gem[1] + ';' + gem[0]); }
                        else {
                            div.setAttribute('onmouseover', gem[1]); }
                        if ( cfg.alertColours && gem[4] ) {
                            div.className += ' usable'; }
                        img = document.createElement('img');
                        img.src = gem[2];
                        img.className = 'btqi';
                        div.appendChild(img);
                        img = document.createElement('img');
                        img.src = '/y/ab/b.png';
                        img.className = 'btqb';
                        div.appendChild(img); } break;
            default: if ( (spell = document.getElementById(cfg.quickbarExtend[i])) ) {
                        img = document.createElement('img');
                        var info = spell.getAttribute('onmouseover');
                        if ( (action = spell.getAttribute('onclick')) ) {
                            div.setAttribute('onclick', action); }
                        else {
                            img.style.opacity = 0.5; }
                        div.setAttribute('onmouseover', info);
                        if ( cfg.quickbarExtend[i].indexOf('ikey') < 0 ) {
                            img.src = '/y/a/' + info.match(regexp.spellicon)[1] + '.png'; }
                        else {
                            var name = spell.innerHTML;
                            if ( regexp.defaultfont.test(name) ) name = ParseDefault(spell);
                            switch ( true ) {
                                case name.indexOf('ealth') > -1: img.src = '/y/e/healthpot.png'; break;
                                case name.indexOf('ana') > -1: img.src = '/y/e/manapot.png';
                                    if ( cfg.alertColours && name.indexOf('otion') > -1 && mp < (100-cfg.mppot+cfg.mppot*cfg.mpboost/(cfg.mpboost+100))/100 ) {
                                        div.className += ' usable'; } break;
                                case name.indexOf('pirit') > -1: img.src = '/y/e/spiritpot.png';
                                    if ( cfg.alertColours && name.indexOf('otion') > -1 && sp < (100-cfg.sppot+cfg.sppot*cfg.spboost/(cfg.spboost+100))/100 ) {
                                        div.className += ' usable'; } break;
                                case name.indexOf('lixir') > -1: img.src = '/y/e/healthpot.png'; break;
                                case name.indexOf('rink') > -1: img.src = '/y/e/soulstone.png'; break;
                                case name.indexOf('andy') > -1: img.src = '/y/e/soulstone.png'; break;
                                case name.indexOf('lames') > -1: img.src = '/y/e/fireinfusion.png'; break;
                                case name.indexOf('rost') > -1: img.src = '/y/e/coldinfusion.png'; break;
                                case name.indexOf('ightning') > -1: img.src = '/y/e/elecinfusion.png'; break;
                                case name.indexOf('torms') > -1: img.src = '/y/e/windinfusion.png'; break;
                                case name.indexOf('ivinity') > -1: img.src = '/y/e/holyinfusion.png'; break;
                                case name.indexOf('arkness') > -1: img.src = '/y/e/darkinfusion.png'; break;
                                case name.indexOf('wiftness') > -1: img.src = '/y/e/haste_scroll.png'; break;
                                case name.indexOf('rotection') > -1: img.src = '/y/e/protection_scroll.png'; break;
                                case name.indexOf('vatar') > -1: img.src = '/y/e/protection_scroll.png'; break;
                                case name.indexOf('bsorption') > -1: img.src = '/y/e/absorb_scroll.png'; break;
                                case name.indexOf('hadows') > -1: img.src = '/y/e/shadowveil_scroll.png'; break;
                                case name.indexOf('ife') > -1: img.src = '/y/e/sparklife_scroll.png'; break;
                                case name.indexOf('ods') > -1: img.src = '/y/e/sparklife_scroll.png'; break;
                                case name.indexOf('ase') > -1: img.src = '/y/e/flowers.png'; break;
                                case name.indexOf('um') > -1: img.src = '/y/e/gum.png'; break;
                                default: img.src = '/y/e/channeling.png'; break; }}
                        img.className = 'btqi';
                        div.appendChild(img);
                        img = document.createElement('img');
                        img.src = '/y/ab/b.png';
                        img.className = 'btqb';
                        div.appendChild(img); } break; }
        quickbar.appendChild(div); }}

function MonsterBar() {
    if ( !cfg.monsterBar[0] || cfg.quickbarBesideMonsters || cfg.condenseLeft ) return;
    var mbar, div, img;
    for ( var i = 0; i < monsters.length; i++ ) {
        mbar = document.createElement('div');
        mbar.id = 'mbar_' + i;
        mbar.className = 'mbar';
        for ( var j = 0; (j < 3 || (j < 4 && !cfg.spacedBar)) && j < cfg.monsterBar.length; j++ ) {
            div = document.createElement('div');
            div.className = 'btqs mbs';
            img = document.createElement('img');
            img.src = cfg.monsterBar[j][1] ?
                      '/y/a/' + document.getElementById(cfg.monsterBar[j][1]).getAttribute('onmouseover').match(regexp.spellicon)[1] + '.png' :
                      '/y/e/wpn_bleed.png';
            img.className = 'btqi';
            div.appendChild(img);
            img = document.createElement('img');
            img.src = '/y/ab/b.png';
            img.className = 'btqb';
            div.appendChild(img);
            if ( cfg.monsterBar[j][0] ) {
                div.onmouseout = Disengage;
                div.onmouseover = BarEngage(i, cfg.monsterBar[j]); }
            else {
                div.onclick = BarAction(i, cfg.monsterBar[j]); }
            mbar.appendChild(div); }
        document.body.appendChild(mbar); }}

function Disengage() { target = false; override = false; }

function BarEngage(i, actions) {
    return function() {
        if ( !monsters[i].hasAttribute('onclick') ) return;
        override = StrongestBar(actions);
        SetTarget(i)(); };}

function BarAction(i, actions) {
    return function() {
        if ( !monsters[i].hasAttribute('onclick') ) return;
        StrongestBar(actions)();
        monsters[i].click(); };}

function StrongestBar(actions) { return function() { var n = actions.length; while ( n-- > 1 ) document.getElementById(actions[n]).click(); };}

function ProcessLog() {
    turn = log.innerHTML.match(regexp.turn);
    if ( turn ) turn = turn[0]; else return;
    if ( !cfg.showCooldowns && !cfg.trackSpeed && !cfg.trackDamage && !cfg.trackUsage ) return;
    if ( !timelog.startTime && cfg.trackSpeed ) timelog.startTime = Date.now();
    var action = turn.match(regexp.action);
    if ( action ) action = action[1]; else return;
    timelog.action++;
    if ( !regexp.zeroturn.test(action) ) timelog.turn++;
    var use = action.match(regexp.use), miss, counter;
    if ( use ) {
        use = use[2];
        if ( cfg.showCooldowns ) timelog.lastuse[use] = timelog.turn;
        if ( cfg.trackUsage ) combatlog.used[use] = (combatlog.used[use] ? combatlog.used[use] : 0) + 1; }
    else if ( !cfg.trackUsage ) {}
    else if ( action.indexOf('Spirit Stance') > -1 ) combatlog.used.Spirit = (combatlog.used.Spirit ? combatlog.used.Spirit : 0) + 1;
    else if ( action.indexOf('Defending.') > -1 ) combatlog.used.Defend = (combatlog.used.Defend ? combatlog.used.Defend : 0) + 1;
    else if ( action.indexOf('Focusing.') > -1 ) combatlog.used.Focus = (combatlog.used.Focus ? combatlog.used.Focus : 0) + 1;
    else combatlog.used.Attack = (combatlog.used.Attack ? combatlog.used.Attack : 0) + 1;
    if ( cfg.trackDamage ) {
        var dmg;
        if ( (dmg = turn.match(regexp.damage)) ) {
            var cast = action.indexOf('You cast') > -1, data, shield;
            for ( var i = 0; i < dmg.length; i++ ) {
                if ( (data = dmg[i].match(regexp.type)) ) {
                    if ( dmg[i].indexOf('its you for') > -1 ) {
                        var crit = dmg[i].indexOf(' crits ') > -1;
                        if ( dmg[i].indexOf(' casts ') > -1 ) {
                            combatlog.mtaken.hit++;
                            if ( crit ) combatlog.mtaken.crit++;
                            combatlog.mtaken[data[2]] = (combatlog.mtaken[data[2]] ? combatlog.mtaken[data[2]] : 0) + parseInt(data[1]);
                            if ( (shield = dmg[i].match(regexp.shield)) ) {
                                combatlog.mtaken.shit++;
                                if ( crit ) combatlog.mtaken.scrit++;
                                combatlog.mtaken['s'+data[2]] = (combatlog.mtaken['s'+data[2]] ? combatlog.mtaken['s'+data[2]] : 0) + parseInt(shield[1]); }}
                        else {
                            combatlog.ptaken.hit++;
                            if ( crit ) combatlog.ptaken.crit++;
                            combatlog.ptaken[data[2]] = (combatlog.ptaken[data[2]] ? combatlog.ptaken[data[2]] : 0) + parseInt(data[1]);
                            if ( (shield = dmg[i].match(regexp.shield)) ) {
                                combatlog.ptaken.shit++;
                                if ( crit ) combatlog.ptaken.scrit++;
                                combatlog.ptaken['s'+data[2]] = (combatlog.ptaken['s'+data[2]] ? combatlog.ptaken['s'+data[2]] : 0) + parseInt(shield[1]); }}}
                    else {
                        if ( cast ) {
                            if ( dmg[i].indexOf(' explodes ') < 0 ) {
                                combatlog.mdealt.hit++;
                                if ( dmg[i].indexOf(' blasts ') > -1 ) combatlog.mdealt.crit++; }
                            combatlog.mdealt[data[2]] = (combatlog.mdealt[data[2]] ? combatlog.mdealt[data[2]] : 0) + parseInt(data[1]); }
                        else {
                            if ( !regexp.strike.test(dmg[i]) ) {
                                combatlog.pdealt.hit++;
                                if ( regexp.crit.test(dmg[i]) ) combatlog.pdealt.crit++; }
                            combatlog.pdealt[data[2]] = (combatlog.pdealt[data[2]] ? combatlog.pdealt[data[2]] : 0) + parseInt(data[1]); }}}
                else if ( (data = dmg[i].match(regexp.dot)) ) {
                    if ( regexp.pdot.test(dmg[i]) ) combatlog.pdealt.dot = (combatlog.pdealt.dot ? combatlog.pdealt.dot : 0) + parseInt(data[1]);
                    else combatlog.mdealt.dot = (combatlog.mdealt.dot ? combatlog.mdealt.dot : 0) + parseInt(data[1]); }
                else if ( (data = dmg[i].match(regexp.points)) ) {
                    if ( dmg[i].indexOf('You counter') > -1 ) {
                        combatlog.pdealt.hit++;
                        combatlog.pdealt[data[2]] = (combatlog.pdealt[data[2]] ? combatlog.pdealt[data[2]] : 0) + parseInt(data[1]); }
                    else {
                        combatlog.mdealt[data[2]] = (combatlog.mdealt[data[2]] ? combatlog.mdealt[data[2]] : 0) + parseInt(data[1]); }}}}
        if ( (miss = turn.match(regexp.mdmiss)) ) combatlog.mdealt.miss += miss.length;
        if ( (miss = turn.match(regexp.mdevade)) ) combatlog.mdealt.evade += miss.length;
        if ( (miss = turn.match(regexp.mdresist)) ) combatlog.mdealt.resist += miss.length;
        if ( (miss = turn.match(regexp.md50)) ) combatlog.mdealt.r50 += miss.length;
        if ( (miss = turn.match(regexp.md75)) ) combatlog.mdealt.r75 += miss.length;
        if ( (miss = turn.match(regexp.md90)) ) combatlog.mdealt.r90 += miss.length;
        if ( (miss = turn.match(regexp.pdmiss)) ) combatlog.pdealt.miss += miss.length;
        if ( (miss = turn.match(regexp.pdevade)) ) combatlog.pdealt.evade += miss.length;
        if ( (miss = turn.match(regexp.pdparry)) ) combatlog.pdealt.parry += miss.length;
        if ( (miss = turn.match(regexp.mtevade)) ) combatlog.mtaken.evade += miss.length;
        if ( (miss = turn.match(regexp.mtblock)) ) combatlog.mtaken.block += miss.length;
        if ( (miss = turn.match(regexp.mt50)) ) combatlog.mtaken.r50 += miss.length;
        if ( (miss = turn.match(regexp.mt75)) ) combatlog.mtaken.r75 += miss.length;
        if ( (miss = turn.match(regexp.mt90)) ) combatlog.mtaken.r90 += miss.length;
        if ( (miss = turn.match(regexp.ptmiss)) ) combatlog.ptaken.miss += miss.length;
        if ( (miss = turn.match(regexp.ptevade)) ) combatlog.ptaken.evade += miss.length;
        if ( (miss = turn.match(regexp.ptparry)) ) combatlog.ptaken.parry += miss.length;
        if ( (miss = turn.match(regexp.ptblock)) ) combatlog.ptaken.block += miss.length; }
    if ( cfg.trackUsage && (counter = turn.match(regexp.counter)) ) combatlog.used.Counter = (combatlog.used.Counter ? combatlog.used.Counter : 0) + counter.length; }

function ShowCooldowns() {
    if ( !cfg.showCooldowns ) return;
    var quickbar = document.getElementById('quickbar'), info, used, cooldown, div,
        buttons = quickbar.querySelectorAll('.btqs[onmouseover]:not([onclick])'), n = buttons.length;
    while ( n-- > 0 ) {
        if ( (info = buttons[n].getAttribute('onmouseover').match(regexp.spellinfo)) &&
             (used = timelog.lastuse[info[1]]) && (cooldown = info[2] - timelog.turn + used) > 0 ) {
            div = document.createElement('div');
            div.className = 'cooldown';
            div.innerHTML = cooldown;
            buttons[n].appendChild(div); }}}

function MaxVitals() {
    if ( !cfg.maxVitals ) return;
    var hpd, mpd, spd, changed = false;
    if ( (hpd = document.getElementById('dvrhd') || document.getElementById('dvrhb')) ) {
        mpd = document.getElementById('dvrm');
        spd = document.getElementById('dvrs'); }
    else {
        hpd = document.getElementById('vrhd') || document.getElementById('vrhb');
        mpd = document.getElementById('vrm');
        spd = document.getElementById('vrs'); }
    var hpv = parseInt(hpd.innerHTML), mpv = parseInt(mpd.innerHTML), spv = parseInt(spd.innerHTML);
    if ( hpv > vitals.hp ) { vitals.hp = hpv; changed = true; }
    if ( mpv > vitals.mp ) { vitals.mp = mpv; changed = true; }
    if ( spv > vitals.sp ) { vitals.sp = spv; changed = true; }
    hpd.innerHTML = hpv + '/' + vitals.hp;
    mpd.innerHTML = mpv + '/' + vitals.mp;
    spd.innerHTML = spv + '/' + vitals.sp; }

function FormatLog() {
    if ( !cfg.logColours && !cfg.turnDividers ) return;
    var rows = log.getElementsByTagName('td');
    for ( var i = 0; i < rows.length; i++ ) {
        if ( rows[i].className == 'tls' ) {
            if ( cfg.turnDividers ) {
                rows[i].innerHTML = '<hr>'; }
            break; }
        var text = rows[i].innerHTML;
        if ( cfg.logColours ) {
            if ( regexp.crit.test(text) ) {
                rows[i].className = 'tlb'; }
            if ( regexp.miss.test(text) ) {
                rows[i].className += ' miss'; }
            else if ( text.indexOf('its you for') > -1 ) {
                rows[i].className += ' damage'; }
            else if ( regexp.zeroturn.test(text) ) {
                rows[i].className += ' item'; }
            else if ( regexp.attack.test(text) ) {
                rows[i].className += ' attack'; }
            else if ( regexp.spell.test(text) ) {
                rows[i].className += ' spell'; }
            else if ( regexp.recovery.test(text) ) {
                rows[i].className += ' recovery'; }
            else if ( text.indexOf('You gain the effect') > -1 ) {
                rows[i].className += ' effect'; }
            else if ( text.indexOf('Spirit Stance') > -1 ) {
                rows[i].className += ' spirit'; }
            else if ( text.indexOf('proficiency') > -1 ) {
                rows[i].className += ' proficiency'; }}}}

function ShowRound() {
    var div = document.getElementById(cfg.bigRoundCounter ? 'mainpane' : 'battle_right').appendChild(document.createElement('div')), rounds;
    div.id = 'gay_sex';
    if ( !cfg.showRound ) return;
    if ( !timelog.rounds && (rounds = log.innerHTML.match(regexp.round)) ) {
        timelog.round = rounds[1];
        timelog.rounds = rounds[2]; }
    if ( timelog.rounds ) {
        div.innerHTML = (cfg.bigRoundCounter ? '' : 'Round ') + timelog.round.toLocaleString() + ' / ' + timelog.rounds.toLocaleString();
        if ( cfg.bigRoundCounter ) {
            div.setAttribute('style', 'position: absolute; left: 1110px; top: 10px; font-size: 200%; font-weight: bold'); }}}

function CfgButton() {
    if ( !cfg.cfgButton ) return;
    var div = document.createElement('div');
    div.id = 'cfgbutton';
    div.innerHTML = '\u2699';
    div.onclick = Settings;
    document.body.appendChild(div);
    var hasper = regexp.profile.test(JSON.stringify(cfg.persona));
    var hasisk = regexp.profile.test(JSON.stringify(cfg.isekai.persona)) || (hasper && cfg.isekaiInherit);
    if ( (!isekai && hasper) || (isekai && hasisk) ) {
        var menu = div.appendChild(document.createElement('div'));
        const Set = function(i, p, s) {
            return function(e) {
                e.stopPropagation();
                var blue;
                if ( (blue = menu.querySelector('[style*="' + (isekai ? 'red' : 'blue') + '"')) ) {
                    blue.style.color = ''; }
                profile[isekai + 'p'] = p;
                if (p) {
                    profile[isekai + 's' + profile[isekai + 'p']] = s; }
                if ( JSON.stringify(profile) != localStorage.HVmbp ) {
                    localStorage.HVmbp = JSON.stringify(profile);
                    location.href = location.href; }
                i.style.color = isekai ? 'red' : 'blue'; };};
        menu.id = 'mbprofile';
        menu.className = 'fc4 fal fcb';
        var base = menu.appendChild(document.createElement('div')), blue = false;
        base.onclick = Set(base, 0, 0);
        if ( !isekai ) {
            base.innerHTML = cfg.name;
            for ( var i = 0; i < cfg.persona.length; i++ ) {
                if ( regexp.profile.test(JSON.stringify(cfg.persona[i])) ) {
                    var persona = menu.appendChild(document.createElement('div'));
                    persona.innerHTML = '- ' + cfg.persona[i].name;
                    persona.onclick = Set(persona, i+1, 0);
                    for ( var j = 0; j < cfg.persona[i].set.length; j++ ) {
                        if ( regexp.profile.test(JSON.stringify(cfg.persona[i].set[j])) ) {
                            var set = menu.appendChild(document.createElement('div'));
                            set.innerHTML = '-- ' + cfg.persona[i].set[j].name;
                            set.onclick = Set(set, i+1, j+1);
                            if ( profile.p == i+1 && profile['s' + profile.p] == j+1 ) {
                                set.style.color = 'blue';
                                blue = true; }}}
                    if ( profile.p == i+1 && (!blue || profile['s' + profile.p] == 0) ) {
                        persona.style.color = 'blue';
                        blue = true; }}}
            if ( !blue || profile.p == 0 ) {
                base.style.color = 'blue'; }}
        else {
            base.innerHTML = cfg.isekai.name;
            for ( i = 0; i < cfg.isekai.persona.length; i++ ) {
                hasisk = regexp.profile.test(JSON.stringify(cfg.isekai.persona[i]));
                hasper = regexp.profile.test(JSON.stringify(cfg.persona[i])) && cfg.isekaiInherit;
                if ( hasisk || hasper ) {
                    persona = menu.appendChild(document.createElement('div'));
                    persona.innerHTML = '- ' + (hasisk ? cfg.isekai.persona[i].name : cfg.persona[i].name);
                    persona.onclick = Set(persona, i+1, 0);
                    for ( j = 0; j < cfg.isekai.persona[i].set.length; j++ ) {
                        hasisk = regexp.profile.test(JSON.stringify(cfg.isekai.persona[i].set[j]));
                        hasper = regexp.profile.test(JSON.stringify(cfg.persona[i].set[j])) && cfg.isekaiInherit;
                        if ( hasisk || hasper ) {
                            set = menu.appendChild(document.createElement('div'));
                            set.innerHTML = '-- ' + (hasisk ? cfg.isekai.persona[i].set[j].name : cfg.persona[i].set[j].name);
                            set.onclick = Set(set, i+1, j+1);
                            if ( profile.ip == i+1 && profile['is' + profile.ip] == j+1 ) {
                                set.style.color = 'red';
                                blue = true; }}}
                    if ( profile.ip == i+1 && (!blue || profile['is' + profile.ip] == 0) ) {
                        persona.style.color = 'red';
                        blue = true; }}}
            if ( !blue || profile.ip == 0 ) {
                base.style.color = 'red'; }}}}

function TrackDrops() {
    if ( (!cfg.trackDrops && !cfg.trackProficiency && !cfg.proficiencySidebar) || !document.getElementById('btcp') ) return;
    if ( cfg.trackDrops ) {
        var reward = turn.match(regexp.reward);
        if ( reward && (reward = parseInt(reward[1])) ) {
            droplog.Credit = (droplog.Credit ? droplog.Credit : 0) + reward; }
        var drops = turn.match(regexp.drops);
        if ( !drops ) drops = [];
        var n = drops.length, crystal, crystals, credit, exp, proficiencies, proficiency, prof;
        while ( n-- > 0 ) {
            var drop = drops[n].match(regexp.drop);
            if ( drop[2] == 'BA05B4' && (crystal = drop[3].match(regexp.crystal)) ) {
                droplog.Crystal = (droplog.Crystal ? droplog.Crystal : 0) + (parseInt(crystal[1]) || 1);
                if ( cfg.detailedCrystlog && (crystals = drop[3].match(regexp.crystals)) ) {
                    droplog.Crystals[crystals[0]] = (droplog.Crystals[crystals[0]] ? droplog.Crystals[crystals[0]] : 0) + (parseInt(crystal[1]) || 1); }}
            else if (drop[2] == 'A89000' && (credit = drop[3].match(regexp.credit)) ) {
                droplog.Credit = (droplog.Credit ? droplog.Credit : 0) + (parseInt(credit[1]) || 1); }
            else if ( drop[2] == 'FF0000' && !drop[4] && regexp.quality[8].test(drop[0]) ) {
                var tracked = false;
                if ( (tracked = cfg.equipmentCutoff > 0 && drop[3].indexOf('Peerless') > -1) ) {
                    droplog.Peerless = (droplog.Peerless ? droplog.Peerless : 0) + 1; }
                else if ( (tracked = cfg.equipmentCutoff > 1 && drop[3].indexOf('Legendary') > -1) ) {
                    droplog.Legendary = (droplog.Legendary ? droplog.Legendary : 0) + 1; }
                else if ( (tracked = cfg.equipmentCutoff > 2 && drop[3].indexOf('Magnificent') > -1) ) {
                    droplog.Magnificent = (droplog.Magnificent ? droplog.Magnificent : 0) + 1; }
                else if ( (tracked = cfg.equipmentCutoff > 3 && drop[3].indexOf('Exquisite') > -1) ) {
                    droplog.Exquisite = (droplog.Exquisite ? droplog.Exquisite : 0) + 1; }
                else if ( (tracked = cfg.equipmentCutoff > 4 && drop[3].indexOf('Superior') > -1) ) {
                    droplog.Superior = (droplog.Superior ? droplog.Superior : 0) + 1; }
                else if ( (tracked = cfg.equipmentCutoff > 5 && drop[3].indexOf('Average') > -1) ) {
                    droplog.Average = (droplog.Average ? droplog.Average : 0) + 1; }
                else if ( (tracked = cfg.equipmentCutoff > 6 && drop[3].indexOf('Fair') > -1) ) {
                    droplog.Fair = (droplog.Fair ? droplog.Fair : 0) + 1; }
                else if ( (tracked = cfg.equipmentCutoff > 7 && drop[3].indexOf('Crude') > -1) ) {
                    droplog.Crude = (droplog.Crude ? droplog.Crude : 0) + 1; }
                else {
                    droplog.Equipment = (droplog.Equipment ? droplog.Equipment : 0) + 1; }
                if ( cfg.detailedDroplog && tracked ) {
                    droplog.Equips[drop[3]] = (droplog.Equips[drop[3]] ? droplog.Equips[drop[3]] : 0) + 1; }}
            else if ( drop[2] == 'FF0000' && !drop[4] ) {
                droplog.Material = (droplog.Material ? droplog.Material : 0) + (parseInt(drop[1]) || 1);
                if ( cfg.detailedDroplog ) {
                    droplog.Mats[drop[3]] = (droplog.Mats[drop[3]] ? droplog.Mats[drop[3]] : 0) + (parseInt(drop[1]) || 1); }}
            else if ( drop[3].indexOf('Chaos') > -1 ) {
                droplog.Chaos = (droplog.Chaos ? droplog.Chaos : 0) + 1; }
            else if ( drop[3].indexOf('Blood') > -1 ) {
                droplog.Blood = (droplog.Blood ? droplog.Blood : 0) + 1; }
            else if ( drop[3].indexOf('Soul') > -1 ) {
                droplog.Soul = (droplog.Soul ? droplog.Soul : 0) + (drop[1] == 'five' ? 5 : (parseInt(drop[1]) || 1)); }
            else if ( drop[3].indexOf('Figurine') > -1 ) {
                droplog.Figurine = (droplog.Figurine ? droplog.Figurine : 0) + 1;
                if ( cfg.detailedDroplog ) {
                    droplog.Figurines[drop[3]] = (droplog.Figurines[drop[3]] ? droplog.Figurines[drop[3]] : 0) + 1; }}
            else if ( drop[2] == '0000FF' ) {
                droplog.Artifact = (droplog.Artifact ? droplog.Artifact : 0) + 1;
                if ( cfg.detailedDroplog ) {
                    droplog.Artifacts[drop[3]] = (droplog.Artifacts[drop[3]] ? droplog.Artifacts[drop[3]] : 0) + 1; }}
            else if ( drop[2] == '461B7E' ) {
                droplog.Trophy = (droplog.Trophy ? droplog.Trophy : 0) + 1;
                if ( cfg.detailedDroplog ) {
                    droplog.Trophies[drop[3]] = (droplog.Trophies[drop[3]] ? droplog.Trophies[drop[3]] : 0) + 1; }}
            else if ( drop[2] == '00B000' && drop[3].indexOf('Gem') < 0 ) {
                droplog.Consumable = (droplog.Consumable ? droplog.Consumable : 0) + 1;
                if ( cfg.detailedDroplog ) {
                    droplog.Consumables[drop[3]] = (droplog.Consumables[drop[3]] ? droplog.Consumables[drop[3]] : 0) + 1; }}
            else if ( drop[2] == '489EFF' ) {
                droplog.Food = (droplog.Food ? droplog.Food : 0) + 1;
                if ( cfg.detailedDroplog ) {
                    droplog.Foods[drop[3]] = (droplog.Foods[drop[3]] ? droplog.Foods[drop[3]] : 0) + 1; }}}
        if ( (exp = log.innerHTML.match(regexp.exp)) ) {
            droplog.EXP = (droplog.EXP ? droplog.EXP : 0) + parseInt(exp[1]); }}
    if ( (cfg.trackProficiency || cfg.proficiencySidebar) && (proficiencies = log.innerHTML.match(regexp.proficiencies)) ) {
        for ( var i = 0; i < proficiencies.length; i++ ) {
            if ( (proficiency = proficiencies[i].match(regexp.proficiency)) && (prof = parseFloat(proficiency[1])) && prof > 0 ) {
                droplog.proficiency[proficiency[2]] = (droplog.proficiency[proficiency[2]] ? droplog.proficiency[proficiency[2]] : 0) + prof; }}}}

function Profbar() {
    if ( !cfg.proficiencySidebar ) return;
    var profbar = document.getElementById('profbar');
    if ( !profbar ) {
        profbar = document.createElement('table');
        profbar.id = 'profbar';
        document.getElementById('csp').appendChild(profbar); }
    profbar.innerHTML = '<tbody>' +
        (droplog.proficiency['one-handed weapon'] ? '<tr><td>' + droplog.proficiency['one-handed weapon'].toFixed(3) + '</td><td>1H</td></tr>' : '') +
        (droplog.proficiency['two-handed weapon'] ? '<tr><td>' + droplog.proficiency['two-handed weapon'].toFixed(3) + '</td><td>2H</td></tr>' : '') +
        (droplog.proficiency['dual wielding'] ? '<tr><td>' + droplog.proficiency['dual wielding'].toFixed(3) + '</td><td>DW</td></tr>' : '') +
        (droplog.proficiency['cloth armor'] ? '<tr><td>' + droplog.proficiency['cloth armor'].toFixed(3) + '</td><td>cloth</td></tr>' : '') +
        (droplog.proficiency['light armor'] ? '<tr><td>' + droplog.proficiency['light armor'].toFixed(3) + '</td><td>light</td></tr>' : '') +
        (droplog.proficiency['heavy armor'] ? '<tr><td>' + droplog.proficiency['heavy armor'].toFixed(3) + '</td><td>heavy</td></tr>' : '') +
        (droplog.proficiency.staff ? '<tr><td>' + droplog.proficiency.staff.toFixed(3) + '</td><td>staff</td></tr>' : '') +
        (droplog.proficiency['elemental magic'] ? '<tr><td>' + droplog.proficiency['elemental magic'].toFixed(3) + '</td><td>elem</td></tr>' : '') +
        (droplog.proficiency['divine magic'] ? '<tr><td>' + droplog.proficiency['divine magic'].toFixed(3) + '</td><td>holy</td></tr>' : '') +
        (droplog.proficiency['forbidden magic'] ? '<tr><td>' + droplog.proficiency['forbidden magic'].toFixed(3) + '</td><td>dark</td></tr>' : '') +
        (droplog.proficiency['deprecating magic'] ? '<tr><td>' + droplog.proficiency['deprecating magic'].toFixed(3) + '</td><td>dep</td></tr>' : '') +
        (droplog.proficiency['supportive magic'] ? '<tr><td>' + droplog.proficiency['supportive magic'].toFixed(3) + '</td><td>sup</td></tr>' : '') +
        '</tbody>'; }

function ShowDrops(end) {
    var btcp = document.getElementById('btcp');
    if ( end && !btcp ) return;
    if ( !end && !btcp ) {
        if ( (btcp = document.getElementById('dropbox')) ) {
            btcp.parentNode.removeChild(btcp);
            return; }
        btcp = document.createElement('div');
        btcp.id = 'dropbox';
        document.getElementById('pane_completion').appendChild(btcp); }
    if ( end && cfg.selectLog ) {
        document.querySelector('img[src$="finishbattle.png"]').setAttribute('onclick', btcp.getAttribute('onclick'));
        btcp.removeAttribute('onclick'); }
    var span, crystal, credit, peerless, legendary, magnificent, exquisite, superior, average, fair, crude, equipment,
        material, chaos, blood, soul, artifact, figurine, trophy, consumable, food, exp, turns, startTime;
    if ( cfg.consoleLog ) {
        console.log(JSON.stringify(combatlog));
        console.log(JSON.stringify(droplog)); }
    btcp.setAttribute('style', 'display: block; height: auto; min-height: 120px; max-height: 621px; overflow: auto');
    if ( (crystal = droplog.Crystal) ) {
        btcp.appendChild(document.createElement('br'));
        span = btcp.appendChild(document.createElement('span'));
        span.className = 'drop crystal';
        span.innerHTML = crystal.toLocaleString() + (cfg.terseLog ? '\t' : ' ') + 'Crystal' + (crystal > 1 || cfg.terseLog ? 's' : '');
        if ( cfg.detailedCrystlog ) {
            for ( var crystals in droplog.Crystals ) {
                btcp.appendChild(document.createElement('br'));
                span = btcp.appendChild(document.createElement('span'));
                span.className = 'drop crystal';
                span.innerHTML = droplog.Crystals[crystals].toLocaleString() + (cfg.terseLog ? '\t' : 'x ') + crystals; }}}
    if ( (credit = droplog.Credit) ) {
        btcp.appendChild(document.createElement('br'));
        span = btcp.appendChild(document.createElement('span'));
        span.className = 'drop credit';
        span.innerHTML = credit.toLocaleString() + (cfg.terseLog ? '\t' : ' ') + 'Credit' + (credit > 1 || cfg.terseLog ? 's' : ''); }
    if ( cfg.detailedDroplog ) {
        for ( var equips in droplog.Equips ) {
            btcp.appendChild(document.createElement('br'));
            span = btcp.appendChild(document.createElement('span'));
            span.className = 'drop equipment';
            span.innerHTML = droplog.Equips[equips].toLocaleString() + (cfg.terseLog ? '\t' : 'x ') + equips; }}
    else {
        if ( (peerless = droplog.Peerless) ) {
            btcp.appendChild(document.createElement('br'));
            span = btcp.appendChild(document.createElement('span'));
            span.className = 'drop equipment';
            span.innerHTML = peerless.toLocaleString() + (cfg.terseLog ? '\t' : 'x ') + 'Peerless Equipment'; }
        if ( (legendary = droplog.Legendary) ) {
            btcp.appendChild(document.createElement('br'));
            span = btcp.appendChild(document.createElement('span'));
            span.className = 'drop equipment';
            span.innerHTML = legendary.toLocaleString() + (cfg.terseLog ? '\t' : 'x ') + 'Legendary Equipment'; }
        if ( (magnificent = droplog.Magnificent) ) {
            btcp.appendChild(document.createElement('br'));
            span = btcp.appendChild(document.createElement('span'));
            span.className = 'drop equipment';
            span.innerHTML = magnificent.toLocaleString() + (cfg.terseLog ? '\t' : 'x ') + 'Magnificent Equipment'; }
        if ( (exquisite = droplog.Exquisite) ) {
            btcp.appendChild(document.createElement('br'));
            span = btcp.appendChild(document.createElement('span'));
            span.className = 'drop equipment';
            span.innerHTML = exquisite.toLocaleString() + (cfg.terseLog ? '\t' : 'x ') + 'Exquisite Equipment'; }
        if ( (superior = droplog.Superior) ) {
            btcp.appendChild(document.createElement('br'));
            span = btcp.appendChild(document.createElement('span'));
            span.className = 'drop equipment';
            span.innerHTML = superior.toLocaleString() + (cfg.terseLog ? '\t' : 'x ') + 'Superior Equipment'; }
        if ( (average = droplog.Average) ) {
            btcp.appendChild(document.createElement('br'));
            span = btcp.appendChild(document.createElement('span'));
            span.className = 'drop equipment';
            span.innerHTML = average.toLocaleString() + (cfg.terseLog ? '\t' : 'x ') + 'Average Equipment'; }
        if ( (fair = droplog.Fair) ) {
            btcp.appendChild(document.createElement('br'));
            span = btcp.appendChild(document.createElement('span'));
            span.className = 'drop equipment';
            span.innerHTML = fair.toLocaleString() + (cfg.terseLog ? '\t' : 'x ') + 'Fair Equipment'; }
        if ( (crude = droplog.Crude) ) {
            btcp.appendChild(document.createElement('br'));
            span = btcp.appendChild(document.createElement('span'));
            span.className = 'drop equipment';
            span.innerHTML = crude.toLocaleString() + (cfg.terseLog ? '\t' : 'x ') + 'Crude Equipment'; }}
    if ( (equipment = droplog.Equipment) ) {
        btcp.appendChild(document.createElement('br'));
        span = btcp.appendChild(document.createElement('span'));
        span.className = 'drop equipment';
        span.innerHTML = equipment.toLocaleString() + (cfg.terseLog ? '\t' : 'x ') + (cfg.equipmentCutoff > 0 ? 'Lesser ' : '') + 'Equipment'; }
    if ( cfg.detailedDroplog ) {
        for ( var mats in droplog.Mats ) {
            btcp.appendChild(document.createElement('br'));
            span = btcp.appendChild(document.createElement('span'));
            span.className = 'drop equipment';
            span.innerHTML = droplog.Mats[mats].toLocaleString() + (cfg.terseLog ? '\t' : 'x ') + mats; }}
    else {
        if ( (material = droplog.Material) ) {
            btcp.appendChild(document.createElement('br'));
            span = btcp.appendChild(document.createElement('span'));
            span.className = 'drop equipment';
            span.innerHTML = material.toLocaleString() + (cfg.terseLog ? '\t' : ' ') + 'Material' + (material > 1 && !cfg.terseLog ? 's' : ''); }}
    if ( (chaos = droplog.Chaos) ) {
        btcp.appendChild(document.createElement('br'));
        span = btcp.appendChild(document.createElement('span'));
        span.className = 'drop token';
        span.innerHTML = chaos.toLocaleString() + (cfg.terseLog ? '\t' : ' ') + 'Chaos Token' + (chaos > 1 && !cfg.terseLog ? 's' : ''); }
    if ( (blood = droplog.Blood) ) {
        btcp.appendChild(document.createElement('br'));
        span = btcp.appendChild(document.createElement('span'));
        span.className = 'drop token';
        span.innerHTML = blood.toLocaleString() + (cfg.terseLog ? '\t' : ' ') + 'Token' + (blood > 1 && !cfg.terseLog ? 's' : '') + ' of Blood'; }
    if ( (soul = droplog.Soul) ) {
        btcp.appendChild(document.createElement('br'));
        span = btcp.appendChild(document.createElement('span'));
        span.className = 'drop token';
        span.innerHTML = soul.toLocaleString() + (cfg.terseLog ? '\t' : ' ') + ' Soul Fragment' + (cfg.terseLog ? '' : 's'); }
    if ( cfg.detailedDroplog ) {
        for ( var artifacts in droplog.Artifacts ) {
            btcp.appendChild(document.createElement('br'));
            span = btcp.appendChild(document.createElement('span'));
            span.className = 'drop artifact';
            span.innerHTML = droplog.Artifacts[artifacts].toLocaleString() + (cfg.terseLog ? '\t' : 'x ') + artifacts; }
        for ( var figurines in droplog.Figurines ) {
            btcp.appendChild(document.createElement('br'));
            span = btcp.appendChild(document.createElement('span'));
            span.className = 'drop artifact';
            span.innerHTML = droplog.Figurines[figurines].toLocaleString() + (cfg.terseLog ? '\t' : 'x ') + figurines; }
        for ( var trophies in droplog.Trophies ) {
            btcp.appendChild(document.createElement('br'));
            span = btcp.appendChild(document.createElement('span'));
            span.className = 'drop trophy';
            span.innerHTML = droplog.Trophies[trophies].toLocaleString() + (cfg.terseLog ? '\t' : 'x ') + trophies; }
        for ( var consumables in droplog.Consumables ) {
            btcp.appendChild(document.createElement('br'));
            span = btcp.appendChild(document.createElement('span'));
            span.className = 'drop consumable';
            span.innerHTML = droplog.Consumables[consumables].toLocaleString() + (cfg.terseLog ? '\t' : 'x ') + consumables; }
        for ( var foods in droplog.Foods ) {
            btcp.appendChild(document.createElement('br'));
            span = btcp.appendChild(document.createElement('span'));
            span.className = 'drop food';
            span.innerHTML = droplog.Foods[foods].toLocaleString() + (cfg.terseLog ? '\t' : 'x ') + foods; }}
    else {
        if ( (artifact = droplog.Artifact) ) {
            btcp.appendChild(document.createElement('br'));
            span = btcp.appendChild(document.createElement('span'));
            span.className = 'drop artifact';
            span.innerHTML = artifact.toLocaleString() + (cfg.terseLog ? '\t' : ' ') + 'Artifact' + (artifact > 1 && !cfg.terseLog ? 's' : ''); }
        if ( (figurine = droplog.Figurine) ) {
            btcp.appendChild(document.createElement('br'));
            span = btcp.appendChild(document.createElement('span'));
            span.className = 'drop artifact';
            span.innerHTML = figurine.toLocaleString() + (cfg.terseLog ? '\t' : ' ') + 'Figurine' + (figurine > 1 && !cfg.terseLog ? 's' : ''); }
        if ( (trophy = droplog.Trophy) ) {
            btcp.appendChild(document.createElement('br'));
            span = btcp.appendChild(document.createElement('span'));
            span.className = 'drop trophy';
            span.innerHTML = trophy.toLocaleString() + (cfg.terseLog ? '\t' : ' ') + 'Troph' + (trophy > 1 && !cfg.terseLog ? 'ies' : 'y'); }
        if ( (consumable = droplog.Consumable) ) {
            btcp.appendChild(document.createElement('br'));
            span = btcp.appendChild(document.createElement('span'));
            span.className = 'drop consumable';
            span.innerHTML = consumable.toLocaleString() + (cfg.terseLog ? '\t' : ' ') + 'Consumable' + (consumable > 1 && !cfg.terseLog ? 's' : ''); }
        if ( (food = droplog.Food) ) {
            btcp.appendChild(document.createElement('br'));
            span = btcp.appendChild(document.createElement('span'));
            span.className = 'drop food';
            span.innerHTML = food.toLocaleString() + (cfg.terseLog ? '\t' : 'x ') + 'Food'; }}
    if ( (exp = droplog.EXP) ) {
        btcp.appendChild(document.createElement('br'));
        span = btcp.appendChild(document.createElement('span'));
        span.className = 'drop';
        span.innerHTML = exp.toLocaleString() + (cfg.terseLog ? '\t' : ' ') + 'EXP'; }
    if ( cfg.trackProficiency ) {
        for ( var prof in droplog.proficiency ) {
            btcp.appendChild(document.createElement('br'));
            span = btcp.appendChild(document.createElement('span'));
            span.className = 'drop';
            span.innerHTML = droplog.proficiency[prof].toFixed(3) + (cfg.terseLog ? '\t' : ' ') + prof; }}
    if ( cfg.trackSpeed && (turns = timelog.action) && (startTime = timelog.startTime) ) {
        btcp.appendChild(document.createElement('br'));
        btcp.appendChild(document.createElement('br'));
        var time = Math.round((Date.now() - startTime) / 1000.0),
            hours = Math.floor(time / 3600),
            minutes = Math.floor(time / 60) % 60,
            seconds = time % 60,
            tps = turns / time;
        span = btcp.appendChild(document.createElement('span'));
        span.className = 'speed';
        span.innerHTML = turns.toLocaleString() + ' turn' + (turns == 1 ? '' : 's') + '  ' +
                         hours + ':' +
                         (minutes < 10 ? '0' : '') + minutes + ':' +
                         (seconds < 10 ? '0' : '') + seconds + '  (' +
                         tps.toLocaleString() + ' t/s)' +
                         (timelog.horse ? '  ' + timelog.horse + ' horse' + (timelog.horse > 1 ? 's' : '') : ''); }}

function ShowDamage() {
    if ( !cfg.trackDamage ) return;
    var any = { fire:     combatlog.pdealt.fire || combatlog.mdealt.fire || combatlog.ptaken.fire || combatlog.mtaken.fire,
                cold:     combatlog.pdealt.cold || combatlog.mdealt.cold || combatlog.ptaken.cold || combatlog.mtaken.cold,
                elec:     combatlog.pdealt.elec || combatlog.mdealt.elec || combatlog.ptaken.elec || combatlog.mtaken.elec,
                wind:     combatlog.pdealt.wind || combatlog.mdealt.wind || combatlog.ptaken.wind || combatlog.mtaken.wind,
                holy:     combatlog.pdealt.holy || combatlog.mdealt.holy || combatlog.ptaken.holy || combatlog.mtaken.holy,
                dark:     combatlog.pdealt.dark || combatlog.mdealt.dark || combatlog.ptaken.dark || combatlog.mtaken.dark,
                crushing: combatlog.pdealt.crushing || combatlog.mdealt.crushing || combatlog.ptaken.crushing || combatlog.mtaken.crushing,
                slashing: combatlog.pdealt.slashing || combatlog.mdealt.slashing || combatlog.ptaken.slashing || combatlog.mtaken.slashing,
                piercing: combatlog.pdealt.piercing || combatlog.mdealt.piercing || combatlog.ptaken.piercing || combatlog.mtaken.piercing,
                void:     combatlog.pdealt.void || combatlog.mdealt.void || combatlog.ptaken.void || combatlog.mtaken.void,
                dot:      combatlog.pdealt.dot || combatlog.mdealt.dot,
                pdealt:   combatlog.pdealt.dot || combatlog.pdealt.hit || combatlog.pdealt.miss || combatlog.pdealt.evade || combatlog.pdealt.parry,
                mdealt:   combatlog.mdealt.fire || combatlog.mdealt.cold || combatlog.mdealt.elec || combatlog.mdealt.wind ||
                            combatlog.mdealt.dot || combatlog.mdealt.hit || combatlog.mdealt.miss || combatlog.mdealt.evade || combatlog.mdealt.resist,
                ptaken:   combatlog.ptaken.hit || combatlog.ptaken.miss || combatlog.ptaken.evade || combatlog.ptaken.parry || combatlog.ptaken.block,
                mtaken:   combatlog.mtaken.hit || combatlog.mtaken.evade || combatlog.mtaken.block,
                pspirit:  combatlog.ptaken.sfire || combatlog.ptaken.scold || combatlog.ptaken.selec || combatlog.ptaken.swind || combatlog.ptaken.sholy ||
                            combatlog.ptaken.sdark || combatlog.ptaken.scrushing || combatlog.ptaken.sslashing || combatlog.ptaken.spiercing || combatlog.ptaken.svoid,
                mspirit:  combatlog.mtaken.sfire || combatlog.mtaken.scold || combatlog.mtaken.selec || combatlog.mtaken.swind || combatlog.mtaken.sholy ||
                            combatlog.mtaken.sdark || combatlog.mtaken.scrushing || combatlog.mtaken.sslashing || combatlog.mtaken.spiercing || combatlog.mtaken.svoid,
                hit:      combatlog.pdealt.hit || combatlog.mdealt.hit || combatlog.ptaken.hit || combatlog.mtaken.hit,
                r50:      combatlog.mdealt.r50 || combatlog.mtaken.r50,
                r75:      combatlog.mdealt.r75 || combatlog.mtaken.r75,
                r90:      combatlog.mdealt.r90 || combatlog.mtaken.r90,
                miss:     combatlog.pdealt.miss || combatlog.mdealt.miss || combatlog.ptaken.miss,
                evade:    combatlog.pdealt.evade || combatlog.mdealt.evade || combatlog.ptaken.evade || combatlog.mtaken.evade,
                parry:    combatlog.pdealt.parry || combatlog.ptaken.parry,
                resist:   combatlog.mdealt.resist,
                block:    combatlog.ptaken.block || combatlog.mtaken.block };
    if ( !any.pdealt && !any.mdealt && !any.ptaken && !any.mtaken ) return;
    any.dtotal = any.pdealt && any.mdealt;
    any.ttotal = any.ptaken && any.mtaken || any.pspirit || any.mspirit;
    any.none = any.miss || any.evade || any.parry || any.resist || any.block;
    const Row = function(type) {
            return any[type] ? '</tr><tr><td>' + type + '</td>' +
                (any.pdealt ? '<td>' + (combatlog.pdealt[type] ? combatlog.pdealt[type].toLocaleString() : '') + '</td>' : '') +
                (any.mdealt ? '<td>' + (combatlog.mdealt[type] ? combatlog.mdealt[type].toLocaleString() : '') + '</td>' : '') +
                (any.dtotal ? '<td>' + (combatlog.pdealt[type] || combatlog.mdealt[type] ?
                    ((combatlog.pdealt[type] ? combatlog.pdealt[type] : 0) + (combatlog.mdealt[type] ? combatlog.mdealt[type] : 0)).toLocaleString() : '') + '</td>' : '') +
                (any.ptaken ? '<td>' + (combatlog.ptaken[type] ? combatlog.ptaken[type].toLocaleString() : '') + '</td>' : '') +
                (any.pspirit ? '<td>' + (combatlog.ptaken['s'+type] ? combatlog.ptaken['s'+type].toLocaleString() : '') + '</td>' : '') +
                (any.mtaken ? '<td>' + (combatlog.mtaken[type] ? combatlog.mtaken[type].toLocaleString() : '') + '</td>' : '') +
                (any.mspirit ? '<td>' + (combatlog.mtaken['s'+type] ? combatlog.mtaken['s'+type].toLocaleString() : '') + '</td>' : '') +
                (any.ttotal ? '<td>' + (combatlog.ptaken[type] || combatlog.mtaken[type] ?
                    ((combatlog.ptaken[type] ? combatlog.ptaken[type] : 0) + (combatlog.ptaken['s'+type] ? combatlog.ptaken['s'+type]: 0) +
                     (combatlog.mtaken[type] ? combatlog.mtaken[type] : 0) + (combatlog.mtaken['s'+type] ? combatlog.mtaken['s'+type] : 0)).toLocaleString() : '') +
                    '</td>' : '') : ''; },
        Column = function(type, s) {
            return (combatlog[type][s+'fire'] ? combatlog[type][s+'fire'] : 0) + (combatlog[type][s+'cold'] ? combatlog[type][s+'cold'] : 0) +
                (combatlog[type][s+'elec'] ? combatlog[type][s+'elec'] : 0) + (combatlog[type][s+'wind'] ? combatlog[type][s+'wind'] : 0) +
                (combatlog[type][s+'holy'] ? combatlog[type][s+'holy'] : 0) + (combatlog[type][s+'dark'] ? combatlog[type][s+'dark'] : 0) +
                (combatlog[type][s+'crushing'] ? combatlog[type][s+'crushing'] : 0) + (combatlog[type][s+'slashing'] ? combatlog[type][s+'slashing'] : 0) +
                (combatlog[type][s+'piercing'] ? combatlog[type][s+'piercing'] : 0) + (combatlog[type][s+'void'] ? combatlog[type][s+'void'] : 0) +
                (combatlog[type][s+'dot'] ? combatlog[type][s+'dot'] : 0); },
        ColumnNone = function(type) {
            return (combatlog[type].miss ? combatlog[type].miss : 0) + (combatlog[type].evade ? combatlog[type].evade : 0) +
                (combatlog[type].parry ? combatlog[type].parry : 0) + (combatlog[type].resist ? combatlog[type].resist : 0) +
                (combatlog[type].block ? combatlog[type].block : 0); },
        Count = function(type) {
            return '<td>' + type + '</td>' +
                (any.pdealt ? '<td>' + (combatlog.pdealt[type] ? combatlog.pdealt[type].toLocaleString() : '') + '</td>' : '') +
                (any.mdealt ? '<td>' + (combatlog.mdealt[type] ? combatlog.mdealt[type].toLocaleString() : '') + '</td>' : '') +
                (any.dtotal ? '<td>' + (combatlog.pdealt[type] || combatlog.mdealt[type] ?
                                       (combatlog.pdealt[type] + combatlog.mdealt[type]).toLocaleString() : '') + '</td>' : '') +
                (any.ptaken ? '<td>' + (combatlog.ptaken[type] ? combatlog.ptaken[type].toLocaleString() : '') + '</td>' : '') +
                (any.pspirit ? '<td>' + (combatlog.ptaken['s'+type] ? combatlog.ptaken['s'+type].toLocaleString() : '') + '</td>' : '') +
                (any.mtaken ? '<td>' + (combatlog.mtaken[type] ? combatlog.mtaken[type].toLocaleString() : '') + '</td>' : '') +
                (any.mspirit ? '<td>' + (combatlog.mtaken['s'+type] ? combatlog.mtaken['s'+type].toLocaleString() : '') + '</td>' : '') +
                (any.ttotal ? '<td>' + (combatlog.ptaken[type] || combatlog.mtaken[type] ?
                                       (combatlog.ptaken[type] + combatlog.mtaken[type]).toLocaleString() : '') + '</td>' : ''); };
    var total = { pdealt: Column('pdealt', ''), mdealt: Column('mdealt', ''),
                  ptaken: Column('ptaken', ''), pspirit: Column('ptaken', 's'),
                  mtaken: Column('mtaken', ''), mspirit: Column('mtaken', 's'),
                  pdnone: ColumnNone('pdealt'), mdnone: ColumnNone('mdealt'),
                  ptnone: ColumnNone('ptaken'), mtnone: ColumnNone('mtaken'), };
    total.dealt = total.pdealt + total.mdealt;
    total.taken = total.ptaken + total.pspirit + total.mtaken + total.mspirit;
    total.dnone = total.pdnone + total.mdnone;
    total.tnone = total.ptnone + total.mtnone;
    var damagelog = log.parentNode.insertBefore(document.createElement('table'), log);
    damagelog.id = 'damagelog';
    damagelog.innerHTML = '<tbody><tr><td></td>' +
        (any.pdealt || any.mdealt ? '<td' + (any.dtotal ? ' colspan="3"' : '') + '>damage dealt</td>' : '') +
        (any.ptaken || any.mtaken ? '<td' + (any.ttotal ? ' colspan="' +
            ((any.ptaken ? 1 : 0) + (any.mtaken ? 1 : 0) + (any.pspirit ? 1 : 0) + (any.mspirit ? 1 : 0) + 1) + '"' : '') + '>damage taken</td>' : '') +
        '</tr><tr><td></td>' +
        (any.pdealt ? '<td>physical</td>' : '') + (any.mdealt ? '<td>magical</td>' : '') +
        (any.dtotal ? '<td>total</td>' : '') +
        (any.ptaken ? '<td>physical</td>' : '') + (any.pspirit ? '<td style="border-left:none">spirit</td>' : '') +
        (any.mtaken ? '<td>magical</td>' : '') + (any.mspirit ? '<td style="border-left:none">spirit</td>' : '') +
        (any.ttotal ? '<td>total</td>' : '') +
        Row('fire') + Row('cold') + Row('elec') + Row('wind') + Row('holy') + Row('dark') + Row('crushing') + Row('slashing') + Row('piercing') + Row('void') + Row('dot') +
        '</tr><tr><td>total</td>' +
        (any.pdealt ? '<td>' + total.pdealt.toLocaleString() + '</td>' : '') + (any.mdealt ? '<td>' + total.mdealt.toLocaleString() + '</td>' : '') +
        (any.dtotal ? '<td>' + total.dealt.toLocaleString() + '</td>' : '') +
        (any.ptaken ? '<td>' + total.ptaken.toLocaleString() + '</td>' : '') + (any.pspirit ? '<td>' + total.pspirit.toLocaleString() + '</td>' : '') +
        (any.mtaken ? '<td>' + total.mtaken.toLocaleString() + '</td>' : '') + (any.mspirit ? '<td>' + total.mspirit.toLocaleString() + '</td>' : '') +
        (any.ttotal ? '<td>' + total.taken.toLocaleString() + '</td>' : '') +
        (any.hit ? '</tr><tr style="border-top:1px solid">' + Count('hit') + '</tr><tr>' + Count('crit') +
        (any.r50 ? '</tr><tr>' + Count('r50') : '') + (any.r75 ? '</tr><tr>' + Count('r75') : '') + (any.r90 ? '</tr><tr>' + Count('r90') : '') : '') +
        (any.none ? '</tr><tr style="border-top:1px solid">' + (any.miss ? '</tr><tr>' + Count('miss') : '') + (any.evade ? '</tr><tr>' + Count('evade') : '') +
        (any.parry ? '</tr><tr>' + Count('parry') : '') + (any.resist ? '</tr><tr>' + Count('resist') : '') + (any.block ? '</tr><tr>' + Count('block') : ''): '') +
        (any.none ? '</tr><tr><td>total</td>' +
        (any.pdealt ? '<td>' + total.pdnone.toLocaleString() + '</td>' : '') + (any.mdealt ? '<td>' + total.mdnone.toLocaleString() + '</td>' : '') +
        (any.dtotal ? '<td>' + total.dnone.toLocaleString() + '</td>' : '') +
        (any.ptaken ? '<td>' + total.ptnone.toLocaleString() + '</td>' : '') + (any.pspirit ? '<td></td>' : '') +
        (any.mtaken ? '<td>' + total.mtnone.toLocaleString() + '</td>' : '') + (any.mspirit ? '<td></td>' : '') +
        (any.ttotal ? '<td>' + total.tnone.toLocaleString() + '</td>' : '') : '') + '</tr></tbody>'; }

function ShowUsage() {
    if ( !cfg.trackUsage || JSON.stringify(combatlog.used) == '{}' ) return;
    var td = log.insertBefore(document.createElement('tbody'), log.firstChild).appendChild(document.createElement('tr')).appendChild(document.createElement('td'));
    td.className = 'tlb';
    td.innerHTML = 'Used: ' + JSON.stringify(combatlog.used).replace('{','').replace('}','').replace(/"/g,'').replace(/:/g,': ').replace(/,/g,', '); }

function NoPopup() {
    var btcp;
    if ( !(btcp = document.getElementById('btcp')) ) return;
    if ( cfg.ajaxRound ) {
        btcp.onclick = function() {
            var x = new XMLHttpRequest();
            x.onreadystatechange = function() {
                if ( x.readyState == XMLHttpRequest.DONE ) {
                    if ( x.status == 200 ) {
                        var doc = (new DOMParser()).parseFromString(x.responseText, 'text/html');
                        document.body.innerHTML = doc.body.innerHTML;
                        var script = document.createElement('script');
                        script.type = 'text/javascript';
                        if ( doc.getElementById('riddlemaster') ) {
                            script.innerHTML = document.getElementsByTagName('script')[2].innerHTML.replace(
                                'e("riddleanswer").value = "?";', '').replace('e("riddleform").submit();', ''); }
                        else {
                            script.innerHTML = 'var t = setTimeout(function(){}, 0); for ( var i = t; i > 0' +
                                               (cfg.ajaxIntervals ? ' && i > t - ' + cfg.ajaxIntervals : '') +
                                               '; i-- ) clearInterval(i); battle = new Battle();'; }
                        document.getElementById('mainpane').appendChild(script);
                        var event = new Event('DOMContentLoaded');
                        document.dispatchEvent(event); }
                    else {
                        alert('error during round transition: code ' + x.status); }}};
            x.open('GET', document.location.href, true);
            x.send(); };}
    monsterData = {};
    localStorage.removeItem('HVmonsterData');
    if ( cfg.showRound ) {
        timelog.round++; }
    if ( cfg.noPopup && (!cfg.stopOnEquipDrop || !regexp.quality[cfg.equipmentCutoff].test(log.innerHTML)) ) {
        btcp.click(); }
    else {
        btcp.setAttribute('style', 'display: block'); }}

// main function running out of combat
function OutOfCombat() {
    DeleteLog();
    ProfileSwitch();
    SettingsLink(); }

// out-of-combat helper functions
function DeleteLog() {
    localStorage.removeItem('HVmonsterData' + isekai);
    localStorage.removeItem('HVtimelog' + isekai);
    localStorage.removeItem('HVvitals' + isekai);
    localStorage.removeItem('HVcursor' + isekai);
    if ( cfg.deleteDropLog == 2 || (cfg.deleteDropLog == 1 && document.URL.indexOf('Battle') < 0) ) {
        localStorage.removeItem('HVtrackdrops' + isekai); }
    if ( cfg.deleteCombatLog == 2 || (cfg.deleteCombatLog == 1 && document.URL.indexOf('Battle') < 0) ) {
        localStorage.removeItem('HVcombatlog' + isekai); }}

function ProfileSwitch() {
    if ( !cfg.profileAutoswitch ) return;
    var choice;
    if ( (choice = document.querySelector('[name="persona_set"] [selected]')) ) {
        profile[(isekai ? 'i' : '') + 'p'] = choice.value; }
    if ( (choice = document.querySelector(['[src*="equip/set"][src$="_on.png"]'])) ) {
        profile[(isekai ? 'i' : '') + 's' + profile[(isekai ? 'i' : '') + 'p']] = parseInt(choice.src.match(regexp.number)); }
    if ( JSON.stringify(profile) != localStorage.HVmbp ) {
        localStorage.HVmbp = JSON.stringify(profile); }}

function SettingsLink() {
    var character;
    if ( !cfg.cfgInterface || !(character = document.getElementById('child_Character')) ) return;
    var div = character.firstChild.appendChild(document.createElement('div')),
        link = div.appendChild(document.createElement('div')),
        def = regexp.defaultletter.test(character.innerHTML);
    document.head.appendChild(document.createElement('style')).innerHTML = '#mbsettings { cursor: pointer; }' +
        '#mbsettings:hover #mbprofile { visibility: visible; } #mbprofile { visibility: hidden; position: relative; left: ' + (def ? '223' : '175') +
        'px; top: -' + (def ? '9' : '25') + 'px; display: block; width: max-content; height: max-content; padding: 5px; background: #EDEBDF; border: 1px solid #5C0D11; }';
    div.id = 'mbsettings';
    link.onclick = Settings;
    link.className = def ? 'fl f4b' : 'fc4 fal fcb';
    if ( def ) {
        character.style.width = '228px';
        link.appendChild(document.createElement('div')).className = 'c5m';
        link.appendChild(document.createElement('div')).className = 'c5o';
        link.appendChild(document.createElement('div')).className = 'c5n';
        link.appendChild(document.createElement('div')).className = 'c5s';
        link.appendChild(document.createElement('div')).className = 'c5t';
        link.appendChild(document.createElement('div')).className = 'c5e';
        link.appendChild(document.createElement('div')).className = 'c5r';
        link.appendChild(document.createElement('div')).className = 'c5b';
        link.appendChild(document.createElement('div')).className = 'c5a';
        link.appendChild(document.createElement('div')).className = 'c5t';
        link.appendChild(document.createElement('div')).className = 'c5i';
        link.appendChild(document.createElement('div')).className = 'c5o';
        link.appendChild(document.createElement('div')).className = 'c5n';
        link.appendChild(document.createElement('div')).className = 'c59';
        link.appendChild(document.createElement('div')).className = 'c5s';
        link.appendChild(document.createElement('div')).className = 'c5e';
        link.appendChild(document.createElement('div')).className = 'c5t';
        link.appendChild(document.createElement('div')).className = 'c5t';
        link.appendChild(document.createElement('div')).className = 'c5i';
        link.appendChild(document.createElement('div')).className = 'c5n';
        link.appendChild(document.createElement('div')).className = 'c5g';
        link.appendChild(document.createElement('div')).className = 'c5s'; }
    else {
        link.appendChild(document.createElement('div')).innerHTML = 'Monsterbation Settings'; }
    var hasper = regexp.profile.test(JSON.stringify(cfg.persona));
    var hasisk = regexp.profile.test(JSON.stringify(cfg.isekai.persona)) || (hasper && cfg.isekaiInherit);
    if ( (!isekai && hasper) || (isekai && hasisk) ) {
        var menu = div.appendChild(document.createElement('div'));
        const Set = function(i, p, s) {
            return function() {
                var blue;
                if ( (blue = menu.querySelector('[style*="' + (isekai ? 'red' : 'blue') + '"')) ) {
                    blue.style.color = ''; }
                profile[isekai + 'p'] = p;
                if (p) {
                    profile[isekai + 's' + profile[isekai + 'p']] = s; }
                if ( JSON.stringify(profile) != localStorage.HVmbp ) {
                    localStorage.HVmbp = JSON.stringify(profile); }
                i.style.color = isekai ? 'red' : 'blue'; };};
        menu.id = 'mbprofile';
        menu.className = 'fc4 fal fcb';
        var base = menu.appendChild(document.createElement('div')), blue = false;
        base.onclick = Set(base, 0, 0);
        if ( !isekai ) {
            base.innerHTML = cfg.name;
            for ( var i = 0; i < cfg.persona.length; i++ ) {
                if ( regexp.profile.test(JSON.stringify(cfg.persona[i])) ) {
                    var persona = menu.appendChild(document.createElement('div'));
                    persona.innerHTML = '- ' + cfg.persona[i].name;
                    persona.onclick = Set(persona, i+1, 0);
                    for ( var j = 0; j < cfg.persona[i].set.length; j++ ) {
                        if ( regexp.profile.test(JSON.stringify(cfg.persona[i].set[j])) ) {
                            var set = menu.appendChild(document.createElement('div'));
                            set.innerHTML = '-- ' + cfg.persona[i].set[j].name;
                            set.onclick = Set(set, i+1, j+1);
                            if ( profile.p == i+1 && profile['s' + profile.p] == j+1 ) {
                                set.style.color = 'blue';
                                blue = true; }}}
                    if ( profile.p == i+1 && (!blue || profile['s' + profile.p] == 0) ) {
                        persona.style.color = 'blue';
                        blue = true; }}}
            if ( !blue || profile.p == 0 ) {
                base.style.color = 'blue'; }}
        else {
            base.innerHTML = cfg.isekai.name;
            for ( i = 0; i < cfg.isekai.persona.length; i++ ) {
                hasisk = regexp.profile.test(JSON.stringify(cfg.isekai.persona[i]));
                hasper = regexp.profile.test(JSON.stringify(cfg.persona[i])) && cfg.isekaiInherit;
                if ( hasisk || hasper ) {
                    persona = menu.appendChild(document.createElement('div'));
                    persona.innerHTML = '- ' + (hasisk ? cfg.isekai.persona[i].name : cfg.persona[i].name);
                    persona.onclick = Set(persona, i+1, 0);
                    for ( j = 0; j < cfg.isekai.persona[i].set.length; j++ ) {
                        hasisk = regexp.profile.test(JSON.stringify(cfg.isekai.persona[i].set[j]));
                        hasper = regexp.profile.test(JSON.stringify(cfg.persona[i].set[j])) && cfg.isekaiInherit;
                        if ( hasisk || hasper ) {
                            set = menu.appendChild(document.createElement('div'));
                            set.innerHTML = '-- ' + (hasisk ? cfg.isekai.persona[i].set[j].name : cfg.persona[i].set[j].name);
                            set.onclick = Set(set, i+1, j+1);
                            if ( profile.ip == i+1 && profile['is' + profile.ip] == j+1 ) {
                                set.style.color = 'red';
                                blue = true; }}}
                    if ( profile.ip == i+1 && (!blue || profile['is' + profile.ip] == 0) ) {
                        persona.style.color = 'red';
                        blue = true; }}}
            if ( !blue || profile.ip == 0 ) {
                base.style.color = 'red'; }}}}

function LoadCfg(p, s, i) {
    var local, value;
    if ( settings.cfgInterface && (local = localStorage.HVmbcfg) ) {
        local = JSON.parse(local); }
    var persona = local && local.persona ? local.persona : settings.persona;
    var isk = local && local.isekai ? local.isekai : settings.isekai;
    for ( var setting in settings ) {
        cfg[setting] = i && p && s && ((value = isk.persona[p-1].set[s-1].settings[setting]) || value === false || value === 0) ? value :
                       ((!i || cfg.isekaiInherit) && p && s && ((value = persona[p-1].set[s-1].settings[setting]) || value === false || value === 0) ? value :
                       (i && p && ((value = isk.persona[p-1].settings[setting]) || value === false || value === 0) ? value :
                       ((!i || cfg.isekaiInherit) && p && ((value = persona[p-1].settings[setting]) || value === false || value === 0) ? value :
                       (i && ((value = isk.settings[setting]) || value === false || value === 0) ? value :
                       (local && ((value = local[setting]) || value === false || value === 0) ? value : settings[setting])))));
        if ( typeof(settings[setting]) == 'object' && !(settings[setting] instanceof Array) ) {
            for ( var item in settings[setting] ) {
                if ( !cfg[setting][item] && !(cfg[setting][item] === false) ) {
                    cfg[setting][item] = settings[setting][item]; }}}}
    cfg.bind = cfg.bind.replace(regexp.whitespace,''); }

function StoreTmp() {
    if ( (cfg.showMonsterHP || cfg.shortenHPbars || cfg.monsterKeywords) && JSON.stringify(monsterData) != '{}' ) {
        localStorage['HVmonsterData' + isekai] = JSON.stringify(monsterData); }
    if ( cfg.showCooldowns || cfg.trackSpeed || cfg.showRound ) {
        localStorage['HVtimelog' + isekai] = JSON.stringify(timelog); }
    if ( cfg.trackDamage || cfg.trackUsage ) {
        localStorage['HVcombatlog' + isekai] = JSON.stringify(combatlog); }
    if ( cfg.maxVitals && JSON.stringify(vitals) != localStorage['HVvitals' + isekai] ) {
        localStorage['HVvitals' + isekai] = JSON.stringify(vitals); }
    if ( cfg.trackDrops || cfg.trackProficiency || cfg.proficiencySidebar ) {
        localStorage['HVtrackdrops' + isekai] = JSON.stringify(droplog); }
    if ( cursor >= 0 ) localStorage['HVcursor' + isekai] = cursor; }

// default font parser
function ParseDefault(div) {
    var string = '', letters = div.innerHTML.match(regexp.defaultstring);
    for ( var j = 0; j < letters.length; j++ ) {
        string += letters[j].match(regexp.defaultletter)[1]; }
    return string; }

if ( !profile.p ) { profile.p = 0; }
if ( !profile.ip ) { profile.ip = 0; }
for ( var i = 1; i < 10; i++ ) {
    if ( !profile['s' + i] ) { profile['s' + i] = 0; }
    if ( !profile['is' + i] ) { profile['is' + i] = 0; }}
if ( !droplog.Mats ) { droplog.Mats = {}; }
LoadCfg(profile[isekai + 'p'], profile[isekai + 's' + profile[isekai + 'p']], isekai);
if ( document.getElementById('textlog') || document.getElementById('riddlemaster') ) {
    Enhance();
    document.addEventListener('DOMContentLoaded', Enhance);
    window.addEventListener('beforeunload', StoreTmp); }
else { OutOfCombat(); }