Greasy Fork is available in English.

Dressing Room

WoWhead Dressing Room

// ==UserScript==
// @name         Dressing Room
// @version      3.1
// @description  WoWhead Dressing Room
// @namespace    http://mogboutique.tumblr.com/
// @author       http://mogboutique.tumblr.com/
// @match        https://*.wowhead.com/dressing-room*
// @match        https://*.wowhead.com/beta/dressing-room*
// @grant        GM_setValue
// ==/UserScript==


var HTML = '<style type="text/css" id="wmv_style">.dressing-room-controls-block.block-bg{display:none;}\
.dr_code_buttons>button{margin:4px 4px;background:#a71a19;padding:2px;font-size:10px;width:40px;.dr_link{background:transparent;}\
#record_video[active="true"]{background-color:red;}\
#dr_script_arrow_button:before{content: "\f078";}\
</style>\
<div id="dressing_room_script" data-open="false" style="border-top:1px dashed #ffd100;margin-top:10px;">\
<span id="dr_script_header" style="cursor:pointer;border-bottom:1px dashed;color:#ffd100;">Dressing Room Script</span>\
<div id="dr_script_arrow_button" style="cursor:pointer;color:#ffd100;"></div>\
<div id="dressing_room_wrapper" style="display:none;">\
<button id="comp_click" class="" \
style="border:1px solid #ffd100;color:#ffd100;padding-left:5px;padding-right:5px;font-size:14px;margin-top:5px;background-color:transparent;">\
Import MogIt or WoWHead Comparison link</button>\
<div id="dr_itemlist" style="margin-top:5px;/*background:black*/;display:flex;flex-direction:row;" > \
<div id="dr-preview" style="padding:10px;font-size:10px;margin-top:5px;margin-bottom:5px;border:1px solid #ffd100;display:block;width:90%;height:178px;" >\
</div>\
<textarea id="dr_code_box" style="padding:10px;margin-top:5px;margin-bottom:5px;display:none;border:1px solid;font-size:10px;\
height:200px;width:90%;background:transparent;color:#ffd100;border-radius:0px;"></textarea>\
<div class="dr_code_buttons" style="text-align:center;display:flex;order:-1;flex-direction:column;vertical-align:top;">\
<button mode="preview" title="Preview">👁️</button>\
<button mode="html" title="Create HTML Item List">HTML</button><button mode="reddit" title="Create Reddit Markup Item List">Reddit</button>\
<button mode="forum" title="Create BBCode Item List">Forum</button><button mode="plain" title="Create Plain Text Item List">Plain</button>\
<button id="code_wmv" mode="wmv" title="Show WoW Model Viewer Code">WMV</button></div></div>\
<div id="wmv_pad" style="margin-top:5px;margin-bottom:5px;display:flex;flex-direction:row;justify-content: space-around;" >\
    <button id="generate_chr" class="btn btn-site" href="javascript:void(0);"\
    title="Save as an WoW Model Viewer&#013;.CHR (Character) file.&#013;Use Character -> Load Character in WMV"\
    style="text-align:center;width:48%;">\
    WMV .chr file</button>\
    <button class="btn btn-site" id="generate_eq" href="javascript:void(0);"\
    title="Save as an WoW Model Viewer&#013;.EQ (equipment-only) file.&#013;Use Character -> Load Equipment in WMV"\
    style="text-align:center;width:48%;">\
    WMV .eq file</button>\
<a class="fa canvas_screen dr_link" title="test&#013;test" href="javascript:void(0);" style="text-align:center;display:none;border:0px solid;font-size:10px;padding:0px;">\
Save Screenshot</a>\
<div id="wmv_hide" style="display:none;"><a id="download_file" download="MyCharacter.chr" style="display:none;" href="javascript:void:(0);"></a>\
<a id="code_file" style="display:none;" target="File Code" href="javascript:void:(0);"></a>\
<a href="#" class="button" id="btn-download" download="my-file-name.png">Download</a>\
</div></div>\
<div id="video_recorder" style="border-top:1px dashed #ffd100;padding-top:5px;">\
    <button id="record_video" style="border-radius:4px;background-color:#a71a19;color:white;">🎦 Record video</button>\
    <a href="" id="download_video_link" download="movie.mkv" style="margin-left:10px;font-size:10px;">Download movie</a>\
<div>\
</div></div>';

var DR = {};

var $ = unsafeWindow.$;

document.addEventListener('click', pageClick, false);

function pageClick(e) {
    if (e.target.className === 'appearance-button') {
        if (e.target.dataset.itemBonus && e.target.dataset.itemId) {
            var bonus = e.target.dataset.itemBonus;
            DR.setItem({ id: e.target.dataset.itemId, bonus: bonus });
        }
    }
    else if (e.target.id === 'dr_script_header') {
        $('#dressing_room_wrapper').slideToggle('slow');
    }
}

