Mob Editor.js

Note: After saving, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Internet Explorer: Hold Ctrl while clicking Refresh, or press Ctrl-F5
  • Opera: Go to Menu → Settings (Opera → Preferences on a Mac) and then to Privacy & security → Clear browsing data → Cached images and files.
/* globals stratics, mw, $, jQuery */
/* jshint esversion:6 */
(function() {
"use strict";

let i,
    initialized = false,
    elementCounter = 0,
    tabcounter = 20;
const br = '<br>',
    input = 'input',
    label = 'label',
    legend = 'legend',
    loc = location.href.split('#')[0].toString(),
    makehtml = stratics.f.util.makehtml,
    nbsp = '&nbsp;',
    number = 'number',
    span = 'span',
    logs = [],
    tabs = {},
    fields = {},
    boolFields = [
        ['Autodispel', 'autoDispel'],
        ['Loreable', 'unloreable'],
        ['Paragon', 'disableParagon'],
        ['Polymorphable', 'polymorphable'],
        ['Spawning', 'spawning'],
        ['Statue', 'statue'],
        ['Subdue', 'subdueRequired'],
        ['Summonable', 'summonable'],
        ['Tameable', 'tameable']
    ],
    textFields = [
        ['', 'abilities'], // Deprecated parameter
        ['', 'FamilyTree'], // Deprecated parameter
        ['', 'Mobile'], // Deprecated parameter
        ['', 'loot_carved'], // Deprecated parameter
        ['', 'loot_harvester'], // Deprecated parameter
        ['added', 'firstSeen'],
        ['alignment', 'alignment'],
        ['bardNotes', 'bardingNotes'],
        ['bodytype', 'bodytype'],
        ['combatNotes', 'combatStrategy'],
        ['commonItems', 'loot_base'],
        ['fame', 'fame'],
        ['generalInfo', 'body_text'],
        ['gold', 'loot_gold'],
        ['hues', 'hues'],
        ['karma', 'karma'],
        ['loyalty', 'loyaltyPoints'],
        ['minMount', 'mountskill'],
        ['minTame', 'taming'],
        ['name', 'name'],
        ['poisonLevel', 'poisonLevel'],
        ['polyItem', 'polymorphPage'],
        ['quest', 'questPage'],
        ['ranged', 'rangedAttack'],
        ['slayer', 'slayer'],
        ['spawn', 'found'],
        ['specialItems', 'loot_special'],
        ['speed', 'speed'],
        ['statue', 'statuePage'],
        ['stealing', 'loot_stealing'],
        ['summonItem', 'summonPage'],
        ['tameNotes', 'taming_notes'],
        ['trivia', 'trivia_text'],
        ['rawRibs', 'rawRibs'],
        ['pileHidesN', 'pileHidesN'],
        ['pileHidesS', 'pileHidesS'],
        ['pileHidesH', 'pileHidesH'],
        ['pileHidesB', 'pileHidesB'],
        ['rawBird', 'rawBird'],
        ['rawLamb', 'rawLamb'],
        ['rawChicken', 'rawChicken'],
        ['feathers', 'feathers'],
        ['dragonBlood', 'dragonBlood'],
        ['carvedOther', 'carvedOther'],
        ['dragonScaleBlack', 'dragonScaleBlack'],
        ['dragonScaleBlue', 'dragonScaleBlue'],
        ['dragonScaleGreen', 'dragonScaleGreen'],
        ['dragonScaleRed', 'dragonScaleRed'],
        ['dragonScaleWhite', 'dragonScaleWhite'],
        ['dragonScaleYellow', 'dragonScaleYellow'],
        ['classification', 'classification'],
        ['trainableMagicalAbility', 'trainableMagicalAbility'],
        ['trainableSpecialAbility', 'trainableSpecialAbility'],
        ['trainableSpecialMove', 'trainableSpecialMove'],
        ['trainableAreaEffect', 'trainableAreaEffect']
    ];

stratics.data.AbilityClassifications = ['Clawed', 'Insectoid', 'Magical', 'Necromantic', 'Sticky Skin', 'Tailed', 'Tokuno-Native', 'Untrainable'].sort();
stratics.data.AbilityClassifications.push('none');
stratics.data.AreaEffectAbilities = ['Aura of Energy', 'Explosive Goo', 'Essence of Earth', 'Aura of Nausea', 'Poison Breath', 'Essence of Disease'].sort();
stratics.data.AreaEffectAbilities.push('none');
stratics.data.MagicalAbilities = ['Chivalry', 'Discordance', 'Magery', 'Necromancy', 'Mysticism', 'Magery Mastery', 'Necromage', 'Bushido', 'Ninjitsu', 'Poisoning', 'Piercing', 'Bashing', 'Slashing', 'Battle Defense', 'Wrestling Mastery', 'Spellweaving'].sort();
stratics.data.MagicalAbilities.push('none');
stratics.data.SpecialAbilities = ['Angry Fire', 'Conductive Blast', 'Dragon Breath', 'Grasping Claw', 'Inferno', 'Lightning Force', 'Mana Drain', 'Raging Breath', 'Repel', 'Searing Wounds', 'Steal Life', 'Venomous Bite', 'Vicious Bite', 'Rune Corruption', 'Life Leech', 'Sticky Skin', 'Tail Swipe'].sort();
stratics.data.SpecialAbilities.push('none');
stratics.data.SpecialMoves = ['Cold Wind', 'Nerve Strike', 'Talon Strike', 'Psychic Attack', 'Armor Ignore', 'Armor Pierce', 'Bladeweave', 'Bleed', 'Block', 'Concussion Blow', 'Crushing Blow', 'Dismount', 'Feint', 'Force of Nature', 'Frenzied Whirlwind', 'Mortal Strike', 'Paralyze', 'Whirlwind Attack', 'Disarm'].sort();
stratics.data.SpecialMoves.push('none');

$('body').data('datafields', []);

/* Script-specific localization */
const lstrings = [];

const l = (str, id) => {
    lstrings.push([id, str]);
    let translatedStr = stratics.f.util.l(str);
    if (undefined !== translatedStr) {
        return translatedStr;
    } else {
        stratics.f.util.log(3, `New string: ${str}`);
        return str;
    }
};

const tabsUpdate = () => {
    $('#tabControlAL2 li').each(function() {
        let a = $(this).find('a');
        a.text(stratics.f.util.l(a.data('value')));
    });
};

const lupdate = () => {
    for (let x of lstrings) {
        let $ele = $(`#${x[0]}`);
        if ($ele.is('input[type=button]')) {
            $ele.prop('value', stratics.f.util.l(x[1]));
        } else if ($ele.is('input[type=text]')) {
            $ele.prop('placeholder', stratics.f.util.l(x[1]));
        } else {
            $ele.text(stratics.f.util.l(x[1]));
        }
    }
    tabsUpdate();
    if ($('#editToolMenuLangSelect').val() !== stratics.f.util.getLang) {
        $('#editToolMenuLangSelect').val(stratics.f.util.getLang);
    }
};

/* Logs a message to the debug console. */
/* 3 for status, 4 for text dump. */
stratics.f.util.log = (i, msg) => {
    const $console = $(`#debugConsole${i}`);
    if (i === 3) {
        logs.push(msg);
    }
    if ($('#debugConsole3').length > 0) {
        if (i === 3) {
            for (let logmsg of logs) {
                $console.html(`${$console.html()}${stratics.f.util.getTime()}: ${logmsg}${br}`);
            }
            logs.length = 0;
        } else if (i === 4) {
            $console.html(msg);
        }
    }
};
stratics.f.util.log(3, 'Starting script.');

/* Returns the maximum value from an array. */
stratics.f.util.getMax = (arr) => Math.max(...arr);

/* Returns the minimum value from an array, ignoring null values. */
stratics.f.util.getMin = (arr) => Math.min(...arr);

/* Event handler for markup AJAX request */
$('body').on('markupArrived', (e) => {
    if (!initialized) {
        const log = stratics.f.util.log,
            makeBoolean = a => /[1yot]/i.test(`${a}` [0]) ? true : a.length > 0 && /[0nf]/i.test(`${a}` [0]) ? false : 'unknown',
            getVal = (str) => {
                try {
                    let x = new RegExp(`\\|\\s?${str}((?!<!--)(?![\r\n]+\\|)[\\W\\w])+`, 'gm');
                    stratics.data[str] = stratics.wiki.markup2.match(x)[0].split(/=\s/)[1].trim().replace(/{{!}}/g,'|').trim();
                        if (str === 'bodytype' || str === 'hues') {
                            stratics.data[str] = stratics.data[str].replace(/,\s?/g,', ').trim();
                        }
                } catch (e) { // requested parameter does not exist in the markup, or does not have a value in the markup
                    if (str === 'name') {
                        stratics.data[str] = loc.split(':')[2].split('(')[0]; // Get the name from the URL
                        stratics.data.name = stratics.data.name.replace(/_/g,' ');
                    } else if (['alignment','speed','classification','trainableMagicalAbility','trainableSpecialAbility','trainableSpecialMove','trainableAreaEffect'].indexOf(str)  > -1) {
                        stratics.data[str] = 'unknown';
                    } else {
                        stratics.data[str] = '';
                    }
                }
                return stratics.data[str];
            },
            getNum = (str) => {
                switch (str) {
                    case 'none':
                        return 0;
                    case 'lesser':
                        return 1;
                    case 'standard':
                        return 2;
                    case 'greater':
                        return 3;
                    case 'deadly':
                        return 4;
                    case 'lethal':
                        return 5;
                    default:
                        return str;
                }
            };

        log(3, 'Markup has been received.');
        log(4, stratics.wiki.markup);

        stratics.wiki.markup = stratics.wiki.markup.replace(/<!-- ------------------------------------------------------------------------ -->/g, '')
            .replace('<!-- WRITE TEXT FOR THE MAIN TOP SUMMARY OF THIS MOB HERE. -->', '')
            .replace('<!-- RANDOM TRIVIA TEXT GOES HERE. -->', '')
            .replace('<!-- WRITE TEXT ABOUT BARDING THIS MOBILE HERE-->', '')
            .replace('<!-- WRITE TEXT ABOUT HOW TO FIGHT THIS MOBILE HERE-->', '')
            .replace('<!-- WRITE TEXT ABOUT HOW TO TAME THIS MOBILE HERE-->', '');
        let loreData = stratics.wiki.markup.split('<!-- Everything below');
        if (loreData.length > 1) {
            stratics.wiki.loreData = '<!-- Everything below ' + loreData[loreData.length - 1].replace('}}','').trim();
        } else {
            stratics.wiki.loreData = '';
        }
        stratics.wiki.markup2 = loreData[0].trim().replace(/}}$/,'');

        log(3, 'Storing old info from markup.');

        for (let x of textFields) {
            if (x[0].length > 0) { // This lets us use zero-length strings in the textFields array to remove depricated params
                if (['alignment','speed','classification','trainableMagicalAbility','trainableSpecialAbility','trainableSpecialMove','trainableAreaEffect'].indexOf(x[0])  > -1) {
                    $(`#${fields[x[0]]}`).val(getNum(getVal(x[1])));
                    $('body').data('datafields').push([$(`#${fields[x[0]]}`), x[1], 'selectable']);
                } else {
                    $(`#${fields[x[0]]}`).val(getVal(x[1]));
                    $('body').data('datafields').push([$(`#${fields[x[0]]}`), x[1], 'text']);
                }
            }
        }
        for (let x of boolFields) {
            stratics.data[x[1]] = makeBoolean(getVal(x[1]));
            if (stratics.data[x[1]] === true) {
                $(`#editToolMenu${x[0]}`).val(true).attr('checked','checked');
            } else if (stratics.data[x[1]] === 'unknown') {
                $(`#editToolMenu${x[0]}`)[0].indeterminate = true;
            }
            $('body').data('datafields').push([$(`#editToolMenu${x[0]}`), x[1], 'boolean']);
        }
        log(3, 'Old info has been stored.');

        log(3, 'Selecting the selectables.');
        for (let x of ['alignment', 'speed', 'classification','trainableMagicalAbility','trainableSpecialAbility','trainableSpecialMove','trainableAreaEffect']) {
            var selectedOptions = stratics.data[x].split(', ');
            for (let i of selectedOptions) {
                if (i === 'Chivilry') i = 'Chivalry'; // Update to correct stored typos
                if (i === 'Wrestle Mastery') i = 'Wrestling Mastery'; // Update to correct stored typos
                $(`#${fields[x]}`).find(`[value="${i}"]`).addClass("ui-selecting");
            }
            $(`#${fields[x]}`).selectable('refresh').data("selectable")._mouseStop(null);
        }

        log(3, 'Clearing old info from markup.');
        let cleanerRE = [];
        for (var x of [...textFields, ...boolFields]) {
            cleanerRE.push(x[1]);
        }
        cleanerRE = new RegExp(`\\|\\s?(?:${cleanerRE.join('|')})((?!<!--)(?![\r\n]+\\|)[\\W\\w](?!\n}}))+`, 'gm'); // http://stackoverflow.com/questions/43290667
        stratics.wiki.markup2 = stratics.wiki.markup2.replace(/\n\s*\|/g, '\n|').replace(cleanerRE, '').replace(/\n+(\n\||<!--)/g, '\n|').trim();
        stratics.wiki.excessMarkup = stratics.wiki.markup2.replace('{{Mobile','').trim();
        log(3, 'Markup has been cleaned.');

        log(4, stratics.wiki.markup2);
        $('body').trigger('markupReady');

        initialized = true;
    }
});

