Mob Editor.js

Revision as of 21:48, 22 June 2017 by BrianFreud (talk | contribs)

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 */
"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
        ['', 'carved'], // Deprecated parameter
        ['', 'harvester'], // Deprecated parameter
        ['added', 'firstSeen'],
        ['alignment', 'alignment'],
        ['bardNotes', 'bardingNotes'],
        ['bodytype', 'bodytype'],
        ['carved', 'loot_carved'],
        ['combatNotes', 'combatStrategy'],
        ['commonItems', 'loot_base'],
        ['fame', 'fame'],
        ['generalInfo', 'body_text'],
        ['gold', 'loot_gold'],
        ['hues', 'hues'],
        ['harvester', 'loot_harvester'],
        ['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']
    ];
$('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 (str === 'alignment' || str === 'speed') {
                        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 (x[0] === 'alignment' || x[0] === 'speed') {
                    $(`#${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']) {
            $(`#${fields[x]}`).find(`[value="${stratics.data[x]}"]`).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 = x[0].find('.ui-selected').text();
        } 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...');
    location.href = `${location.href}&action=purge`;
});

/* 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 an arbitrary 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']) {
    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.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) => {
        let $div = $('<div style="display: block;margin-top: 4px;">'),
            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);
    },
    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;
    },
    addSelect = (field, name, options) => {
        let id = `editToolMenu${elementCounter}`;
        const liArr = [],
            $ul = $(`<ul class="selectable" id="${id}">`);
        for (i = 0; options.length > i; i++) {
            liArr.push(`<li id="option${elementCounter}" style="cursor:pointer;" 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?');
$(`#fieldset${tabs.basicStats}`).append(makehtml(label, {
    style: 'color:#FFF;padding-left: 0.4em;padding-right: 1em;',
    id: `inputLabel${elementCounter}`
}, l('Alignment:', `inputLabel${elementCounter}`), `inputLabel${elementCounter++}`));
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');
fields.karma = addNumberInput(tabs.basicStats, 'Karma');
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')));
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>');
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');

$(`#fieldset${tabs.combat}`).append(makehtml(label, {
    style: 'color:#FFF;padding-left: 0.4em;padding-right: 1em;',
    id: `inputLabel${elementCounter}`
}, l('Speed:', `inputLabel${elementCounter}`), `inputLabel${elementCounter++}`));
fields.speed = addSelect(tabs.combat, 'speed', ['Slow', 'Medium', 'Fast', 'Paragon', 'unknown']);
addBreak(tabs.combat);
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: 6em;'
}, 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(''));

/* 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');
});

/* 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');
}