DR.mode = "html";

DR.urlInput;

DR.equipmentOrderToCharacterSlot = {
    1: 1,
    2: 3,
    3: 15,
    4: 5,
    5: 4,
    6: 19,
    7: 9,
    8: 10,
    9: 6,
    10: 7,
    11: 8,
    12: 16,
    13: 17
};
DR.characterSlotToEquipmentOrder = {
    //Head
    1: 1,
    //Shoulders
    3: 2,
    //Cloak
    16: 3,
    //Chest
    5: 4,
    //Shirt
    4: 5,
    //Tabard
    19: 6,
    //Wrists
    9: 7,
    //Hands
    10: 8,
    //Waist
    6: 9,
    //Legs
    7: 10,
    //Feet
    8: 11,
    //Ranged
    15: 12,
    //1-Hand
    13: 12,
    //2-Hand
    17: 12,
    //Shield
    14: 13,
    //Off-Hand
    23: 13
};

DR.characterSlotToSlotName = {
    1: "head",
    3: "shoulder",
    15: "back",
    5: "chest",
    4: "shirt",
    19: "tabard",
    9: "wrist",
    10: "hands",
    6: "waist",
    7: "legs",
    8: "feet",
    16: "mainhand",
    17: "offhand"
};

DR.equipmentOrderToWMVSlot = {
    //Head
    1: [0, 'Head'],
    //Shoulders
    2: [1, 'Shoulders'],
    //Cloak
    3: [11, 'Cloak'],
    //Chest
    4: [6, 'Chest'],
    //Shirt
    5: [4, 'Shirt'],
    //Tabard
    6: [12, 'Tabard'],
    //Wrists
    7: [7, 'Wrists'],
    //Hands
    8: [8, 'Hands'],
    //Waist
    9: [3, 'Waist'],
    //Legs
    10: [5, 'Legs'],
    //Feet
    11: [2, 'Feet'],
    //Ranged
    12: [9, 'Main Hand'],
    //Shield
    13: [10, 'Off-Hand'],
}

DR.init = function () {
    //console.log('WH.Wow.DressingRoom', WH.Wow.DressingRoom, 'Controls', $('div.dressing-room-controls')[0]);

    const switchers = $('.dressing-room-character-controls-category-switchers');

    /*<a data-category="gear" href="javascript:" style="background-image: url(&quot;https://wow.zamimg.com/images/wow/icons/large/inv_chest_leather_09.jpg&quot;);" class=""></a>*/
    //achievement_boss_grandmagustelestra petbattle_health inv_misc_toy_07
    const a = $(`<a data-category="script" href="javascript:" 
    style="order:90;background-image: url('https://wow.zamimg.com/images/wow/icons/large/inv_misc_toy_07.jpg');
    background-position:center center;background-size:contain;"
    class=""></a>`);
    switchers.append(a);

    //console.log('TEST**************************************', $('.dressing-room-character-controls-category').last());

    /*<div class="dressing-room-character-controls-category-option" data-character-customization-type="tattoo">Tessssssssssst</div>*/
    const opt = $(`<div class="dressing-room-character-controls-category" data-active="false" data-category="script" style="pointer-events:auto;"></div>`);
    $('.dressing-room-character-controls-category').last().after(opt);

    $('head').append(`<style>
    .dressing-room-character-controls-category #dr_script_header {
        font-size: 14px;
        font-weight: bold;
        margin: 0 0 10px;
        text-transform: uppercase;
    }
    #dressing_room_script{
        border:0 !important;
        margin-top:0 !important;
    }
    #dr_itemlist {
        flex-direction:column !important;
    }
    .dr_code_buttons {
        flex-direction:row !important;
    }
    </style>`);

    new MutationObserver(me => {
        for (let m of me) {
            if (m.target && m.target.tagName === "A" && m.target.dataset.category !== "script") {
                //console.log(m.target.dataset.category, m.target.dataset.active);
                if (m.target.dataset.active === "true") {
                    opt[0].dataset.active = false;
                    a[0].dataset.active = false;
                }
            }
        }
    }).observe($('.dressing-room-character-controls-category-switchers')[0], {
        attributes: true, childList: false, subtree: true,
        attributeFilter: ['data-active'],
    });

    $('.dressing-room-character-controls-category-switchers').on('click', e => {
        const t = e.target;
        if (e.target.tagName === "A") {
            if (t.dataset.category === "script") {
                $('.dressing-room-character-controls-category-switchers > a').each(function (index) {
                    this.dataset.active = false;
                });
                $(".dressing-room-character-controls-category").each(function (index) {
                    this.dataset.active = false;
                });
                $(".dressing-room-character")[0].dataset.category = "script";
                opt[0].dataset.active = true;
                a[0].dataset.active = true;
            }
            /*else {
                opt[0].dataset.active = false;
                a[0].dataset.active = false;
            }*/
            //console.log("CATEGORY SWITCH",t.dataset.category);
        }
    });

    if (!WH.Wow.DressingRoom) {
        console.log('No Wowhead dressing room.');
        return;
    }
    //$('div.dressing-room-character-controls-general').after('<div id="Dressing_Room_Script_Controls" style="max-width:315px;"></div>');
    $('body').append('<div id="Dressing_Room_Script_Controls" style="background:#202020;position:fixed;z-index:100000;right:0;bottom:0;min-width:400px;padding:20px;"></div>');
    //$('div.dressing-room-controls').append(HTML);
    //$('#Dressing_Room_Script_Controls').append(HTML);
    opt.append(HTML);
    $('#dressing_room_wrapper').show();
    //console.log('Character', WH.Wow.DressingRoom.getCharacterForHash());

    DR.urlInput = $('#comp_src');
    DR.urlInput.change(DR.onUrlInput);
    $('#comp_click').click(DR.onImportClick);
    $('.dr_code_buttons button').on('click', function (e) {
        //console.log("CLICKED");
        var mode = $(e.target).attr('mode');
        DR.createListCode(mode);
        DR.mode = mode;
        if (mode === "preview") {
            $('#dr_code_box').hide();
            $('#dr-preview').show();
        }
        else {
            $('#dr_code_box').show();
            $('#dr-preview').hide();
        }

    });
    $('#generate_chr').on('click', function (e) {
        DR.createWMVCode();
        $("#download_file")[0].click();
    });
    $('#generate_eq').on('click', function (e) {
        DR.createWMVCode('eq');
        $("#download_file")[0].click();
    });

    $('#record_video').on('click', function (e) {
        $(this).attr('active', true);
        if (!DR.recorder)
            DR.initRecorder();

        if (DR.recorder.state == 'inactive')
            DR.startRecording();
        else if (DR.recorder.state == 'recording')
            DR.stopRecording();
    });

    var interval = setInterval(function () {
        var paperdoll = g_paperdolls['dressing-room-paperdoll'];
        if (paperdoll) {
            clearInterval(interval);
            DR.hookUpdateViewer(paperdoll);
            DR.hookUpdateViewer(Paperdoll.prototype);
            //DR.onUpdateDressingRoom();
        }
    }, 100);


}