/* Event handler for when form is ready to be submitted. */
$('body').on('markupReady', function() {
    $('#editToolMenuSubmit2').button('enable');
});

/* Event handler for when form is submitted. */
$('body').on('formSubmit2', () => {
    let data = [],
        thisData = [];
    const log = stratics.f.util.log;

    log(3, 'Submit command received.  Collecting data from datafields.');
    for (let x of $('body').data('datafields')) {
        let val = '';
        if (x[2] === 'text') {
            val = x[0].val().replace(/\|/g,'{{!}}');
        } else if (x[2] === 'boolean') {
            if (x[2][0].indeterminate) {
                val = 'unknown';
            } else {
                val = x[0].is(':checked');
            }
        } else if (x[2] === 'selectable') {
            val = [];
            var z = $(x[0]).find('.ui-selected');
            for (let i = 0;i < z.length; i++) {
                val.push( $(z[i]).text());
            }
            val = val.filter(entry => /\S/.test(entry)).join(', ');
        } else {
            stratics.f.util.log(`Encountered stored field of unknown type: ${x[1]}.`);
        }
        thisData.push(`| ${x[1]} = ${val}`);
    }
    log(3, 'Building new markup.');
    thisData = thisData.sort((x, y) => {
        return x.localeCompare(y);
    });
    let newMarkup = thisData.join('\n').replace('unknown', '');
    stratics.wiki.markup2 = `{{Mobile\n${newMarkup}\n${stratics.wiki.loreData}\n${stratics.wiki.excessMarkup}\n}}`;
    log(3, 'New markup has been built and stored.');
    log(4, stratics.wiki.markup2);

    log(3, 'Submitting changed markup.');
    stratics.f.wiki.submitMarkup('Updating the mob\'s info.', stratics.wiki.markup2);
});