DR.hookUpdateViewer = function (p) {
    if (!p._updateSlots) {
        p._updateSlots = p.updateSlots;
        p.updateSlots = function (b) {
            this._updateSlots(b);
            try {
                //console.log('PAPER DOLL UPDATING SLOTS');
                DR.onUpdateDressingRoom();
            }
            catch (e) {
                console.log("COULD NOT INIT UPDATE SLOTS EVENT, ERROR ", e);
            }
        }
    }
    if (!p._updateCharAppearance) {
        p._updateCharAppearance = p.updateCharAppearance;
        p.updateCharAppearance = function (a) {
            this._updateCharAppearance(a);
            try {
                console.log('PAPER DOLL UPDATING CHAR APPEARANCE');
                DR.onUpdateDressingRoom();
            }
            catch (e) {
                console.log("COULD NOT INIT UPDATE CHAR APPEARANCE EVENT, ERROR ", e);
            }
        }
    }
    if (!p._saveData) {
        p._saveData = p.saveData;
        p.saveData = function (a) {
            var data = this._saveData(a);
            try {
                console.log('PAPER DOLL SAVING DATA');
                DR.onUpdateDressingRoom();
            }
            catch (e) {
                console.log("COULD NOT INIT SAVE PAPERDOLL DATA EVENT, ERROR ", e);
            }
            return data;
        }
    }
}

DR.recorder = null;

DR.recorderActive = false;

DR.onUpdateDressingRoom = function () {
    DR.createListCode(DR.mode);
    DR.multiAppearances();
}

DR.initRecorder = function () {

    var stream = $('.paperdoll-model-inner canvas')[0].captureStream();
    DR.recorder = new MediaRecorder(stream);

    DR.recorder.chunks = [];
    DR.recorder.totalsize = 0;

    DR.recorder.ondataavailable = function (event) {
        var audioURL = window.URL.createObjectURL(event.data);

        this.chunks.push(event.data);
        DR.recorder.totalsize = DR.recorder.totalsize + event.data.size;
        var totalSize = DR.recorder.totalsize;
        for (var i in this.chunks) {
            totalSize = totalSize + this.chunks[i].size;
        }
        if (totalSize / Math.pow(1024, 2) > 10) {
            console.log("More than 10MB of data");
            this.stop();
        }
        var totalSizeKB = Math.round(totalSize / Math.pow(1024, 1));
        var totalSizeMB = Math.round(totalSize / Math.pow(1024, 2));
        //var totalSizeGB = totalsize / Math.pow(1024,3);
        $('#download_video_link').text('Download movie (' + totalSizeMB + 'MB)');
    }

    DR.recorder.onstop = function (event) {
        var finalBlob = new Blob(this.chunks, { 'type': this.chunks[0].type });
        //var finalBlob = new Blob(this.chunks, { type: "video/webm" });
        var finalSrc = window.URL.createObjectURL(finalBlob);
        this.chunks = []; this.totalsize = 0;
        $('#record_video').text('Recording stopped');
        $('#download_video_link').attr('href', finalSrc);
    }
}

DR.startRecording = function () {
    if (!this.recorder) this.initRecorder();
    DR.recorder.chunks = []; DR.recorder.totalsize = 0;

    DR.recorder.start(500);
    $('#record_video').text('Recording video...');
}

DR.stopRecording = function () {
    this.recorderActive = false;
    DR.recorder.stop();
}


DR.test = function () {
    var i = e[i];
}