/* Handle a successful markup submission. */
$('body').on('submitMarkupSuccess', () => {
    $('#editToolMenuNotification').text('Saved. Reloading the page...');
    stratics.f.util.purgePage();
});

/* Handle a failed markup submission. */
$('body').on('submitMarkupFailure', () => {
    $('#editToolMenuNotification').text('An error has occurred submitting the data. Please try again.');
});

/* Creates an arbitrary element */
const makeElem = (elem, num, args, label, after) => {
    if (typeof args.id === 'undefined') { // Allow for defined element ids
        if (typeof args.type === 'undefined') {
            args.id = elem + num;
        } else {
            args.id = args.type + num;
        }
    }
    if (typeof label === 'undefined') {
        return makehtml(elem, args);
    } else {
        if (typeof after === 'undefined') {
            return makehtml('label', {}, label + nbsp + makehtml(elem, args));
        } else {
            return makehtml('label', {}, makehtml(elem, args) + nbsp + label);
        }
    }
};

/* Creates a fieldset element */
const makeFieldset = (id, name) => $('<fieldset>', {
        id: 'fieldset' + id
    })
    .append(makehtml(legend, {
        style: 'color:#FFF;font-weight:bolder;padding-bottom: 1em;',
        id: `fieldsetLegend${id}`
    }, name));

/* Creates a dl element */
const makeDL = (str) => makehtml('dl', {
    id: `dl${elementCounter}`,
    style: "margin-bottom: 1em"
}, str);

/* Creates a dt element */
const makeDT = (str) => makehtml('dt', {
    id: `dt${elementCounter}`,
    style: "font-weight: 700;margin-left: 2em;color: goldenrod"
}, str);

/* Creates a dd element */
const makeDD = (str) => makehtml('dd', {
    id: `dd${elementCounter}`,
    style: "margin-left: 4em"
}, str);

/* Creates a span element */
const makeSpan = (str) => makehtml(span, {
    id: `span${elementCounter}`
}, str);

/* Creates an input[type=number] element */
const makeNumber = (j, val) => makeElem(input, j, {
    type: number,
    min: 0,
    step: 1,
    style: 'width: 9em;',
    value: val
});

/* Create popup dialog, tab structure, and data fieldsets */
const $editToolMenu = $(makehtml('div', {
        id: 'editToolMenu'
    })),
    $tabsInfo = $('<div>', {
        id: 'tabs2'
    }),
    $tabsList = $('<ul id="tabControlAL2">'),
    makeTab = (i, name) => {
        const $div1 = $('<div style="float:left;width: 100%;" id="container' + i + '">').append(makeFieldset(i, l(name, `fieldsetLegend${i}`))),
            $div2 = $('<div style="float:right;display: flex;">');
        $(`<div id="ALMenu${i}">`).append([$div1, $div2])
            .appendTo($editToolMenu);
        $(makehtml('li', {}, makehtml('a', {
            href: `${loc}#ALMenu${i}`,
            id: `tab${i}`
        }, name))).appendTo($tabsList);
    };

$editToolMenu.append([
    '<div id="editToolMenuLanguageBox" style="position: absolute; bottom: 2px; left: 1em;">',
    '<div id="editToolMenuButtons" style="position: absolute; bottom: 2px; right: 1em;">'
]);

for (let x of ['basicStats', 'skills', 'trivia', 'loot', 'combat', 'credits', 'debug', 'tamer', 'animallore', 'bard', 'petTraining']) {
    tabs[x] = tabcounter++;
}

makeTab(tabs.basicStats, 'Basic Stats');
makeTab(tabs.skills, 'Skills');
makeTab(tabs.trivia, 'Trivia');
makeTab(tabs.loot, 'Loot');
makeTab(tabs.combat, 'Combat');
makeTab(tabs.petTraining, 'Pet Training');
makeTab(tabs.debug, 'Debug');
makeTab(tabs.credits, 'Credits');

$editToolMenu.appendTo('body').prepend($tabsInfo.append($tabsList));

$(`#fieldset${tabs.skills}`).append([
    makeFieldset(tabs.tamer, l('Tamer', `fieldsetLegend${tabs.tamer}`)),
    makeFieldset(tabs.animallore, l('Animal Lore', `fieldsetLegend${tabs.animallore}`)),
    makeFieldset(tabs.bard, l('Bard', `fieldsetLegend${tabs.bard}`))
]);