DR.processUrl = function (url) {
    //console.log(url.match(/^compare\?items=/));
    if (url.match(/^compare\?items=/)) {
        url = window.location.origin + '/' + url;
    }
    var _URL = new URL(url);
    if (_URL.hostname === "www.wowhead.com" && _URL.pathname === "/compare") {
        this.loadComparison(_URL);
        return;
    }
    if (url.match(/wowhead\.com\/compare/)) {
        this.loadComparison(url);
    }
    else if (url.match(/:\/\/\w+.wowhead.com\/outfit=/)) {
        var s = "/outfit=" + url.match(/outfit=(\d+)/)[1];
        $.get(s, function (data) {
            var l = data.match(/su_addToSaved\('([\d:.]+)',/)[1];
            DR.loadComparison(document.domain + "/compare?items=" + l);
        });
    }
    else {
        alert("Not a valid comparison or outfit URL");
    }
}


DR.loadComparison = function (url) {
    //url = url.search.replace("?items=","").replaceAll(":",",")
    const itemList = DR.buildComparisonList(url.search);
    //console.log("ITEMLIST",itemList);
    fetch("/beta/gatherer?items=" + itemList[0]).then(res=>res.text()).then(text=>{
            var summary = DR.parseSummary(text,itemList[1]);
            var character = DR.setEquipment(WH.Wow.DressingRoom.getCharacterForHash(), summary);
            //console.log("Updating from hash",character);
            WH.Wow.DressingRoom.updateFromHash(character);
            DR.updatePaperdoll();
    });
}

DR.buildComparisonList = function(string) {
    string = string.replace("?items=","")
    var items = [];
    var itemsWithBonuses = [];
    var items_ = string.split(":");
    
    for( var i of items_ ) {
            var i_ = i.split('.0.0.0.0.0.0.0.0.0.');
            itemsWithBonuses.push(i_);
            items.push(i_[0]);
    }

    return [items.join(','),itemsWithBonuses];
}

DR.parseSummary = function (text,items) {
    //console.log('DR.parseSummary');
    let match = text.match(/(\{(.+)\})/);
    //console.log('NEW ITEMS',text,"match1",match[1]);
    if(!match || !match[1]) return;


    const json = JSON.parse(match[1]);

    //console.log("NEW ITEMS****************************",items,text);

    eval(text);

    if (!items) {
        alert("No item list in summary");
        return null;
    }

    var itemlist = {};
    for (var i of items) {
        //const id = i[0];
        //const bonus = i[1];
        //var slot = g_items[items[i][0]].jsonequip.slot;
        //console.log('g_items[i[0]]',i[0],g_items);
        //var slot = g_items[i[0]].json.slot;
        if(!json[i[0]]) continue;

        var slot = json[i[0]].json.slot;
        
        var slotEq = DR.characterSlotToEquipmentOrder[slot];
        //console.log("TEST",json[i[0]],json[i[0]].json.slot,'slotEQ'+slotEq);
        //itemlist[slotEq] = { itemId: items[i][0], itemBonus: items[i][10], slotId: slot };
        itemlist[slotEq] = { itemId: i[0], itemBonus: i[1], slotId: slot };
    }
    return itemlist;
}

DR.setEquipment = function (character, equipment) {
    //console.log("NEW EQUIPMENT*************",equipment);
    for (var i in character.equipment) {
        character.equipment[i].itemId = 0;
        character.equipment[i].itemBonus = 0;

        var slot = DR.equipmentOrderToCharacterSlot[i];
        character.equipment[i].slotId = slot;

        if (equipment[i]) {
            character.equipment[i].itemId = equipment[i].itemId;
            character.equipment[i].itemBonus = equipment[i].itemBonus;
        }
    }
    return character;
}

DR.parseSummary_old = function (text) {
    var s_match = text.match(/new Summary(\(.+)/);
    if (!s_match || !s_match[0]) {
        alert("No summary detected");
        return null;
    }
    var summary = null;
    var Summary = function (data) {
        this.data = data;
    }
    summary = eval(s_match[0]);
    var items = summary.data.groups[0];
    if (!items) {
        alert("No item list in summary");
        return null;
    }
    var itemlist = {};
    for (var i in items) {
        var slot = g_items[items[i][0]].jsonequip.slot;
        var slotEq = DR.characterSlotToEquipmentOrder[slot];
        itemlist[slotEq] = { itemId: items[i][0], itemBonus: items[i][10], slotId: slot };
    }
    return itemlist;
}

DR.loadComparison_old = function (url) {
    $.ajax("/compare" + url.search,
        {
            dataType: "text",
            complete: function (data) {
                var g_match = data.responseText.match(/(g_items.add.+)/g);
                eval(g_match.join(''));
                var summary = DR.parseSummary(data.responseText);
                var character = DR.setEquipment(WH.Wow.DressingRoom.getCharacterForHash(), summary);
                WH.Wow.DressingRoom.updateFromHash(character);
                DR.updatePaperdoll();
            }
        });
}

DR.setPaperdollSlot = function (itemId, itemBonus, k) {
    var item = { itemId: itemId, itemBonus: itemBonus };
    item.slotId = DR.getGitemsJsonProp(itemId, 'slot');
    var raw = WH.convertPaperdollItemDataToRaw(item);
    var data = { raw: raw, artifactAppearanceMod: 0 }
    g_paperdolls["dressing-room-paperdoll"].setSlot(data, true);
}

DR.updatePaperdoll = function () {
    var raw = [];
    var newCharacter = WH.Wow.DressingRoom.getCharacterForHash();
    for (var i in newCharacter.equipment) {
        var item = {};
        var slot = newCharacter.equipment[i].slotId;
        if (!slot) {
            newCharacter.equipment[i].slotId = DR.getGitemsJsonProp(newCharacter.equipment[i].itemId, 'slot');
        }
        item.raw = WH.convertPaperdollItemDataToRaw(newCharacter.equipment[i]);
        item.artifactAppearanceMod = 0;
        raw.push(item);
    }
    g_paperdolls["dressing-room-paperdoll"].updateSlots(raw);
}



DR.onUrlInput = function (e) {
    var url = e.target.value;
    DR.processUrl(url);
}

DR.onImportClick = function (e) {
    var urlInput = prompt("Enter MogIt or WoWHead comparison link  e.g.\ncompare?items=115551:177580:25822:54473:18730:125171:51232:24112:71360\n", "");

    if (urlInput != null && urlInput.length > 0) {
        DR.processUrl(urlInput);
    }
}

DR.getItem = function (id) {
    return unsafeWindow.g_items[id]
}

DR.getGitemsJsonProp = function (id, propname) {
    var gitem = unsafeWindow.g_items[id];
    if (!gitem) {
        //console.log( "No such g_item: " + id , g_items[id] );
        return null;
    }
    //console.log('JSON ITEM', g_items[id]);
    if (g_items[id].json && g_items[id].json[propname])
        return g_items[id].json[propname];
    else if (g_items[id].jsonequip && g_items[id].jsonequip[propname])
        return g_items[id].jsonequip[propname];
    else return null;
}

DR.itemAppearances = {};

DR.multiAppearances = function () {
    $('div.iconmedium > div.bonus-buttons').remove();
    $('div.iconmedium a').css('outline', 'none');
    var equipment = WH.Wow.DressingRoom.getCharacterForHash().equipment;
    for (var i in equipment) {
        var appearances = DR.getGitemsJsonProp(equipment[i].itemId, 'appearances');
        if (appearances && Object.keys(appearances).length > 1) {
            //if item has more than one appearance
            if (!DR.itemAppearances[equipment[i].itemId])
                DR.searchItem(equipment[i].itemId);
            else
                DR.createAppearanceButtons(equipment[i].itemId);
        }
    }
}

DR.getItemName = function (id) {
    var gitem = g_items[id];
    for (var prop in gitem) {
        if (prop.match('name_')) {
            return gitem[prop];
        }
    }
    return null;
}

DR.searchItem = function (id) {
    var name = this.getItemName(id);
    if (!name || name.length < 1)
        return false;
    var base = window.location.origin + '/search?q=';
    var url = base + encodeURIComponent(name) + '&json&skipcb&skipis';
    $.post(url, function (res) {
        //console.log("SEARCHING ITEM", id);
        var a = eval(res);
        DR.searchItemProcess(id, a);
    });
}

DR.searchItemProcess = function (id, data) {
    if (!data[1] || !data[1].items) {
        return false;
    }
    var bonuses = [];
    var items = data[1].items
    for (var i = 0; i < items.length; i++) {
        if (items[i].id == id) {
            bonuses.push(items[i].bonuses[0]);
        }
    }
    DR.itemAppearances[id] = bonuses.sort();
    //console.log(DR.itemAppearances);
    DR.createAppearanceButtons(id);
}

DR.createAppearanceButtons = function (id) {
    var bonuses = DR.itemAppearances[id];
    //find paperdoll icon
    var a = $('div.iconmedium > a[href*="/item=' + id + '"]');
    a.css('outline', '1px solid red');
    a.parent('div.iconmedium').find('div.bonus-buttons').remove();
    var div = $('<div class="bonus-buttons" style="z-index:300;position:absolute;display:flex;flex-direction:row;bottom:0px;"></div>');
    a.parent('div.iconmedium').append(div);
    for (var b = 0; b < bonuses.length; b++) {
        var bonusButton = $('<button class="appearance-button" style="width:10px;font-size:9px;background:red;color:white;padding:0px;" />');
        div.append(bonusButton);
        bonusButton[0].dataset.itemId = id;
        bonusButton[0].dataset.itemBonus = bonuses[b];
        bonusButton.text(b);
    }
}

DR.setItem = function (info) {
    var eq = WH.Wow.DressingRoom.getCharacterForHash();
    if (!info.slot) {
        for (var e in eq.equipment) {
            if (parseInt(eq.equipment[e].itemId) == parseInt(info.id)) {
                eq.equipment[e].itemBonus = info.bonus;
            }
        }
    }
    WH.Wow.DressingRoom.updateFromHash(eq);
    DR.setPaperdollSlot(parseInt(info.id), parseInt(info.bonus), true);
}

DR.createListCode = function (mode) {
    if (!mode) mode = DR.mode;
    if (mode == "wmv") return this.createWMVCode();

    //var equipment = WH.Wow.DressingRoom.getCharacter().equipment;
    var equipment = WH.Wow.DressingRoom.getCharacterForHash().equipment;

    var list = [];

    for (var e in equipment) {
        if (equipment[e].itemId > 0) {
            var id = equipment[e].itemId;
            //var name = DR.getGitemsJsonProp( id , 'name' ).slice(1);
            var name = DR.getGitemsJsonProp(id, 'name');
            var slot = DR.getGitemsJsonProp(id, 'slot');

            //var slotName = g_item_slots[slot];
            var slotName = DR.getSlotName(slot);
            slotName = slotName || "";
            slotName = slotName.charAt(0).toUpperCase() + slotName.slice(1);

            var subclass = DR.getGitemsJsonProp(id, 'subclass');

            if (e > 11 && subclass > -1) { //weapons
                slotName = g_item_subclasses[2][subclass];
            }
            slotName = (slotName) ? slotName : "";
            name = (name) ? name : "";
            var url = "https://" + document.domain + "/item=" + id + "&bonus=" + equipment[e].itemBonus;
            list.push({ name: name, href: url, slot: slotName });
        }
    }

    var list_text = "";
    var codes = { html: "", reddit: "", forum: "", plain: "" };

    /*
  for( var i=0;i<list.length;i++ ) {
     if( mode == "html")
        list_text = list_text + list[i].slot + ": " + '<a href="' + list[i].href + '">' + list[i].name + '</a><br>\n';
     else if( mode == "reddit")
        list_text = list_text + list[i].slot + ": " + '[' + list[i].name + '](' + list[i].href + ')\n\n';
     else if( mode == "forum")
        list_text = list_text + list[i].slot + ': [url=' + list[i].href + ']' + list[i].name + '[/url]';
     else
        list_text = list_text + list[i].slot + ': ' + list[i].name + '\n';
  }*/

    for (var i = 0; i < list.length; i++) {
        codes.html = codes.html + list[i].slot + ": " + '<a href="' + list[i].href + '">' + list[i].name + '</a><br>\n';
        codes.reddit = codes.reddit + list[i].slot + ": " + '[' + list[i].name + '](' + list[i].href + ')\n\n';
        codes.forum = codes.forum + list[i].slot + ': [url=' + list[i].href + ']' + list[i].name + '[/url]';
        codes.plain = codes.plain + list[i].slot + ': ' + list[i].name + '\n';
    }

    //console.log( list_text );
    $('#dr_code_box').val(codes[mode]);
    $('#dr-preview')[0].innerHTML = codes.html;

    DR.mode = mode;
    return codes;
}

DR.getSlotName = function (slotId) {
    if (!g_character_slots_data) return false;
    for (let i of g_character_slots_data) {
        if (i.id === slotId) return i.name;
    }
    return false;
}



DR.createWMVItemCode = function (itemId, bonus, slot) {
    var gitem = g_items[itemId];
    var charSlot = DR.equipmentOrderToWMVSlot[slot];
    if (!charSlot || !gitem) {
        return "";
    }

    var id, displayId, level;

    //if( gitem ) {
    var json = (gitem.json) ? gitem.json : gitem.jsonequip;

    id = itemId;
    var gappearance = g_items.getAppearance(id, [bonus], WH.Wow.DressingRoom.getCharacterForHash().settings.artifactAppearanceMod);
    if (gappearance) {
        displayId = gappearance[0];
        var appearances = json.appearances;
        var levels = [];
        for (var o in appearances) {
            levels.push(appearances[o][0]);
        }
        level = levels.indexOf(gappearance[0]);
    }
    else {
        //if item has only 1 appearance
        for (var j in json.appearances) {
            if (json.appearances[j][0]) {
                displayId = json.appearances[j][0];
                break;
            }
        }
    }
    //}

    id = (id) ? id : -1;
    displayId = (displayId) ? displayId : -1;
    level = (level) ? level : 0;

    var string = '   <item>\n' +
        '      <slot value="' + charSlot[0] + '" name="' + charSlot[1] + '"/>\n' +
        '      <id value="' + id + '" name="' + json.name + '"/>\n' +
        '      <displayId value="' + displayId + '"/>\n' +
        '      <level value="' + level + '"/>\n' +
        '   </item>\n';
    return string;

}

DR.testHairHat = function (equipment) {
    var baldHats = "cowl chapeau turban hat faceguard visor helm helmet fez cap shroud";
    function isBald(name) {
        name = name.toLowerCase();
        for (var i of baldHats.split(" ")) {
            if (name.includes(" "+i)||name.includes(i+" ")) return true;
        }
        return false;
    }
    if (equipment[1].itemId > 0) {
        var gitem = g_items[equipment[1].itemId];
        var json = (gitem.json) ? gitem.json : gitem.jsonequip;
        let bald = isBald(json.name);
        //console.log('HEAD', json, 'bald', bald,'json',json);
        return bald;
    }
}

DR.createWMVCode = function (param) {
    DR.mode = "wmv";
    GM_setValue('mode', 'wmv');
    DR.bald = false;
    var character = WH.Wow.DressingRoom.getCharacterForHash();
    var settings = character.settings;

    //var race = g_file_races[settings.race];
    var race = WH.getPageData('wow.race.names')[settings.race];

    const genders = ['Male', 'Female'];

    //const showHair = (DR.testHairHat(character.equipment))?0:1;
    const showHair = 1;

    //var gender = g_file_genders[settings.gender];
    var gender = genders[settings.gender];

    //console.log('CHARACTER***************', character);

    const Custom = WH.getPageData('wow.dressingRoom.customizationOptions');

    const getCustomizationOptions = function (e, t) {
        let i = Custom[e];
        i = i && i[t];
        i = i || [];
        return i.slice()
    }

    const opts = getCustomizationOptions(settings.race, settings.gender);

    //console.log('customizationOptions', opts);

    //console.log('RACE', settings.race, WH.getPageData('wow.race.names'));

    const hdModels = 'human gnome orc bloodelf tauren draenei nightelf troll dwarf scourge';

    const optsByName = {};

    function createCharDetail(detail) {
        let arr = [];
        for (let d in detail) {
            arr.push(`${d}="${detail[d]}"`);
        }
        return `<customization ${arr.join(" ")}/>`;
    }

    const optstrings = [];

    for (let set of opts) {
        const choices = set.choices;
        let oo = optsByName[set.slug];
        oo = {
            id: set.id,
            value: settings[set.slug],
            name: set.name,
        };

        for (let c of choices) {
            if (c.id === settings[set.slug]) {
                if (c.color) oo.color = '#' + c.color;
                else oo.valueName = c.name;
                break;
            }
        }
        oo.string = createCharDetail(oo);
        optstrings.push(oo.string);
        optsByName[set.slug] = oo;
    }

    //console.log('OPTIONS', optsByName, optstrings.join("\n"));

    //Race Modification
    race = race.toLowerCase().replaceAll(" ", "");
    //If Naga
    if (settings.race == 13) race = "naga_";
    //If Undead
    if (settings.race == 5) race = "scourge";

    var classs = settings.class;

    var eyeGlow = 1;
    if (settings.class == 6)
        eyeGlow = 2;

    const classNames = WH.getPageData('wow.playerClass.names');
    //console.log("CLASSES", classNames);

    var isDemonHunter = 0;
    var DHTattooStyle = 0;
    var DHTattooColor = 0;
    var DHHornStyle = 0;
    var DHBlindFolds = 0;

    //If Demon Hunter
    if (classs == 12) {
        isDemonHunter = 1;
        DHTattooStyle = settings.tattoos;
        DHTattooColor = 0;
        DHHornStyle = settings.hornstyle;
        DHBlindFolds = settings.blindfolds;
    }

    var hdon = (g_paperdolls["dressing-room-paperdoll"].enableHD);
    var hd = "";

    //Races that have HD & OLD models
    /*if( settings.race < 12 ){
        if(hdon) hd = "_hd";
        //demon hunter models should be hd
        if(classs == 12) hd = "_hd";
    }*/

    if (hdModels.split(" ").indexOf(race) > -1) {
        hd = "_hd";
    }

    //If Naga
    //if( settings.race == 13 ) race = "naga_";

    //Old char details
    /*  '     <skinColor value="' + settings["skin-color"] + '"/>\n' +
        '     <faceType value="' + settings.face + '"/>\n' +
        '     <hairColor value="' + settings["hair-color"] + '"/>\n' +
        '     <hairStyle value="' + settings["hair-style"] + '"/>\n' +*/


    gender = gender.toLowerCase();
    var modelstring = 'character/' + race + '/' + gender + '/' + race + gender + hd;

    var equiplist = WH.Wow.DressingRoom.getCharacterForHash().equipment;
    let equipText = "";
    for (var i in equiplist) {
        try {
            var itemCode = DR.createWMVItemCode(equiplist[i].itemId, equiplist[i].itemBonus, i);
            equipText = equipText + itemCode;
        }
        catch (e) {
            console.log("Error processing item " + equiplist[i].itemId + " \n Errorcode: " + e);
        }
    }

    const modelText = `<model>
                        <file name="${modelstring}.m2"/>
                        <CharDetails>
                            ${optstrings.join("\n")}
                            <eyeGlowType value="${eyeGlow}"/>
                            <showUnderwear value="1"/>
                            <showEars value="1"/>
                            <showHair value="${showHair}"/>
                            <showFacialHair value="1"/>
                            <showFeet value="0"/>
                            <isDemonHunter value="${isDemonHunter}"/>
                        </CharDetails>
                    </model>`;

    var text = `<?xml version="1.0" encoding="UTF-8"?>
                    <SavedCharacter version="2.0">
                        ${(param == 'eq') ? "" : modelText}
                        <equipment>
                        ${equipText}
                        </equipment>
                    </SavedCharacter>`;

    /*if (param == 'eq') text = '<?xml version="1.0" encoding="UTF-8"?>\n' +
        ' <SavedCharacter version="1.0">\n' +
        '<equipment>\n';



    text = text + '  </equipment>\n' +
        ' </SavedCharacter>\n';*/

    text = `<?xml version="1.0" encoding="UTF-8"?>\n` + DR.prettifyXml(text);
    $('#wmv_export').val(text);


    var file_uri = 'data:text/xml;charset=utf-8,' + encodeURIComponent(text);


    $("#download_file").attr('href', file_uri);
    $("#code_file").attr('href', file_uri);

    var filename = "My Character";

    if (param == "eq") $("#download_file").attr('download', filename + ".eq");
    else
        $("#download_file").attr('download', filename + ".chr");


    filename = "My Character";

    $("#dr_code_box").val(text);


    return text;
}

DR.prettifyXml = function (sourceXml) {
    var xmlDoc = new DOMParser().parseFromString(sourceXml, 'application/xml');
    var xsltDoc = new DOMParser().parseFromString([
        // describes how we want to modify the XML - indent everything
        '<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform">',
        '  <xsl:strip-space elements="*"/>',
        '  <xsl:template match="para[content-style][not(text())]">', // change to just text() to strip space in text nodes
        '    <xsl:value-of select="normalize-space(.)"/>',
        '  </xsl:template>',
        '  <xsl:template match="node()|@*">',
        '    <xsl:copy><xsl:apply-templates select="node()|@*"/></xsl:copy>',
        '  </xsl:template>',
        '  <xsl:output indent="yes"/>',
        '</xsl:stylesheet>',
    ].join('\n'), 'application/xml');

    var xsltProcessor = new XSLTProcessor();
    xsltProcessor.importStylesheet(xsltDoc);
    var resultDoc = xsltProcessor.transformToDocument(xmlDoc);
    var resultXml = new XMLSerializer().serializeToString(resultDoc);
    return resultXml;
};

DR.init();