const addNumberInput = (field, name, skipBreak, inline = false) => {
        let $div = $(`<div style="display: ${inline ? "inline-" : ""}block;margin-top: 4px;" id="inputNumber${name}">`),
            num = elementCounter;
        $(`#fieldset${field}`).append($div.append([
            $(makeNumber(num, "")).prop("name", name + 1),
            stratics.f.util.makehtml(label, {
                id: `label${elementCounter}`,
                for: `number${num}`,
                style: "padding-left:0.5em;"
            }, l(name, `label${elementCounter++}`)),
            skipBreak ? '' : br
        ]));
        return `number${num}`;
    },
    addNumberInputs = (i, arr) => {
        let ids = [];
        for (var item of arr) {
            ids.push(addNumberInput(i, item));
        }
        return ids;
    },
    addRangeInput = (field, name) => {
        let id = addNumberInput(field, name);
        $(`#${id}`).after([
            " - ",
            $(makeNumber(elementCounter, "")).prop("name", name + 2)
        ]);
        return [id, `number${elementCounter++}`];
    },
    addButtonInput = (field, name, text, disabled) => {
        $(`#${field}`).append($(makehtml(input, {
                type: 'button',
                value: text,
                id: `editToolMenu${name}2`,
            })).text(l(text, `editToolMenu${name}2`))
            .button()
            .button(disabled ? 'disable' : 'enable'));
    },
    addBreak = (field) => {
        $(`#fieldset${field}`).append([br, br]);
    },
    addCheckboxInput = (field, name, text, checked) => {
        let $div = $('<div style="display: flex;margin-top: 4px;">');
        let checkbox = $(makehtml(input, {
            type: 'checkbox',
            id: `editToolMenu${name}`
        }));
        if (checked) {
            checkbox.prop('checked', true);
        }
        $(`#fieldset${field}`).append($div.append([checkbox,
            makehtml(label, {
                style: 'color:#FFF; padding-left: 0.4em; padding-right: 1em;',
                for: `editToolMenu${name}`,
                id: `inputLabel${elementCounter}`
            }, l(text, `inputLabel${elementCounter++}`)),
        ]));
    },
    addTextInput = (field, text, placeholder = '', flex = 1) => {
        let id = `editToolMenu${elementCounter}`;
        let $div = $(`<div style="${(flex) ? 'display: flex;' : ''}margin-top: 3px; margin-bottom: 1.4em;">`).append([
            makehtml(label, {
                for: id,
                id: `inputLabel${elementCounter}`,
                style: `margin-left:0.5em; min-height: 28px;`,
            }, l(text, `inputLabel${elementCounter}`)),
            makehtml(input, {
                id: id,
                placeholder: l(placeholder, `editToolMenu${elementCounter++}`),
                style: 'flex: 1 1 auto; margin-left:0.5em; min-height: 28px;',
                type: 'text'
            })
        ]);
        $(`#fieldset${field}`).append($div);
        return id;
    },
    addTextareaInput = (field, text, height = 8) => {
        let id = `editToolMenu${elementCounter}`;
        let $div = $('<div style="display: flex; margin-top: 3px; margin-bottom: 1.4em;">').append([
            makehtml(label, {
                for: id,
                id: `textareaLabel${elementCounter}`,
                style: 'flex: 0 1 auto; color:#FFF; padding-top: 5px;'
            }, l(text, `textareaLabel${elementCounter++}`)),
            makehtml('textarea', {
                id: id,
                style: `flex: 1 1 auto; margin-left:0.5em; min-height: 28px;height:${height}em;`
            })
        ]);
        $(`#fieldset${field}`).append($div);
        return id;
    },
    addSelectLabel = (field, text) => {
        $(`#fieldset${field}`).append(makehtml(label, {
            'class': 'selectLabel',
            id: `inputLabel${elementCounter}`
        }, l(text, `inputLabel${elementCounter}`), `inputLabel${elementCounter++}`));
    },
    addSelect = (field, name, options) => {
        let id = `editToolMenu${elementCounter}`;
        const liArr = [],
            $ul = $(`<ul class="selectable" id="${id}">`);
        addSelectLabel(field, name);
        for (i = 0; options.length > i; i++) {
            liArr.push(`<li id="option${elementCounter}" class="selectLI" value="${options[i]}">${l(options[i], `select${elementCounter++}`)}</li>`);
        }
        $ul.append(liArr);
        $(`#fieldset${field}`).append($ul);
        $ul.selectable();
        return id;
    };

/* Populate tabs */
fields.name = addTextInput(tabs.basicStats, 'What is the name of this mob?');
fields.alignment = addSelect(tabs.basicStats, 'alignment', ['Good (blue)', 'Neutral (grey)', 'Evil (red)', 'Invulnerable (yellow)','unknown']);
$(`#fieldset${tabs.basicStats}`).append([br, br]);
fields.poisonLevel = addNumberInput(tabs.basicStats, 'Poison Level', true);
$(`#${fields.poisonLevel}`).parent().append('<span> (</span>', makeSpan(l('0 = none, 1 = lesser, 2 = standard, 3 = greater, 4 = deadly, 5 = lethal', `span${++elementCounter}`)), '<span>)</span>');
fields.loyalty = addNumberInput(tabs.basicStats, 'Loyalty Points');
fields.fame = addNumberInput(tabs.basicStats, 'Fame', false, true);
fields.karma = addNumberInput(tabs.basicStats, 'Karma', false, true);
fields.bodytype = addTextInput(tabs.basicStats, 'Bodytype #(s)', '', 0);
fields.hues = addTextInput(tabs.basicStats, 'Hue #(s)', '', 0);
$(`#${fields.bodytype}`).parent().append(makehtml('a', {
    href: "/w/index.php?title=UO:Mobile Bodytype List",
    style: "font-style: italic;padding-left: 8px;",
    target: '_blank'
}, l('Reference list of mobile bodytypes')));
$('#inputNumberFame').after('<div id="divCalculatorFame" style="display:inline-block;padding-left: 1rem;">');
$('#inputNumberKarma').after('<div id="divCalculatorKarma" style="display:inline-block;padding-left: 1rem;">');
$('#divCalculatorFame').after('<br>');
$('#divCalculatorKarma').after('<br>');
addButtonInput('divCalculatorFame', 'CalculatorFame', 'Open calculator', false);
addButtonInput('divCalculatorKarma', 'CalculatorKarma', 'Open calculator', false);

fields.generalInfo = addTextareaInput(tabs.basicStats, 'Write any text with general info for this mob.', 13);

addCheckboxInput(tabs.tamer, 'Tameable', 'Check this box if this mob can be tamed.');
addCheckboxInput(tabs.tamer, 'Subdue', 'Check this box if this mob must be subdued before it can be tamed.');
fields.minTame = addNumberInput(tabs.tamer, 'Minimum required taming skill to tame this mob');
fields.minMount = addNumberInput(tabs.tamer, 'Minimum required taming skill to mount this mob, if it can be mounted.');
fields.tameNotes = addTextareaInput(tabs.tamer, 'Write any text with notes about taming this mob.', 9);

addCheckboxInput(tabs.animallore, 'Loreable', 'This type of mobile cannot be lored.');
$(`#fieldset${tabs.animallore}`).append(br);

fields.bardNotes = addTextareaInput(tabs.bard, 'Write any text with notes about barding this mob.', 9);

fields.added = addTextInput(tabs.trivia, 'When was this mobile added to the game?', 'YYYY-MM-DD: Patch #/Publish #/Release');
addCheckboxInput(tabs.trivia, 'Spawning', 'Check this box if this mob is currently spawning.');
addCheckboxInput(tabs.trivia, 'Paragon', 'Check this box if there is no way for this mob to spawn as a paragon.');
fields.spawn = addTextInput(tabs.trivia, 'Where does it spawn?');
addCheckboxInput(tabs.trivia, 'Summonable', 'Check this box if this mob can be summoned in any way. This includes ethereal statues, spells, talismans, etc.');
fields.summonItem = addTextInput(tabs.trivia, 'What item or spell is used?');
addCheckboxInput(tabs.trivia, 'Polymorphable', 'Check this box if a player can take on the appearance of this mob. This includes polymorph spells, costumes, or any other means.');
fields.polyItem = addTextInput(tabs.trivia, 'What item or spell is used?');
addCheckboxInput(tabs.trivia, 'Statue', 'Check this box if a statue of this mob exists. This includes ethereals, veteran rewards, etc. (Do not include rare items.)');
fields.statue = addTextInput(tabs.trivia, 'What item(s)?');
fields.quest = addTextInput(tabs.trivia, 'If this mob only appears during a quest, what is the name of that quest?');
fields.trivia = addTextareaInput(tabs.trivia, 'Write any random trivia for this mob.');

fields.gold = addTextInput(tabs.loot, 'Roughly how much gold does this mob drop?', 'example: 100 - 200');
fields.commonItems = addTextInput(tabs.loot, 'What common items does this mob drop?', 'example: magery reagents, robe, magery scroll');
fields.specialItems = addTextareaInput(tabs.loot, 'What special items does this mob have a chance to drop?', 'example: daemon bone armor', 6);
$(`#${fields.specialItems}`).parent().parent().append(makeFieldset('Carving', l('How many of each item are given by carving this mobile\'s corpse (by an elf or gargoyle)?', `fieldsetLegendCarving`)));
$('#fieldsetCarving').append('<div id="fieldsetCarvingFields" style="-moz-column-count: 3;-moz-column-gap: 20px;-webkit-column-count: 3;-webkit-column-gap: 20px;column-count: 3;column-gap: 20px;padding-bottom: 1em;"></div>');
fields.pileHidesN = addNumberInput('CarvingFields', 'Pile of Hides');
fields.pileHidesS = addNumberInput('CarvingFields', 'Pile of Spined Hides');
fields.pileHidesH = addNumberInput('CarvingFields', 'Pile of Horned Hides');
fields.pileHidesB = addNumberInput('CarvingFields', 'Pile of Barbed Hides');
$('#fieldsetCarvingFields').append('<div>&nbsp;</div><div>&nbsp;</div><div>&nbsp;</div>');
fields.rawRibs = addNumberInput('CarvingFields', 'Cut Of Raw Ribs');
fields.rawLamb = addNumberInput('CarvingFields', 'Raw Leg of Lamb');
fields.rawChicken = addNumberInput('CarvingFields', 'Raw Chicken Leg');
fields.rawBird = addNumberInput('CarvingFields', 'Raw Bird');
fields.feathers = addNumberInput('CarvingFields', 'Feathers');
fields.dragonBlood = addNumberInput('CarvingFields', 'Dragon\'s Blood');
fields.dragonScaleBlack = addNumberInput('CarvingFields', 'Black Dragon Scales');
fields.dragonScaleBlue = addNumberInput('CarvingFields', 'Blue Dragon Scales');
fields.dragonScaleGreen = addNumberInput('CarvingFields', 'Green Dragon Scales');
fields.dragonScaleRed = addNumberInput('CarvingFields', 'Red Dragon Scales');
fields.dragonScaleWhite = addNumberInput('CarvingFields', 'White Dragon Scales');
fields.dragonScaleYellow = addNumberInput('CarvingFields', 'Yellow Dragon Scales');
fields.carvedOther = addTextInput('Carving', 'Other items from carving:');
fields.stealing = addTextInput('Carving', 'What items can be obtained when stealing from this mob?', 'example: none');

fields.classification = addSelect(tabs.petTraining, 'Classification / Restrictions', stratics.data.AbilityClassifications);
addBreak(tabs.petTraining);
fields.trainableMagicalAbility = addSelect(tabs.petTraining, 'Magical Abilities', stratics.data.MagicalAbilities);
addBreak(tabs.petTraining);
fields.trainableSpecialAbility = addSelect(tabs.petTraining, 'Special Abilities', stratics.data.SpecialAbilities);
addBreak(tabs.petTraining);
fields.trainableSpecialMove = addSelect(tabs.petTraining, 'Special Moves', stratics.data.SpecialMoves);
addBreak(tabs.petTraining);
fields.trainableAreaEffect = addSelect(tabs.petTraining, 'Area Effect Abilities', stratics.data.AreaEffectAbilities);
$('#fieldset30').find('ul').each(function () {
    $(this).wrap('<div style="display:inline-block;padding-left:2em;">');
});
/* Allow multiple selection without holding down CTRL */
$('#fieldset30').find(".ui-selectee").bind("mousedown", function(e) {
    e.metaKey = true;
    $(this).toggleClass("ui-selected");
    return false;
})    ;


fields.speed = addSelect(tabs.combat, 'speed', ['Slow', 'Medium', 'Fast', 'Paragon', 'unknown']);
addBreak(tabs.combat);
fields.ranged = addTextInput(tabs.combat, 'If this mob can use ranged attacks, what types of attacks?', 'example: Arrows, Fireball, Energy Bolt');
addCheckboxInput(tabs.combat, 'Autodispel', 'Check this box if this mob can auto-dispel summoned creatures.');
fields.slayer = addTextInput(tabs.combat, 'What slayers is this mob vulnerable to?');
fields.combatNotes = addTextareaInput(tabs.combat, 'Write any text with advice for fighting this mob.', 15);

$(`#editToolMenu`).append(makehtml('div', {
    id: `div${elementCounter}`,
    style: 'position:absolute; bottom: 3.7em;'
}, l('For all questions, separate multiple items with commas. Please use only English item or spell names.', `div${elementCounter++}`)));

$(`#fieldset${tabs.debug}`).append('<div id="debugConsole3"></div><br/><textarea style="width:825px;height:300px;" wrap="off" id="debugConsole4"></textarea>');

$(`#tab${tabs.debug}`).parent().hide(); // Debug tab

/* Populate Credits tab */
let translations = [];
for (let k of stratics.f.util.getLangList()) {
    if (k[0] != 'en') {
        translations.push(makeDL([
            makeDT(makeSpan(l('Translation', `span${++elementCounter}`)) + makeSpan(` (${stratics.i18n[k[0]]["{language name}"]})`)),
            makeDD(stratics.i18n[k[0]]["{translators}"])
        ].join('')));
    }
}
$(`#fieldset${tabs.credits}`).append(makeDL([
    makeDT(l('Programming', `dt${++elementCounter}`)),
    makeDD('BrianFreud')
].join('')) + translations.join(''));

/* Add fame and karma calculator dialogs */
$('body').append(`<div id="dialogFameCalc" title="Fame Calculator" style="display:none">
    <p>To calculate the amount of fame for a mobile, you will need the 'fame' listed on your Loyalty Rating gump both before and after you kill one of those mobiles.</p>
    <br>
    <table>
        <tr>
            <td>
                <label id="labelFameBefore" for="numberFameBefore" style="padding-right:0.5rem;">Before:</label>
            </td>
            <td>
                <input type="number" min="0" step="1" style="width: 9rem;" value="" id="numberFameBefore" name="Fame Before">
            </td>
        </tr>
        <tr>
            <td>
                <label id="labelFameBefore" for="numberFameAfter" style="padding-right:0.5rem;">After:</label>
            </td>
            <td>
                <input type="number" min="0" step="1" style="width: 9em;" value="" id="numberFameAfter" name="Fame After">
            </td>
        </tr>
        <tr>
            <td style="padding-right:0.5rem;">
                In Felucca?
            </td>
            <td>
                <input type="checkbox" id="editToolFameCalcFel">
            </td>
        </tr>
    </table>
    <input type="button" value="Calculate" id="editToolMenuFameCalculate" class="ui-button ui-widget ui-state-default ui-corner-all" role="button" aria-disabled="false">
</div>
<div id="dialogKarmaCalc" title="Karma Calculator" style="display:none">
    <p>To calculate the amount of Karma for a mobile, you will need the 'Karma' listed on your Loyalty Rating gump both before and after you kill one of those mobiles.</p>
    <br>
    <table>
        <tr>
            <td>
                <label id="labelKarmaBefore" for="numberKarmaBefore" style="padding-right:0.5rem;">Before:</label>
            </td>
            <td>
                <input type="number" min="0" step="1" style="width: 9rem;" value="" id="numberKarmaBefore" name="Karma Before">
            </td>
        </tr>
        <tr>
            <td>
                <label id="labelKarmaBefore" for="numberKarmaAfter" style="padding-right:0.5rem;">After:</label>
            </td>
            <td>
                <input type="number" min="0" step="1" style="width: 9em;" value="" id="numberKarmaAfter" name="Karma After">
            </td>
        </tr>
        <tr>
            <td style="padding-right:0.5rem;">
                In Felucca?
            </td>
            <td>
                <input type="checkbox" id="editToolKarmaCalcFel">
            </td>
        </tr>
    </table>
    <input type="button" value="Calculate" id="editToolMenuKarmaCalculate" class="ui-button ui-widget ui-state-default ui-corner-all" role="button" aria-disabled="false">
</div>`);

/* Create button controls */
$('#editToolMenuButtons').append('<span id="editToolMenuNotification">');
addButtonInput('editToolMenuButtons', 'Edit', 'Edit the raw wiki markup', false);
addButtonInput('editToolMenuButtons', 'Submit', 'Submit', true);
addButtonInput('editToolMenuButtons', 'Cancel', 'Cancel', false);

/* Create language selector */
$('#editToolMenuLanguageBox').append('<select id="editToolMenuLangSelect" style="float: right;margin-bottom: 1em;border-radius: 10px;">');
let $options = [];
for (let k of stratics.f.util.getLangList()) {
    let selected = (k[0] == stratics.i18n.current) ? 'selected' : '';
    $options.push(`<option value="${k[0]}" ${selected}>${k[1]}</option>`);
}
$('#editToolMenuLangSelect').append($options).selectmenu({
    style: 'dropdown'
});

/* Handle a changed language selection */
$('#editToolMenuLangSelect').on('change', function() {
    stratics.f.util.setLang($(this).val());
});

/* Handle notification of a changed language selection */
$('body').on('languageChanged', () => {
    lupdate();
});

/* Handle a click on the raw mode edit button. */
$('#editToolMenuEdit2').click(function() {
    window.location.search = `${window.location.search}&action=edit`;
});

/* Handle a click on the cancel button. */
$('#editToolMenuCancel2').click(function() {
    $editToolMenu.dialog('close');
});

/* Handle a click on the submit button. */
$('#editToolMenuSubmit2').click(function() {
    $('#editToolMenuNotification').text('Thanks! Saving...');
    $('body').trigger('formSubmit2');
});

/* Handle a click on the fame calculator button. */
$('#editToolMenuCalculatorFame2').click(function() {
    $( function() {
        $( "#dialogFameCalc" ).dialog();
    });    
});

/* Handle a click on the karma calculator button. */
$('#editToolMenuCalculatorKarma2').click(function() {
    $( function() {
        $( "#dialogKarmaCalc" ).dialog();
    });
});

/* Handle a click on the karma dialog calculate button. */
$('#editToolMenuKarmaCalculate').click(function() {
    $( function() {
        let before = parseInt($('#numberKarmaBefore').val(),10);
        let after = parseInt($('#numberKarmaAfter').val(),10);
        let fel = $('#editToolKarmaCalcFel').prop('checked');
        let karma = Math.round(-((100*(after-before))+before)/(fel ? 1.3 : 1)/100)*100;
        $('#inputNumberKarma').find('input').val(karma);
    });
});

/* Handle a click on the fame dialog calculate button. */
$('#editToolMenuFameCalculate').click(function() {
    $( function() {
        let before = parseInt($('#numberFameBefore').val(),10);
        let after = parseInt($('#numberFameAfter').val(),10);
        let fel = $('#editToolFameCalcFel').prop('checked');
        let fame = Math.round(((100*(after-before))+before)/(fel ? 1.3 : 1)/100)*100;
        $('#inputNumberFame').find('input').val(fame);
    });
});

/* Allow the debug menu to be seen. */
$(document).bind('keydown', function(event) {
    if (event.which === 68 && event.altKey) {
        $(`#tab${tabs.debug}`).parent().toggle();
    }
});

/* Start fetching the page's markup. ASYNCH */
stratics.f.wiki.getMarkup();

/* Initialize popup dialog */
stratics.f.util.log(3, 'Initializing dialog.');
$editToolMenu.tabs()
    .dialog({
        title: l('Mob Editor', 'ui-id-1'),
        autoOpen: false,
        position: {
            my: 'center',
            at: 'center-125',
            of: window
        },
        modal: true,
        width: 1000,
        height: 800,
        close: function() {
            $('#PropBox')
                .dialog('close');
        },
        open: function() {
            if ($('#PropBox')
                .length > 0 && !$('#PropBox')
                .dialog("isOpen")) {
                $('#PropBox')
                    .dialog('open');
            }
        }
    });

/* Create Trigger link. */
stratics.f.util.log(3, 'Creating trigger link.');
$('a[accesskey="e"]').click(e => {
    e.preventDefault();
    $('#editToolMenu').dialog('open');
});

/* Store tabs' text */
$('#tabControlAL2 li').each(function() {
    let a = $(this).find('a');
    a.data('value', a.text());
});
$('body').trigger('languageChanged');

/* Open the editor if 'edit' was already clicked before the script initialized. */
if (!!$('body').data('editClicked')) {
    $('a[title="Edit this page [alt-shift-e]"]').trigger('click');
}

/* Set up the animal training tab with paired classification / abilities */
const setOptionPair = function(first, second, posneg = 1) {
    if (second.length) {
        second = `[value="${second}"]`;
    }
    const $paired = $('#fieldset30')
                .find('ul:not(:first)')
                .find(`.ui-selectee${second}`),
 setInvalid = function() {
        $paired.hide()
            .removeClass('ui-selected');
    };
    let $first = $('#fieldset30')
        .find('ul:first')
        .find(`.ui-selectee[value="${first}"]`)
        .click(function() {
            if ($(this)
                .hasClass('ui-selected') == posneg) {
                $paired.show();
            } else {
                setInvalid();
            }
        });
    if ($first.hasClass('ui-selected') != posneg) {
        setInvalid();
    }
};
setOptionPair('Clawed', 'Grasping Claw');
setOptionPair('Insectoid', 'Rune Corruption');
setOptionPair('Magical', 'Dragon Breath');
setOptionPair('Necromantic', 'Life Leech');
setOptionPair('Sticky Skin', 'Sticky Skin');
setOptionPair('Tailed', 'Tail Swipe');
setOptionPair('Tokuno-Native', 'Bushido');
setOptionPair('Tokuno-Native', 'Ninjitsu');
setOptionPair('Untrainable', '', 0);
}());