Advertisement
Guest User

Untitled

a guest
Nov 16th, 2015
114
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /* Osmium
  2.  * Copyright (C) 2012 Romain "Artefact2" Dalmaso <artefact2@gmail.com>
  3.  *
  4.  * This program is free software: you can redistribute it and/or modify
  5.  * it under the terms of the GNU Affero General Public License as published by
  6.  * the Free Software Foundation, either version 3 of the License, or
  7.  * (at your option) any later version.
  8.  *
  9.  * This program is distributed in the hope that it will be useful,
  10.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12.  * GNU Affero General Public License for more details.
  13.  *
  14.  * You should have received a copy of the GNU Affero General Public License
  15.  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  16.  */
  17.  
  18. osmium_gettheme = function() {
  19.     var links = document.getElementsByTagName("link");
  20.     for(var i = 0; i < links.length; ++i) {
  21.         if(links[i].getAttribute('title') && links[i].getAttribute('rel').indexOf('stylesheet') !== -1) {
  22.             if(!links[i].disabled) return links[i].getAttribute('title');
  23.         }
  24.     }
  25. };
  26.  
  27. osmium_settheme = function(title) {
  28.     var links = document.getElementsByTagName("link");
  29.     var foundit = false;
  30.  
  31.     for(var i = 0; i < links.length; ++i) {
  32.         if(links[i].getAttribute('title') && links[i].getAttribute('rel').indexOf('stylesheet') !== -1) {
  33.             links[i].disabled = true;
  34.             if(links[i].getAttribute('title') === title) {
  35.                 foundit = true;
  36.                 links[i].disabled = false;
  37.             }
  38.         }
  39.     }
  40.  
  41.     if(foundit) {
  42.         return;
  43.     }
  44.     alert('not found');
  45.  
  46.     /* If the title was not found, enable the first sheet as a fallback */
  47.     for(var i = 0; i < links.length; ++i) {
  48.         if(links[i].getAttribute('title') && links[i].getAttribute('rel').indexOf('stylesheet') !== -1) {
  49.             links[i].disabled = false;
  50.             return;
  51.         }
  52.     }
  53. };
  54.  
  55. osmium_setcookie = function(name, value, validityms) {
  56.     var d = new Date();
  57.     d.setTime(d.getTime() + validityms);
  58.     document.cookie = name + "=" + value + "; expires=" + d.toGMTString() + "; path=/";
  59. };
  60.  
  61. $(function() {
  62.     var theme, label;
  63.     if(osmium_gettheme() === 'Light') {
  64.         theme = 'Dark';
  65.         label = 'Paint it black';
  66.     } else {
  67.         theme = 'Light';
  68.         label = 'Paint it white';
  69.     }
  70.  
  71.     var rlink = $(document.createElement('a'));
  72.     rlink.prop('id', 'repaint');
  73.     rlink.data('theme', theme);
  74.     rlink.text(label);
  75.     rlink.click(function() {
  76.         var theme, label, tts;
  77.         if((tts = $(this).data('theme')) === 'Dark') {
  78.             theme = 'Light';
  79.             label = 'Paint it white';
  80.         } else {
  81.             theme = 'Dark';
  82.             label = 'Paint it black';
  83.         }
  84.  
  85.         osmium_setcookie('t', tts, 86400 * 7 * 1000);
  86.         osmium_settheme(tts);
  87.         $(this).data('theme', theme);
  88.         $(this).text(label);
  89.         $(this).blur();
  90.     });
  91.  
  92.     $("div#wrapper + footer > p").append(' — ').append(rlink);
  93. });
  94. /* Osmium
  95.  * Copyright (C) 2012, 2013 Romain "Artefact2" Dalmaso <artefact2@gmail.com>
  96.  *
  97.  * This program is free software: you can redistribute it and/or modify
  98.  * it under the terms of the GNU Affero General Public License as published by
  99.  * the Free Software Foundation, either version 3 of the License, or
  100.  * (at your option) any later version.
  101.  *
  102.  * This program is distributed in the hope that it will be useful,
  103.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  104.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  105.  * GNU Affero General Public License for more details.
  106.  *
  107.  * You should have received a copy of the GNU Affero General Public License
  108.  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  109.  */
  110.  
  111. osmium_notifications = function(relative) {
  112.     var fetch = function() {
  113.         $.get(relative + '/internal/nc', function(cnt) {
  114.             var a = $("a#ncount");
  115.             var rawtitle = document.title.replace(/^\([1-9][0-9]*\) /, "");
  116.  
  117.             a.text(cnt);
  118.             a.data('count', cnt);
  119.             a.prop('title', cnt + ' new notification(s)');
  120.  
  121.             if(parseInt(cnt, 10) > 0) {
  122.                 a.show().css('display', 'inline-block');
  123.                 document.title = "(" + cnt + ") " + rawtitle;
  124.             } else {
  125.                 a.hide();
  126.                 document.title = rawtitle;
  127.             }
  128.         });
  129.  
  130.         setTimeout(fetch, 63000);
  131.     };
  132.  
  133.     setTimeout(fetch, 60000);
  134. };
  135.  
  136. $(function() {
  137.     var prefix = window.location.href.split("/");
  138.     prefix[prefix.length - 1] = '';
  139.  
  140.     osmium_notifications(
  141.         prefix.join('/') + $("div#osmium-data").data('relative')
  142.     );
  143. });
  144. /* Osmium
  145.  * Copyright (C) 2013 Romain "Artefact2" Dalmaso <artefact2@gmail.com>
  146.  *
  147.  * This program is free software: you can redistribute it and/or modify
  148.  * it under the terms of the GNU Affero General Public License as published by
  149.  * the Free Software Foundation, either version 3 of the License, or
  150.  * (at your option) any later version.
  151.  *
  152.  * This program is distributed in the hope that it will be useful,
  153.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  154.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  155.  * GNU Affero General Public License for more details.
  156.  *
  157.  * You should have received a copy of the GNU Affero General Public License
  158.  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  159.  */
  160.  
  161. $(function() {
  162.     var fbdiv = $(document.createElement('div')).prop('id', 'glob_feedback');
  163.     var ul = $(document.createElement('ul'));
  164.  
  165.     fbdiv.append($(document.createElement('span')).text('Feedback'));
  166.     fbdiv.append(ul);
  167.  
  168.     ul.append(
  169.         $(document.createElement('li')).append(
  170.             $(document.createElement('a'))
  171.                 .prop('href', 'https://github.com/osmium-org/osmium/issues/new')
  172.                 .text('Report an issue')
  173.         )
  174.     );
  175.  
  176.     ul.append(
  177.         $(document.createElement('li')).append(
  178.             $(document.createElement('a'))
  179.                 .prop('href', 'http://irc.lc/coldfront/osmium/osmiumguest@@@')
  180.                 .text('Live chat')
  181.         )
  182.     );
  183.  
  184.     ul.append(
  185.         $(document.createElement('li')).append(
  186.             $('footer a[rel="help"]').clone().text('Help & FAQ')
  187.         )
  188.     );
  189.  
  190.     $('div#wrapper').append(fbdiv);
  191.     fbdiv.children('span').first().on('click', function() {
  192.         var fb = $(this).parent();
  193.         var nfb = fb.clone(true); /* Hack to restart the CSS animation */
  194.  
  195.         if(nfb.hasClass('extended')) {
  196.             nfb.removeClass('extended');
  197.             nfb.addClass('notextended');
  198.         } else {
  199.             nfb.removeClass('notextended');
  200.             nfb.addClass('extended');
  201.         }
  202.  
  203.         fb.before(nfb);
  204.         fb.remove();
  205.     });
  206. });
  207. /* This snippet will be included in every page (generated with DOM\Page). */
  208.  
  209. /* Whenever you change this script, update the information in about.php. */
  210. /*<<< require external //cdnjs.cloudflare.com/ajax/libs/jquery/1.10.2/jquery.min.js before >>>*/
  211.  
  212. /*<<< require snippet persistent_theme >>>*/
  213. /*<<< require snippet notifications >>>*/
  214. /*<<< require snippet feedback >>>*/
  215.  
  216. /* Osmium
  217.  * Copyright (C) 2014 Romain "Artefact2" Dalmaso <artefact2@gmail.com>
  218.  *
  219.  * This program is free software: you can redistribute it and/or modify
  220.  * it under the terms of the GNU Affero General Public License as published by
  221.  * the Free Software Foundation, either version 3 of the License, or
  222.  * (at your option) any later version.
  223.  *
  224.  * This program is distributed in the hope that it will be useful,
  225.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  226.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  227.  * GNU Affero General Public License for more details.
  228.  *
  229.  * You should have received a copy of the GNU Affero General Public License
  230.  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  231.  */
  232.  
  233. osmium_put_setting = function(name, value) {
  234.     $.ajax({
  235.         type: 'POST',
  236.         url: osmium_relative + '/internal/ps/' + name,
  237.         data: {
  238.             payload: JSON.stringify(value),
  239.             'o___csrf': osmium_token
  240.         }
  241.     });
  242. };
  243. /*<<< require external /static-1/mousetrap.min.js >>>*/
  244. if(typeof(localStorage) === 'undefined') {
  245.     localStorage = {
  246.         getItem: function() { return null; },
  247.         setItem: function() {},
  248.         removeItem: function() {},
  249.     };
  250. }
  251.  
  252. if(typeof(sessionStorage) === 'undefined') {
  253.     sessionStorage = {
  254.         getItem: function() { return null; },
  255.         setItem: function() {},
  256.         removeItem: function() {},
  257.     };
  258. }
  259. /* Osmium
  260.  * Copyright (C) 2014 Romain "Artefact2" Dalmaso <artefact2@gmail.com>
  261.  *
  262.  * This program is free software: you can redistribute it and/or modify
  263.  * it under the terms of the GNU Affero General Public License as published by
  264.  * the Free Software Foundation, either version 3 of the License, or
  265.  * (at your option) any later version.
  266.  *
  267.  * This program is distributed in the hope that it will be useful,
  268.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  269.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  270.  * GNU Affero General Public License for more details.
  271.  *
  272.  * You should have received a copy of the GNU Affero General Public License
  273.  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  274.  */
  275.  
  276. /*<<< require snippet localstorage_fallback >>>*/
  277.  
  278.  
  279.  
  280. osmium_keyboard_commands = {};
  281.  
  282. /* Register a keyboard command.
  283.  *
  284.  * @param shortnames a Mousetrap compatible shortcut definition, or an
  285.  * array of definitions, or null.
  286.  *
  287.  * @param longname a long descriptive name of the command ([a-z-]
  288.  * only).
  289.  *
  290.  * @param description a description of what the command does.
  291.  *
  292.  * @param action called when the command is used.
  293.  */
  294. osmium_register_keyboard_command = function(shortnames, longname, description, action) {
  295.     if(longname in osmium_keyboard_commands) {
  296.         osmium_unregister_keyboard_command(longname);
  297.     }
  298.  
  299.     if($.isEmptyObject(osmium_keyboard_commands)) {
  300.         /* Bind M-x */
  301.         Mousetrap.bind([ 'meta+x', 'alt+x', 'command+x' ], function() {
  302.             if($('div#mx-cont').length !== 0) {
  303.                 $('div#mx-cont > form > input[type="text"]').focus();
  304.                 return false;
  305.             }
  306.  
  307.             var history = sessionStorage.getItem('mx-history');
  308.             history = (typeof(history) === 'string') ? JSON.parse(history) : [];
  309.             var histposition = history.length;
  310.             var histlast = '';
  311.  
  312.             var c = $(document.createElement('div')).prop('id', 'mx-cont');
  313.             var tabcount = 0;
  314.  
  315.             var bg = $(document.createElement('div')).prop('id', 'mx-bg');
  316.             c.append(bg);
  317.  
  318.             var form = $(document.createElement('form'));
  319.             form.prop('method', 'get');
  320.             form.prop('action', '?');
  321.             c.append(form);
  322.  
  323.             var inp = $(document.createElement('input'));
  324.             inp.prop('type', 'text');
  325.             inp.prop('placeholder', 'Enter command… (Press C-g or ESC twice to exit)');
  326.             inp.addClass('mousetrap'); /* Fire events even if this input has focus */
  327.             var lastval = inp.val();
  328.             form.append(inp);
  329.  
  330.             var setinpval = function(val) {
  331.                 inp.val(val);
  332.                 inp.get(0).setSelectionRange(val.length, val.length);
  333.             };
  334.  
  335.             var submit = $(document.createElement('input'));
  336.             submit.prop('type', 'submit');
  337.             form.append(submit); /* Form won't submit on RET without a submit button */
  338.  
  339.             var ul = $(document.createElement('ul'));
  340.             c.append(ul);
  341.             for(var lc in osmium_keyboard_commands) {
  342.                 var cmd = osmium_keyboard_commands[lc];
  343.  
  344.                 ul.append(
  345.                     $(document.createElement('li'))
  346.                         .text(lc)
  347.                         .prop(
  348.                             'title',
  349.                             lc + "\n"
  350.                                 + (
  351.                                     cmd.shortnames !== null ?
  352.                                         (((typeof(cmd.shortnames) === 'string') ?
  353.                                          cmd.shortnames
  354.                                          : cmd.shortnames.join(', ')) + "\n")
  355.                                     : ''
  356.                                 )
  357.                                 + cmd.description
  358.                         )
  359.                 );
  360.             }
  361.             ul.find('li').sort(function(a, b) {
  362.                 var at = $(a).text();
  363.                 var bt = $(b).text();
  364.                 return (at < bt) ? -1 : ((at > bt) ? 1 : 0);
  365.  
  366.             }).appendTo(ul);
  367.  
  368.             var exit = function(e, after) {
  369.                 Mousetrap.unbind([ 'esc esc', 'ctrl+g' ]);
  370.                 Mousetrap.unbind('tab');
  371.                 Mousetrap.unbind('up');
  372.                 Mousetrap.unbind('down');
  373.  
  374.                 c.fadeOut(100, function() {
  375.                     c.remove();
  376.                     if(typeof after === 'function') {
  377.                         after();
  378.                     }
  379.                 });
  380.  
  381.                 return false;
  382.             };
  383.  
  384.             Mousetrap.bind([ 'esc esc', 'ctrl+g' ], exit);
  385.             bg.click(exit);
  386.  
  387.             form.submit(function(e) {
  388.                 var command = inp.val();
  389.                 if(command in osmium_keyboard_commands) {
  390.                     inp.removeClass('error');
  391.                     inp.addClass('success');
  392.  
  393.                     history.push(command);
  394.                     sessionStorage.setItem('mx-history', JSON.stringify(history));
  395.  
  396.                     exit(e, function() {
  397.                         osmium_keyboard_commands[command].action();
  398.                     });
  399.                 } else {
  400.                     inp.addClass('error');
  401.                 }
  402.  
  403.                 return false;
  404.             });
  405.  
  406.             inp.on('keyup', function() {
  407.                 if(inp.val() !== lastval) {
  408.                     lastval = inp.val();
  409.                     tabcount = 0;
  410.                     inp.removeClass('error');
  411.                 }
  412.             });
  413.  
  414.             Mousetrap.bind('tab', function() {
  415.                 var buf = inp.val();
  416.  
  417.                 if(tabcount === 0) {
  418.                     /* Hide non-matching commands, fill up input with
  419.                      * largest prefix of matched commands */
  420.                     ul.find('li').each(function() {
  421.                         var li = $(this);
  422.                         if(li.text().substring(0, buf.length) === buf) {
  423.                             li.show();
  424.                         } else {
  425.                             li.hide();
  426.                         }
  427.                     });
  428.  
  429.                     var matches = ul.find('li:visible');
  430.                     if(matches.length === 0) {
  431.                         inp.addClass('error');
  432.                     } else {
  433.                         inp.removeClass('error');
  434.                         var largestprefix = matches.first().text();
  435.  
  436.                         matches.each(function() {
  437.                             var v = $(this).text();
  438.                             var i = 0;
  439.                             var ml = largestprefix.length;
  440.  
  441.                             while(i <= ml && largestprefix.substring(0, i) === v.substring(0, i)) {
  442.                                 ++i;
  443.                             }
  444.  
  445.                             largestprefix = v.substring(0, i-1);
  446.                         });
  447.  
  448.                         setinpval(largestprefix);
  449.                         lastval = largestprefix;
  450.                     }
  451.                 } else if(tabcount >= 2) {
  452.                     /* Cycle through matches */
  453.  
  454.                     var matches = ul.find('li:visible');
  455.                     if(matches.length >= 1) {
  456.                         setinpval($(matches[(tabcount - 2) % matches.length]).text());
  457.                         lastval = inp.val();
  458.                     }
  459.                 }
  460.  
  461.                 ++tabcount;
  462.                 return false;
  463.             });
  464.  
  465.             Mousetrap.bind('up', function() {
  466.                 --histposition;
  467.  
  468.                 if(histposition < 0) {
  469.                     histposition = history.length;
  470.                     setinpval(histlast);
  471.                 } else {
  472.                     if(histposition === history.length - 1) histlast = inp.val();
  473.                     setinpval(history[histposition]);
  474.                 }
  475.  
  476.                 return false;
  477.             });
  478.             Mousetrap.bind('down', function() {
  479.                 ++histposition;
  480.  
  481.                 if(histposition === history.length) {
  482.                     setinpval(histlast);
  483.                 }
  484.  
  485.                 if(histposition >= history.length) histposition = history.length;
  486.                 else {
  487.                     setinpval(history[histposition]);
  488.                 }
  489.  
  490.                 return false;
  491.             });
  492.  
  493.             c.hide();
  494.             $('body').append(c);
  495.  
  496.             c.fadeIn(100);
  497.             inp.focus();
  498.  
  499.             return false;
  500.         });
  501.     }
  502.  
  503.     osmium_keyboard_commands[longname] = {
  504.         shortnames: shortnames,
  505.         longname: longname,
  506.         description: description,
  507.         action: action,
  508.     };
  509.  
  510.     if(shortnames !== null) {
  511.         Mousetrap.bind(shortnames, action);
  512.     }
  513. };
  514.  
  515. /* Unregister a keyboard command. Use the same longname you used in
  516.  * osmium_register_keyboard_command. */
  517. osmium_unregister_keyboard_command = function(longname) {
  518.     if(!(longname in osmium_keyboard_commands)) return;
  519.  
  520.     if(osmium_keyboard_commands[longname].shortnames !== null) {
  521.         Mousetrap.unbind(osmium_keyboard_commands[longname].shortnames);
  522.     }
  523.  
  524.     delete osmium_keyboard_commands[longname];
  525.  
  526.     if($.isEmptyObject(osmium_keyboard_commands)) {
  527.         Mousetrap.unbind([ 'meta+x', 'alt+x', 'command+x' ]);
  528.     }
  529. };
  530. if(typeof(localStorage) === 'undefined') {
  531.     localStorage = {
  532.         getItem: function() { return null; },
  533.         setItem: function() {},
  534.         removeItem: function() {},
  535.     };
  536. }
  537.  
  538. if(typeof(sessionStorage) === 'undefined') {
  539.     sessionStorage = {
  540.         getItem: function() { return null; },
  541.         setItem: function() {},
  542.         removeItem: function() {},
  543.     };
  544. }
  545. /* Osmium
  546.  * Copyright (C) 2013, 2014 Romain "Artefact2" Dalmaso <artefact2@gmail.com>
  547.  * Copyright (C) 2013 Josiah Boning <jboning@gmail.com>
  548.  *
  549.  * This program is free software: you can redistribute it and/or modify
  550.  * it under the terms of the GNU Affero General Public License as published by
  551.  * the Free Software Foundation, either version 3 of the License, or
  552.  * (at your option) any later version.
  553.  *
  554.  * This program is distributed in the hope that it will be useful,
  555.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  556.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  557.  * GNU Affero General Public License for more details.
  558.  *
  559.  * You should have received a copy of the GNU Affero General Public License
  560.  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  561.  */
  562.  
  563. argsh = function(x) {
  564.     return Math.log(x + Math.sqrt(x*x + 1.0));
  565. };
  566.  
  567. osmium_targeting_time = function(targeter_scanres, targetee_sigradius) {
  568.     /* Formula taken from the official wiki */
  569.     return 40000.0 / (targeter_scanres * Math.pow(argsh(targetee_sigradius), 2));
  570. };
  571.  
  572. osmium_gen_fattribs = function() {
  573.     osmium_fattribs_load();
  574.  
  575.     var s = osmium_clf['X-damage-profile'][1][0] + osmium_clf['X-damage-profile'][1][1]
  576.         + osmium_clf['X-damage-profile'][1][2] + osmium_clf['X-damage-profile'][1][3];
  577.  
  578.     $("section#defense > h4 > span.pname")
  579.         .text(osmium_clf['X-damage-profile'][0])
  580.         .prop('title',
  581.               "EM: \t\t\t" + (100 * osmium_clf['X-damage-profile'][1][0] / s).toFixed(1) + "%\n" +
  582.               "Explosive: \t" + (100 * osmium_clf['X-damage-profile'][1][1] / s).toFixed(1) + "%\n" +
  583.               "Kinetic: \t\t" + (100 * osmium_clf['X-damage-profile'][1][2] / s).toFixed(1) + "%\n" +
  584.               "Thermal: \t\t" + (100 * osmium_clf['X-damage-profile'][1][3] / s).toFixed(1) + "%"
  585.              )
  586.     ;
  587.  
  588.     var t = "Scan resolution\n\nTime to lock…";
  589.     var sr = parseFloat($("span#scan_resolution").data('value'));
  590.     var sig = parseFloat($("p#signature_radius").data('value'));
  591.  
  592.     for(var i = 0; i < osmium_targetclass.length; ++i) {
  593.         if(!osmium_targetclass[i][1]) continue;
  594.         t += "\n" + osmium_targetclass[i][0] + " (" + osmium_targetclass[i][3].toFixed(0) + " m): "
  595.             + osmium_targeting_time(sr, osmium_targetclass[i][3]).toFixed(1) + " s"
  596.     }
  597.  
  598.     $("span#scan_resolution").prop('title', t);
  599.  
  600.     t = "Signature radius\n\nTime to be locked by…";
  601.  
  602.     for(var i = 0; i < osmium_targetclass.length; ++i) {
  603.         if(!osmium_targetclass[i][2]) continue;
  604.         t += "\n" + osmium_targetclass[i][0] + " (" + osmium_targetclass[i][4].toFixed(0) + " mm) : "
  605.             + osmium_targeting_time(osmium_targetclass[i][4], sig).toFixed(1) + " s"
  606.     }
  607.  
  608.     $("p#signature_radius").prop('title', t);
  609.  
  610.     var capacitor = $("p#capacitor");
  611.     capacitor.children("span.mainsprite:first-child, svg:first-child").replaceWith(
  612.         osmium_gen_capacitor(capacitor.data('capacity'), capacitor.data('usage'))
  613.     );
  614. };
  615.  
  616. osmium_init_fattribs = function() {
  617.     osmium_ctxmenu_bind($("div#computed_attributes > section#mastery"), function() {
  618.         var menu = osmium_ctxmenu_create();
  619.         for(var i = 0; i < osmium_skillsets.length; ++i) {
  620.             osmium_ctxmenu_add_subctxmenu(menu, osmium_skillsets[i], (function(sname) {
  621.                 return function() {
  622.                     var smenu = osmium_ctxmenu_create();
  623.  
  624.                     osmium_ctxmenu_add_option(smenu, "Use", function() {
  625.                         osmium_clf.metadata['X-Osmium-skillset'] = sname;
  626.                         osmium_undo_push();
  627.                         osmium_commit_clf();
  628.                     }, { 'default': true });
  629.  
  630.                     osmium_ctxmenu_add_option(smenu, "Set default", function() {
  631.                         osmium_put_setting('default_skillset', sname);
  632.                     }, {});
  633.  
  634.                     return smenu;
  635.                 };
  636.             })(osmium_skillsets[i]), {
  637.                 toggled: osmium_clf.metadata['X-Osmium-skillset'] === osmium_skillsets[i]
  638.             });
  639.         }
  640.         return menu;
  641.     });
  642.  
  643.     osmium_ctxmenu_bind($("div#computed_attributes > section#defense"), function() {
  644.         var menu = osmium_ctxmenu_create();
  645.  
  646.         osmium_ctxmenu_add_subctxmenu(menu, "Damage profiles", function() {
  647.             var smenu = osmium_ctxmenu_create();
  648.  
  649.             for(var k in osmium_damage_profiles) {
  650.                 osmium_ctxmenu_add_subctxmenu(smenu, k, (function(profiles) {
  651.                     return function() {
  652.                         var ssmenu = osmium_ctxmenu_create();
  653.  
  654.                         for(var z in profiles) {
  655.                             var s = profiles[z][0] + profiles[z][1] + profiles[z][2] + profiles[z][3];
  656.  
  657.                             var opts = {
  658.                                 toggled: z === osmium_clf['X-damage-profile'][0],
  659.                                 title: "EM: \t\t\t" + (100 * profiles[z][0] / s).toFixed(1) + "%\n" +
  660.                                     "Explosive: \t" + (100 * profiles[z][1] / s).toFixed(1) + "%\n" +
  661.                                     "Kinetic: \t\t" + (100 * profiles[z][2] / s).toFixed(1) + "%\n" +
  662.                                     "Thermal: \t\t" + (100 * profiles[z][3] / s).toFixed(1) + "%"
  663.                             };
  664.  
  665.                             if(profiles[z].length >= 5) {
  666.                                 opts.icon = profiles[z][4];
  667.                             }
  668.  
  669.                             osmium_ctxmenu_add_option(ssmenu, z, (function(name, profile) {
  670.                                 return function() {
  671.                                     osmium_clf['X-damage-profile'] = [ name, profile.slice(0, 4) ];
  672.                                     osmium_undo_push();
  673.                                     osmium_commit_clf();
  674.                                 };
  675.                             })(z, profiles[z]), opts);
  676.                         }
  677.                         return ssmenu;
  678.                     };
  679.                 })(osmium_damage_profiles[k]), {});
  680.             }
  681.  
  682.             osmium_ctxmenu_add_subctxmenu(smenu, "Custom", function() {
  683.                 var ssmenu = osmium_ctxmenu_create();
  684.                 var count = 0;
  685.  
  686.                 for(var k in osmium_custom_damage_profiles) {
  687.                     ++count;
  688.  
  689.                     var profile = osmium_custom_damage_profiles[k];
  690.                     var s = profile[0] + profile[1] + profile[2] + profile[3];
  691.  
  692.                     osmium_ctxmenu_add_subctxmenu(ssmenu, k, (function(k, profile) {
  693.                         return function() {
  694.                             var sssmenu = osmium_ctxmenu_create();
  695.  
  696.                             osmium_ctxmenu_add_option(sssmenu, "Use", function() {
  697.                                 osmium_clf['X-damage-profile'] = [ k, profile ];
  698.                                 osmium_undo_push();
  699.                                 osmium_commit_clf();
  700.                             }, { 'default': true });
  701.  
  702.                             osmium_ctxmenu_add_option(sssmenu, "Remove", function() {
  703.                                 if(k === osmium_clf['X-damage-profile'][0]) {
  704.                                     osmium_clf['X-damage-profile'] = [ "Uniform", [ .25, .25, .25, .25 ] ];
  705.                                     osmium_commit_clf();
  706.                                 }
  707.  
  708.                                 delete osmium_custom_damage_profiles[k];
  709.                                 osmium_commit_custom_damage_profiles();
  710.                             }, {});
  711.  
  712.                             return sssmenu;
  713.                         };
  714.                     })(k, osmium_custom_damage_profiles[k]), {
  715.                         toggled: k === osmium_clf['X-damage-profile'][0],
  716.                         title: "EM: \t\t\t" + (100 * profile[0] / s).toFixed(1) + "%\n" +
  717.                             "Explosive: \t" + (100 * profile[1] / s).toFixed(1) + "%\n" +
  718.                             "Kinetic: \t\t" + (100 * profile[2] / s).toFixed(1) + "%\n" +
  719.                             "Thermal: \t\t" + (100 * profile[3] / s).toFixed(1) + "%"
  720.                     });
  721.                 }
  722.  
  723.                 if(count >= 1) {
  724.                     osmium_ctxmenu_add_separator(ssmenu);
  725.                 }
  726.  
  727.                 osmium_ctxmenu_add_option(ssmenu, "Create…", function() {
  728.                     var name = prompt("Damage profile name:");
  729.                     if(!name) return;
  730.                     var prof = prompt("Damage values (EM, Explosive, Kinetic, Thermal):",
  731.                                       "[ 0.25, 0.25, 0.25, 0.25 ]");
  732.                     if(!prof) return;
  733.  
  734.                     try {
  735.                         var p = JSON.parse(prof);
  736.                         var s;
  737.  
  738.                         if(p[0] < 0 || p[1] < 0 || p[2] < 0 || p[3] < 0 || (s = p[0] + p[1] + p[2] + p[3]) <= 0) {
  739.                             alert("Incorrect damage values: all four numbers must be positive, and at least one of them must be nonzero.");
  740.                         } else {
  741.                             osmium_custom_damage_profiles[name] = [
  742.                                 p[0] / s, p[1] / s, p[2] / s, p[3] / s
  743.                             ];
  744.  
  745.                             osmium_clf['X-damage-profile'] = [ name, osmium_custom_damage_profiles[name] ];
  746.                             osmium_undo_push();
  747.                             osmium_commit_clf();
  748.                             osmium_commit_custom_damage_profiles();
  749.                         }
  750.                     } catch(e) {
  751.                         alert("Incorrect syntax in damage values.");
  752.                     }
  753.  
  754.                     $("ul#ctxmenu").trigger('delete_menu');
  755.                 }, {});
  756.  
  757.                 return ssmenu;
  758.             }, {});
  759.  
  760.             return smenu;
  761.         }, {});
  762.  
  763.         return menu;
  764.     });
  765. };
  766.  
  767. osmium_commit_custom_damage_profiles = function() {
  768.     osmium_put_setting('custom_damage_profiles', osmium_custom_damage_profiles);
  769. };
  770. /*<<< require external //cdnjs.cloudflare.com/ajax/libs/jquery.perfect-scrollbar/0.4.6/jquery.perfect-scrollbar-with-mousewheel.min.js >>>*/
  771. /*<<< require css //cdnjs.cloudflare.com/ajax/libs/jquery.perfect-scrollbar/0.4.6/perfect-scrollbar.css >>>*/
  772. /* Osmium
  773.  * Copyright (C) 2012, 2013, 2014 Romain "Artefact2" Dalmaso <artefact2@gmail.com>
  774.  *
  775.  * This program is free software: you can redistribute it and/or modify
  776.  * it under the terms of the GNU Affero General Public License as published by
  777.  * the Free Software Foundation, either version 3 of the License, or
  778.  * (at your option) any later version.
  779.  *
  780.  * This program is distributed in the hope that it will be useful,
  781.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  782.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  783.  * GNU Affero General Public License for more details.
  784.  *
  785.  * You should have received a copy of the GNU Affero General Public License
  786.  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  787.  */
  788.  
  789. /*<<< require snippet perfectscrollbar >>>*/
  790. /*<<< require snippet mousetrap >>>*/
  791.  
  792. osmium_modal_clear = function() {
  793.     $("body > div#modalbg").fadeOut(250);
  794.     $("body > div#modal").animate({
  795.         'margin-top': -$('body').width() + "px"
  796.     }, 250);
  797. }
  798.  
  799. osmium_modal = function(inside) {
  800.     Mousetrap.unbind('esc');
  801.     $("body > div#modalbg, body > div#modal").remove();
  802.  
  803.     var bg = $(document.createElement('div'));
  804.     bg.prop('id', 'modalbg');
  805.     bg.click(osmium_modal_clear);
  806.     bg.hide();
  807.  
  808.     var modal = $(document.createElement('div'));
  809.     modal.prop('id', 'modal');
  810.  
  811.     $('body')
  812.         .append(bg)
  813.         .append(modal)
  814.     ;
  815.  
  816.     Mousetrap.bind('esc', osmium_modal_clear);
  817.  
  818.     modal
  819.         .css('margin-left', (-modal.width() / 2) + "px")
  820.         .css('margin-top', -$('body').width() + "px")
  821.         .append(inside)
  822.         .animate({
  823.             'margin-top': (-modal.height() / 2) + "px"
  824.         }, 500)
  825.     ;
  826.  
  827.     modal.perfectScrollbar({ wheelSpeed: 40 });
  828.  
  829.     bg.fadeIn(500);
  830. };
  831.  
  832. osmium_modal_rotextarea = function(title, contents) {
  833.     var m = $(document.createElement('div'));
  834.     var h = $(document.createElement('header'));
  835.     var textarea = $(document.createElement('textarea'));
  836.  
  837.     h.append($(document.createElement('h2')).text(title));
  838.     m.append(h);
  839.  
  840.     textarea.text(contents);
  841.     textarea.prop('readonly', 'readonly').prop('spellcheck', false);
  842.     textarea.css({
  843.         position: 'absolute',
  844.         top: '0',
  845.         left: '0',
  846.         width: '100%',
  847.         height: '100%',
  848.         'font-size': '0.8em'
  849.     });
  850.     m.append($(document.createElement('div')).css({
  851.         position: 'absolute',
  852.         top: '3.25em',
  853.         left: '1em',
  854.         right: '1em',
  855.         bottom: '1em'
  856.     }).append(textarea));
  857.  
  858.     osmium_modal(m.children());
  859.     textarea.focus().select();
  860. };
  861. /* Osmium
  862.  * Copyright (C) 2013 Romain "Artefact2" Dalmaso <artefact2@gmail.com>
  863.  *
  864.  * This program is free software: you can redistribute it and/or modify
  865.  * it under the terms of the GNU Affero General Public License as published by
  866.  * the Free Software Foundation, either version 3 of the License, or
  867.  * (at your option) any later version.
  868.  *
  869.  * This program is distributed in the hope that it will be useful,
  870.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  871.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  872.  * GNU Affero General Public License for more details.
  873.  *
  874.  * You should have received a copy of the GNU Affero General Public License
  875.  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  876.  */
  877.  
  878. osmium_sprite = function(alt, grid_x, grid_y, grid_width, grid_height, width, height) {
  879.     var span = $(document.createElement('span'));
  880.     var img = $(document.createElement('img'));
  881.  
  882.     span.addClass('mainsprite');
  883.     span.css({
  884.         width: width + 'px',
  885.         height: height + 'px'
  886.     });
  887.  
  888.     img.prop('src', osmium_relative + '/static-' + osmium_staticver + '/icons/sprite.png');
  889.     img.prop('alt', alt);
  890.     img.prop('title', alt);
  891.     img.css({
  892.         width: (width / grid_width * 1024) + 'px',
  893.         height: (height / grid_height * 1024) + 'px',
  894.         top: (-grid_x * width) + 'px',
  895.         left: (-grid_y * height) + 'px'
  896.     });
  897.  
  898.     span.append(img);
  899.     return span;
  900. };
  901. /* Osmium
  902.  * Copyright (C) 2012, 2013 Romain "Artefact2" Dalmaso <artefact2@gmail.com>
  903.  *
  904.  * This program is free software: you can redistribute it and/or modify
  905.  * it under the terms of the GNU Affero General Public License as published by
  906.  * the Free Software Foundation, either version 3 of the License, or
  907.  * (at your option) any later version.
  908.  *
  909.  * This program is distributed in the hope that it will be useful,
  910.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  911.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  912.  * GNU Affero General Public License for more details.
  913.  *
  914.  * You should have received a copy of the GNU Affero General Public License
  915.  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  916.  */
  917.  
  918. osmium_orig_anchor = null;
  919. osmium_selected_tabs = [];
  920. osmium_available_tabs = [];
  921. osmium_tab_ul_count = 0;
  922.  
  923. osmium_tabify = function(ul, selected) {
  924.     if(osmium_orig_anchor === null) {
  925.         if(window.location.hash) {
  926.             osmium_orig_anchor = window.location.hash.substring(1).split(",");
  927.         } else {
  928.             osmium_orig_anchor = [];
  929.         }
  930.     }
  931.  
  932.     var tabs = [];
  933.     var i = 0;
  934.     var selected_pref = 0;
  935.  
  936.     ul.find('li > a').each(function() {
  937.         var t = $(this);
  938.         var href = t.attr('href');
  939.         if(href.substring(0, 1) != '#') return;
  940.         t.parent().addClass('anchor');
  941.         tabs.push(href.substring(1));
  942.         ++i;
  943.     });
  944.  
  945.     for(var j = 0; j < osmium_orig_anchor.length; ++j) {
  946.         var index = $.inArray(osmium_orig_anchor[j], tabs);
  947.         if(index !== -1) {
  948.             /* Got one of our tabs in hash */
  949.             if(selected_pref < 1) {
  950.                 selected = index;
  951.                 selected_pref = 1;
  952.             }
  953.             osmium_orig_anchor.splice(j, 1);
  954.  
  955.             /* Don't break here, as this choice may be overridden by a
  956.              * "true" anchor in another tab */
  957.         } else {
  958.             if(selected_pref >= 2) continue; /* Already found */
  959.  
  960.             /* Not one of our tabs, see if it is an anchor in one of our tabs */
  961.             var target = $('#' + osmium_orig_anchor[j]);
  962.             if(target.length === 0) continue;
  963.             target = target.parent();
  964.             do {
  965.                 var id = target.prop('id');
  966.                 var index;
  967.                 if(id && ((index = $.inArray(id, tabs)) !== -1)) {
  968.                     /* Found one of our tabs as parent */
  969.                     selected = index;
  970.                     selected_pref = 2;
  971.                     break;
  972.                 }
  973.                 target = target.parent();
  974.             } while("length" in target && target.length !== 0);
  975.         }
  976.     }
  977.  
  978.     i = 0;
  979.     ul.find('li.anchor').each(function() {
  980.         var t = $(this);
  981.         var href = t.children('a').attr('href');
  982.         var tget = $(href).addClass('notarget');
  983.  
  984.         if(i !== selected) {
  985.             tget.hide().trigger('made_hidden');
  986.         } else {
  987.             t.addClass('active');
  988.             tget.trigger('made_visible');
  989.         }
  990.         ++i;
  991.     });
  992.  
  993.     var found = false;
  994.     for(i = 0; i < osmium_available_tabs.length; ++i) {
  995.         if($.inArray(tabs[selected], osmium_available_tabs[i]) !== -1) {
  996.             ul.data('tab_ul_index', i);
  997.             found = true;
  998.             break;
  999.         }
  1000.     }
  1001.     if(!found) {
  1002.         osmium_available_tabs.push(tabs);
  1003.         osmium_selected_tabs.push(tabs[selected]);
  1004.         ul.data('tab_ul_index', osmium_tab_ul_count++);
  1005.     }
  1006.  
  1007.     ul.find('li.anchor').on('click', osmium_tab_click);
  1008.  
  1009.     for(i = 0; i < osmium_orig_anchor.length; ++i) {
  1010.         var target = $("#" + osmium_orig_anchor[i]);
  1011.         if(target.length === 0) continue;
  1012.         if(!target.is(":visible")) continue;
  1013.         $(window).scrollTop(target.offset().top);
  1014.         return;
  1015.     }
  1016.  
  1017.     $(window).scrollTop(0);
  1018. };
  1019.  
  1020. osmium_tab_click = function(e) {
  1021.     var li = $(this);
  1022.     var ul_index = li.parent().data('tab_ul_index');
  1023.     var tabnames = osmium_available_tabs[ul_index];
  1024.     var want = li.children('a').blur().attr('href').substring(1);
  1025.  
  1026.     for(var i = 0; i < tabnames.length; ++i) {
  1027.         if(tabnames[i] === want) {
  1028.             $("#" + want).fadeIn(250).trigger('made_visible');
  1029.         } else {
  1030.             $("#" + tabnames[i]).hide().trigger('made_hidden');
  1031.         }
  1032.     }
  1033.  
  1034.     li.parent().children('li.active').removeClass('active');
  1035.     li.addClass('active');
  1036.  
  1037.     osmium_selected_tabs[ul_index] = want;
  1038.  
  1039.     if(window.history && window.history.replaceState) {
  1040.         history.replaceState(history.state, null, '#' + osmium_selected_tabs.join(','));
  1041.     }
  1042.  
  1043.     e.preventDefault();
  1044.     e.stopPropagation();
  1045.     return false;
  1046. };
  1047.  
  1048. osmium_tabify_nohash = function(ul, selected) {
  1049.     ul.find('li > a').on('click', function(e) {
  1050.         var t = $(this);
  1051.         var href = t.attr('href');
  1052.         if(href.substring(0, 1) !== '#') return;
  1053.         var tgt = $(t.attr('href'));
  1054.         var li = t.parent();
  1055.  
  1056.         if(li.hasClass('active')) return false;
  1057.         ul.find('li.active > a').each(function() {
  1058.             var a = $(this);
  1059.             var tgt = $(a.attr('href'));
  1060.             tgt.hide().trigger('made_hidden');
  1061.             a.parent().removeClass('active');
  1062.         });
  1063.  
  1064.         li.addClass('active');
  1065.         tgt.fadeIn(250).trigger('made_visible');
  1066.  
  1067.         t.blur();
  1068.         return false;
  1069.     }).each(function() {
  1070.         var t = $(this);
  1071.         var href = t.attr('href');
  1072.         if(href.substring(0, 1) !== '#') return;
  1073.         $(t.attr('href')).hide();
  1074.     });
  1075.  
  1076.     ul.children('li').eq(selected).children('a').click();
  1077. };
  1078. /* Osmium
  1079.  * Copyright (C) 2012, 2013, 2014 Romain "Artefact2" Dalmaso <artefact2@gmail.com>
  1080.  *
  1081.  * This program is free software: you can redistribute it and/or modify
  1082.  * it under the terms of the GNU Affero General Public License as published by
  1083.  * the Free Software Foundation, either version 3 of the License, or
  1084.  * (at your option) any later version.
  1085.  *
  1086.  * This program is distributed in the hope that it will be useful,
  1087.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  1088.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  1089.  * GNU Affero General Public License for more details.
  1090.  *
  1091.  * You should have received a copy of the GNU Affero General Public License
  1092.  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  1093.  */
  1094.  
  1095. /*<<< require snippet modal >>>*/
  1096. /*<<< require snippet sprite >>>*/
  1097. /*<<< require snippet tabs >>>*/
  1098. /*<<< require snippet loadout_common >>>*/
  1099.  
  1100. osmium_showinfo_sprite_position = [ 6, 58, 16, 16 ];
  1101.  
  1102. osmium_showinfo = function(opts) {
  1103.     osmium_showinfo_internal(opts, function() {
  1104.         /* First error… Try committing CLF and retry once */
  1105.         osmium_commit_clf({
  1106.             success: function() {
  1107.                 osmium_showinfo_internal(opts, function(xhr, error, httperror) {
  1108.                     if(xhr.readyState === 0 || xhr.status === 0) {
  1109.                         return;
  1110.                     }
  1111.  
  1112.                     alert('Could not show info: ' + error + ' (' + httperror
  1113.                           + '). Try refreshing the page and report if the problem persists.');
  1114.                 });
  1115.             }
  1116.         });
  1117.     });
  1118. };
  1119.  
  1120.  
  1121.  
  1122. osmium_showinfo_igb = function(opts) {
  1123.     osmium_showinfo_internal_igb(opts, function() {
  1124.         /* First error… Try committing CLF and retry once */
  1125.         osmium_commit_clf({
  1126.             success: function() {
  1127.                 osmium_showinfo_internal_igb(opts, function(xhr, error, httperror) {
  1128.                     if(xhr.readyState === 0 || xhr.status === 0) {
  1129.                         return;
  1130.                     }
  1131.  
  1132.                     alert('Could not show info: ' + error + ' (' + httperror
  1133.                           + '). Try refreshing the page and report if the problem persists.');
  1134.                 });
  1135.             }
  1136.         });
  1137.     });
  1138. };
  1139.  
  1140.  
  1141.  
  1142. osmium_showinfo_internal_igb = function(opts, onerror) {
  1143.     osmium_clfspinner_push();
  1144.  
  1145.     opts.relative = osmium_relative;
  1146.     opts['o___csrf'] = osmium_token;
  1147.     opts['clftoken'] = osmium_clftoken;
  1148.  
  1149.     $.ajax({
  1150.         type: 'POST',
  1151.         url: osmium_relative + '/internal/showinfo',
  1152.         data: opts,
  1153.         dataType: 'json',
  1154.         error: onerror,
  1155.         complete: function() {
  1156.             osmium_clfspinner_pop();
  1157.         },
  1158.         success: function(json) {
  1159.  
  1160.             var vartypeids = [];
  1161.             for(var i = 0; i < 1; ++i) {
  1162.                 var t = osmium_types[json.variations[i][0]];
  1163.                 CCPEVE.showInfo (t[0]);
  1164.             }
  1165.         }
  1166.     });
  1167. }
  1168.  
  1169.  
  1170.  
  1171. osmium_showinfo_internal = function(opts, onerror) {
  1172.     osmium_clfspinner_push();
  1173.  
  1174.     opts.relative = osmium_relative;
  1175.     opts['o___csrf'] = osmium_token;
  1176.     opts['clftoken'] = osmium_clftoken;
  1177.  
  1178.     $.ajax({
  1179.         type: 'POST',
  1180.         url: osmium_relative + '/internal/showinfo',
  1181.         data: opts,
  1182.         dataType: 'json',
  1183.         error: onerror,
  1184.         complete: function() {
  1185.             osmium_clfspinner_pop();
  1186.         },
  1187.         success: function(json) {
  1188.             osmium_modal(json['modal']);
  1189.             osmium_tabify_nohash($('ul.showinfotabs'), 0);
  1190.  
  1191.             var ul = $("ul.sivariations").last();
  1192.             var vartypeids = [];
  1193.  
  1194.             for(var i = 0; i < json.variations.length; ++i) {
  1195.                 var t = osmium_types[json.variations[i][0]];
  1196.                 var li = $(document.createElement('li'));
  1197.  
  1198.                 li.addClass('module');
  1199.                 li.data('typeid', t[0]);
  1200.                 li.text(t[1]);
  1201.                 li.data('category', t[2]);
  1202.                 li.data('subcategory', t[3]);
  1203.                 vartypeids.push(t[0]);
  1204.  
  1205.                 li.prepend(
  1206.                     $(document.createElement('img'))
  1207.                     .prop('src', '//image.eveonline.com/Type/' + t[0] + '_64.png')
  1208.                     .prop('alt', '')
  1209.                 );
  1210.  
  1211.                 li.append(
  1212.                     $(document.createElement('span'))
  1213.                     .addClass('metalevel')
  1214.                     .text(', meta level ' + json.variations[i][1])
  1215.                 );
  1216.  
  1217.                 if(osmium_loadout_readonly) {
  1218.                     osmium_ctxmenu_bind(li, (function(typeid) {
  1219.                         return function() {
  1220.                             var menu = osmium_ctxmenu_create();
  1221.  
  1222.                             osmium_ctxmenu_add_option(menu, "Show info", function() {
  1223.                                 osmium_showinfo({
  1224.                                     type: 'generic',
  1225.                                     typeid: typeid
  1226.                                 });
  1227.                             }, { icon: osmium_showinfo_sprite_position, 'default': true });
  1228.  
  1229.                            
  1230.                             osmium_ctxmenu_add_option(menu, "Show IGB info", function() {
  1231.                                 osmium_showinfo_igb({
  1232.                                     type: 'generic',
  1233.                                     typeid: typeid
  1234.                                 });
  1235.                             }, { icon: osmium_showinfo_sprite_position, 'default': true });
  1236.  
  1237.                             return menu;
  1238.                         };
  1239.                     })(t[0]));
  1240.                 } else {
  1241.                     osmium_add_non_shortlist_contextmenu(li);
  1242.                 }
  1243.  
  1244.                 ul.append(li);
  1245.             }
  1246.  
  1247.             ul.after($(document.createElement('p')).addClass('compare').append(
  1248.                 $(document.createElement('a'))
  1249.                     .prop('target', '_blank')
  1250.                     .addClass('external')
  1251.                     .prop('href', osmium_relative + '/db/comparetypes/'
  1252.                           + vartypeids.join(',') + '/auto')
  1253.                     .text('Compare these types')
  1254.             ));
  1255.         }
  1256.     });
  1257. }
  1258. /* Osmium
  1259.  * Copyright (C) 2012, 2013, 2014 Romain "Artefact2" Dalmaso <artefact2@gmail.com>
  1260.  *
  1261.  * This program is free software: you can redistribute it and/or modify
  1262.  * it under the terms of the GNU Affero General Public License as published by
  1263.  * the Free Software Foundation, either version 3 of the License, or
  1264.  * (at your option) any later version.
  1265.  *
  1266.  * This program is distributed in the hope that it will be useful,
  1267.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  1268.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  1269.  * GNU Affero General Public License for more details.
  1270.  *
  1271.  * You should have received a copy of the GNU Affero General Public License
  1272.  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  1273.  */
  1274.  
  1275. osmium_ctxmenu_bind = function(element, menu_constructor) {
  1276.     var indicator = $(document.createElement('span'));
  1277.  
  1278.     element.children('.contextmenuindicator').remove();
  1279.     indicator.text('≡');
  1280.     indicator.addClass('contextmenuindicator');
  1281.  
  1282.     element.append(indicator);
  1283.  
  1284.     var showmenu = function(e) {
  1285.         var menu = menu_constructor();
  1286.         menu.prop('id', 'ctxmenu');
  1287.  
  1288.         div = $(document.createElement('div'));
  1289.         div.prop('id', 'ctxbg');
  1290.  
  1291.         div.bind('click contextmenu', function(e2) {
  1292.             menu.click();
  1293.             $(document.elementFromPoint(e2.pageX, e2.pageY)).trigger(e2);
  1294.             return false;
  1295.         });
  1296.  
  1297.         menu.bind('click delete_menu', function() {
  1298.             $("ul#ctxmenu, div#ctxbg, ul.subctxmenu").remove();
  1299.         });
  1300.  
  1301.         $('ul#ctxmenu, div#ctxbg, ul.subctxmenu').remove();
  1302.         $('body').append(div).append(menu);
  1303.  
  1304.         var x = Math.min(e.pageX, $(document).width() - menu.width() - 5);
  1305.         var y = e.pageY;
  1306.         menu.css('left', x);
  1307.         menu.css('top', y);
  1308.  
  1309.         return false;
  1310.     };
  1311.  
  1312.     element.on('dblclick do_default_ctxmenu_action', function() {
  1313.         var menu = menu_constructor();
  1314.         menu.children('li.default').trigger('do_action');
  1315.         return false;
  1316.     });
  1317.  
  1318.     element.on('contextmenu', showmenu);
  1319.     indicator.on('click', showmenu);
  1320.  
  1321.     element.addClass('hascontextmenu');
  1322. };
  1323.  
  1324. osmium_ctxmenu_create = function() {
  1325.     return $(document.createElement('ul'));
  1326. };
  1327.  
  1328. /* opts is a Hashtable that can accept the properties:
  1329.  * - icon: URI of the icon to show
  1330.  * - title: tooltip of this option (uses title attribute)
  1331.  * - enabled: whether this option is enabled or not (default yes); disabled elements cannot be clicked on
  1332.  * - default: whether this option is the default when the element is double-clicked (default false)
  1333.  * - toggled: whether this option is "checked" or "selected"
  1334.  */
  1335. osmium_ctxmenu_add_option = function(menu, name, action, opts) {
  1336.     var li = $(document.createElement('li'));
  1337.  
  1338.     li.append($(document.createElement('span')).text(name));
  1339.     osmium_ctxmenu_apply_opts(menu, li, opts);
  1340.  
  1341.     if(("enabled" in opts) && !opts.enabled) {
  1342.         li.addClass('disabled');
  1343.     } else {
  1344.         li.on('do_action click', function() {
  1345.             action();
  1346.             li.closest('ul#ctxmenu').click();
  1347.         });
  1348.     }
  1349.  
  1350.     if(("default" in opts) && opts['default'] && !li.hasClass('disabled')) {
  1351.         li.addClass('default');
  1352.     }
  1353.  
  1354.     menu.append(li);
  1355. };
  1356.  
  1357. osmium_ctxmenu_add_separator = function(menu) {
  1358.     var li = $(document.createElement('li'));
  1359.     li.addClass('separator');
  1360.     li.text(' ');
  1361.     menu.append(li);
  1362. };
  1363.  
  1364. osmium_ctxmenu_add_heading = function(menu, name, opts) {
  1365.     var li = $(document.createElement('li'));
  1366.  
  1367.     li.addClass('hd');
  1368.     li.addClass('disabled');
  1369.     li.append($(document.createElement('span')).text(name));
  1370.     osmium_ctxmenu_apply_opts(menu, li, opts);
  1371.  
  1372.     menu.append(li);
  1373. };
  1374.  
  1375. /* Same parameters as osmium_ctxmenu_add_option(), but the action is
  1376.  * replaced by a submenu constructor function. */
  1377. osmium_ctxmenu_add_subctxmenu = function(menu, name, submenu_ctor, opts) {
  1378.     var li = $(document.createElement('li'));
  1379.     var timeout_in, timeout_out;
  1380.     var show_submenu;
  1381.  
  1382.     li.append($(document.createElement('span')).text(name));
  1383.     osmium_ctxmenu_apply_opts(menu, li, opts);
  1384.  
  1385.     show_submenu = function() {
  1386.         var submenu = submenu_ctor();
  1387.  
  1388.         submenu.addClass('subctxmenu');
  1389.         if(li.children('ul.subctxmenu').length == 1) return;
  1390.  
  1391.         li.parent().find('ul.subctxmenu').remove();
  1392.         li.append(submenu);
  1393.  
  1394.         var offset = li.offset();
  1395.         var ow = li.outerWidth();
  1396.         var sow = submenu.outerWidth();
  1397.  
  1398.         if(offset.left + ow + sow > $(document).width()) {
  1399.             submenu.offset({
  1400.                 top: offset.top,
  1401.                 left: offset.left - sow
  1402.             });
  1403.         } else {
  1404.             submenu.offset({
  1405.                 top: offset.top,
  1406.                 left: offset.left + ow
  1407.             });
  1408.         }
  1409.     };
  1410.  
  1411.     if(("enabled" in opts) && !opts.enabled) {
  1412.         li.addClass('disabled');
  1413.     } else {
  1414.         li.on('show_submenu', function(e) {
  1415.             show_submenu();
  1416.             e.stopPropagation();
  1417.             return false;
  1418.         }).on('hide_submenu', function(e) {
  1419.             li.children('ul.subctxmenu').remove();
  1420.             e.stopPropagation();
  1421.             return false;
  1422.         }).on('click', function(e) {
  1423.             var def = li.children('ul.subctxmenu').children('li.default');
  1424.             if(def.length === 1) {
  1425.                 def.trigger('do_action');
  1426.                 return;
  1427.             }
  1428.  
  1429.             li.trigger('show_submenu');
  1430.             e.stopPropagation();
  1431.             return false;
  1432.         }).on('mouseenter', function(e) {
  1433.             clearTimeout(timeout_out);
  1434.             timeout_in = setTimeout(function() {
  1435.                 li.trigger('show_submenu');
  1436.             }, 100);
  1437.         }).on('mouseleave', function(e) {
  1438.             clearTimeout(timeout_in);
  1439.             timeout_out = setTimeout(function() {
  1440.                 li.trigger('hide_submenu');
  1441.             }, 250);
  1442.         });
  1443.     }
  1444.  
  1445.     li.addClass('hassubcontextmenu');
  1446.     menu.append(li);
  1447. };
  1448.  
  1449. /* @internal */
  1450. osmium_ctxmenu_apply_opts = function(menu, li, opts) {
  1451.     if(opts === undefined) opts = {};
  1452.    
  1453.     if("title" in opts) {
  1454.         li.prop('title', opts.title);
  1455.     }
  1456.  
  1457.     if("icon" in opts && typeof opts.icon === "string") {
  1458.         var img = $(document.createElement('img'));
  1459.         img.prop('alt', '');
  1460.         if(opts.icon.substring(0, 2) === '//') {
  1461.             /* Absolute URI of type //foo.tld/path.png */
  1462.             img.prop('src', opts.icon);
  1463.         } else {
  1464.             /* Relative URI */
  1465.             img.prop('src', osmium_relative + '/static-' + osmium_staticver + '/icons/' + opts.icon);
  1466.         }
  1467.         img.addClass('icon');
  1468.         li.prepend(img);
  1469.         li.addClass('hasicon');
  1470.     } else if("icon" in opts && typeof opts.icon === "object") {
  1471.         li.prepend(osmium_sprite(
  1472.             '',
  1473.             opts.icon[0],
  1474.             opts.icon[1],
  1475.             opts.icon[2],
  1476.             opts.icon[3],
  1477.             16, 16
  1478.         ));
  1479.         li.addClass('hasicon');
  1480.     }
  1481.  
  1482.     if("toggled" in opts) {
  1483.         var c = $(document.createElement('input'));
  1484.         c.prop('type', 'checkbox');
  1485.         c.prop('checked', opts.toggled);
  1486.         li.prepend(c);
  1487.         li.addClass('hastoggle');
  1488.  
  1489.         if(opts.toggled) {
  1490.             li.addClass('toggled');
  1491.         }
  1492.     }
  1493. };
  1494. /* Osmium
  1495.  * Copyright (C) 2012, 2014 Romain "Artefact2" Dalmaso <artefact2@gmail.com>
  1496.  *
  1497.  * This program is free software: you can redistribute it and/or modify
  1498.  * it under the terms of the GNU Affero General Public License as published by
  1499.  * the Free Software Foundation, either version 3 of the License, or
  1500.  * (at your option) any later version.
  1501.  *
  1502.  * This program is distributed in the hope that it will be useful,
  1503.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  1504.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  1505.  * GNU Affero General Public License for more details.
  1506.  *
  1507.  * You should have received a copy of the GNU Affero General Public License
  1508.  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  1509.  */
  1510.  
  1511. osmium_fattribs_load = function() {
  1512.     var hidden = $('div#osmium-data').data('fattribshidden');
  1513.     if(!hidden) return;
  1514.  
  1515.     var hc = hidden.length;
  1516.     var s = $("div#computed_attributes");
  1517.     for(var i = 0; i < hc; ++i) s.children("section#" + hidden[i]).addClass('hidden').children('div').hide();
  1518. };
  1519.  
  1520. osmium_fattribs_toggle = function(section) {
  1521.     if(section.hasClass('hidden')) {
  1522.         section.children('div').fadeIn(250);
  1523.         section.removeClass('hidden');
  1524.     } else {
  1525.         section.children('div').fadeOut(250);
  1526.         section.addClass('hidden');
  1527.     }
  1528.  
  1529.     var hidden = [];
  1530.  
  1531.     $("div#computed_attributes > section.hidden").each(function() {
  1532.         hidden.push($(this).prop('id'));
  1533.     });
  1534.  
  1535.     osmium_put_setting('fattribs_hidden', hidden);
  1536. };
  1537.  
  1538. $(function() {
  1539.     $(document).on('click', "div#computed_attributes > section > h4", function() {
  1540.         osmium_fattribs_toggle($(this).parent());
  1541.     });
  1542.  
  1543.     osmium_fattribs_load();
  1544. });
  1545. /* Osmium
  1546.  * Copyright (C) 2013 Romain "Artefact2" Dalmaso <artefact2@gmail.com>
  1547.  *
  1548.  * This program is free software: you can redistribute it and/or modify
  1549.  * it under the terms of the GNU Affero General Public License as published by
  1550.  * the Free Software Foundation, either version 3 of the License, or
  1551.  * (at your option) any later version.
  1552.  *
  1553.  * This program is distributed in the hope that it will be useful,
  1554.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  1555.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  1556.  * GNU Affero General Public License for more details.
  1557.  *
  1558.  * You should have received a copy of the GNU Affero General Public License
  1559.  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  1560.  */
  1561.  
  1562. /**
  1563.  * Try to auto-guess "best" initial values. Returns an array of three
  1564.  * values [ tsrOptimal, tvOptimal, tdOptimal ].
  1565.  */
  1566. osmium_probe_optimals_from_ia = function(ia) {
  1567.     var tsrc = 0, tsrs = 0, tvc = 0, tvs = 0, tdc = 0, tds = 0;
  1568.  
  1569.     for(var i = 0; i < ia.length; ++i) {
  1570.         var a = ia[i].raw;
  1571.  
  1572.         if(!("damagetype" in a)) continue;
  1573.  
  1574.         if("sigradius" in a) {
  1575.             ++tsrc;
  1576.             tsrs += a.sigradius;
  1577.         }
  1578.  
  1579.         if("expradius" in a) {
  1580.             ++tsrc;
  1581.             tsrs += a.expradius;
  1582.         }
  1583.  
  1584.         if(a.damagetype === "turret" && "range" in a) {
  1585.             ++tdc;
  1586.             tds += a.range / 1000.0;
  1587.         }
  1588.     }
  1589.  
  1590.     return [
  1591.         tsrc === 0 ? 250 : Math.round(tsrs / tsrc),
  1592.         tvc === 0 ? 0 : Math.round(tvs / tvc),
  1593.         tdc === 0 ? 1 : Math.round(tds / tdc),
  1594.     ];
  1595. };
  1596.  
  1597. /**
  1598.  * Try to auto-guess graph boundaries with some given
  1599.  * constraints. Parameters tsr, tv, td can be filled or left
  1600.  * out. Returns an array of three values [ tsrMax, tvMax, tdMax ].
  1601.  */
  1602. osmium_probe_boundaries_from_ia = function(ia, tsr, tv, td) {
  1603.     var tsrmax = 50, tvmax = 50, tdmax = 5000;
  1604.     var a;
  1605.  
  1606.     if(isNaN(td)) {
  1607.         for(var j = 0; j < ia.length; ++j) {
  1608.             a = ia[j].raw;
  1609.             if(!("damagetype" in a)) continue;
  1610.  
  1611.             var m = Math.min(
  1612.                 ("range" in a && "falloff" in a) ? (a.range + 3 * a.falloff) : Infinity,
  1613.                 ("maxrange" in a) ? (a.maxrange * 1.1) : Infinity,
  1614.                 ("controlrange" in a) ? (a.controlrange * 1.1) : Infinity
  1615.             );
  1616.  
  1617.             if(isFinite(m)) tdmax = Math.max(tdmax, m);
  1618.         }
  1619.  
  1620.         tdmax /= 1000;
  1621.     } else {
  1622.         tdmax = td;
  1623.     }
  1624.  
  1625.     if(isNaN(tsr)) {
  1626.         for(var j = 0; j < ia.length; ++j) {
  1627.             a = ia[j].raw;
  1628.             if(!("damagetype" in a)) continue;
  1629.  
  1630.             if("sigradius" in a) {
  1631.                 tsrmax = Math.max(tsrmax, a.sigradius * 3);
  1632.                 continue;
  1633.             }
  1634.  
  1635.             if("expradius" in a) {
  1636.                 tsrmax = Math.max(tsrmax, a.expradius * 3);
  1637.                 continue;
  1638.             }
  1639.         }
  1640.     } else {
  1641.         tsrmax = tsr;
  1642.     }
  1643.  
  1644.     if(isNaN(tv)) {
  1645.         for(var j = 0; j < ia.length; ++j) {
  1646.             a = ia[j].raw;
  1647.             if(!("damagetype" in a)) continue;
  1648.  
  1649.             if("trackingspeed" in a && "range" in a && "falloff" in a) {
  1650.                 if(a.damagetype === "combatdrone" && a.maxvelocity > 0) {
  1651.                     continue;
  1652.                 }
  1653.  
  1654.                 tvmax = Math.max(
  1655.                     tvmax,
  1656.                     Math.min(a.damagetype === "combatdrone" ? 2500 : 12000,
  1657.                              (a.range + a.falloff) * a.trackingspeed)
  1658.                 );
  1659.                 continue;
  1660.             }
  1661.  
  1662.             if("expvelocity" in a) {
  1663.                 tvmax = Math.max(tvmax, a.expvelocity * 8);
  1664.                 continue;
  1665.             }
  1666.         }
  1667.     } else {
  1668.         tvmax = tv;
  1669.     }
  1670.  
  1671.     return [ tsrmax, tvmax, tdmax ];
  1672. };
  1673.  
  1674. /**
  1675.  * Return a color from a parameter between 0 and 1. The color is red
  1676.  * for 1, and will smoothly go through all the color spectrum to reach
  1677.  * transparent-ish purple at zero.
  1678.  */
  1679. osmium_heat_color = function(t) {
  1680.     return "hsla("
  1681.         + Math.round((1 - t) * 360).toString()
  1682.         + ", 100%, 50%, "
  1683.         + Math.min(1, t).toFixed(2)
  1684.         + ")";
  1685. };
  1686.  
  1687. /**
  1688.  * Generate labels and append them next to a canvas element.
  1689.  *
  1690.  * @param ctx the root element to add the labels to
  1691.  * @param canvas the canvas element
  1692.  * @param xlabel text to label the X axis with
  1693.  * @param ylabel text to label the Y axis with
  1694.  */
  1695. osmium_graph_gen_labels = function(ctx, canvas, xlabel, ylabel) {
  1696.     var xl, yl;
  1697.     ctx.append(xl = $(document.createElement('span')).addClass('xlabel').text(xlabel));
  1698.     ctx.append(yl = $(document.createElement('span')).addClass('ylabel').text(ylabel));
  1699.  
  1700.     var cpos = canvas.offset();
  1701.  
  1702.     xl.offset({
  1703.         top: cpos.top + canvas.height() + 4,
  1704.         left: cpos.left + canvas.width() / 2 - xl.width() / 2
  1705.     });
  1706.  
  1707.     /* Rotating first gives different results on Chromium/Firefox */
  1708.     yl.offset({
  1709.         top: cpos.top + canvas.height() / 2 - yl.height() / 2,
  1710.         left: cpos.left - yl.width() / 2 - yl.height() / 2 - 4
  1711.     }).addClass('rotated');
  1712. };
  1713.  
  1714. /**
  1715.  * Draw a labeled grid using a given canvas context.
  1716.  *
  1717.  * @param cctx the canvas context to draw with
  1718.  * @param cw canvas width
  1719.  * @param ch canvas height
  1720.  * @param xmin minimum value for X axis
  1721.  * @param xmax maximum value for X axis
  1722.  * @param xsteps minimum number of vertical guides to draw
  1723.  * @param ymin minimum value for Y axis
  1724.  * @param ymax maximum value for Y axis
  1725.  * @param ysteps minimum value of horizontal guides to draw
  1726.  * @param axisopacity opacity (between 0 and 1) of the drawn guides
  1727.  * @param labelopacity opacity (between 0 and 1) of the drawn labels
  1728.  */
  1729. osmium_graph_draw_grid = function(cctx, cw, ch, xmin, xmax, xsteps, ymin, ymax, ysteps, axisopacity, labelopacity) {
  1730.     var steps = [ 50000, 20000, 10000,
  1731.                   5000, 2000, 1000,
  1732.                   500, 200, 100,
  1733.                   50, 20, 10,
  1734.                   5, 2, 1,
  1735.                   .5, .2, .1,
  1736.                   .05, .02, .01,
  1737.                   .005, .002, .001,
  1738.                   .0005, .0002, .0001 ];
  1739.  
  1740.     var xstep = 1, ystep = 1;
  1741.     for(var i = 0; i < steps.length; ++i) {
  1742.         if((xmax - xmin) / steps[i] >= xsteps) {
  1743.             xstep = steps[i];
  1744.             break;
  1745.         }
  1746.     }
  1747.     for(var i = 0; i < steps.length; ++i) {
  1748.         if((ymax - ymin) / steps[i] >= ysteps) {
  1749.             ystep = steps[i];
  1750.             break;
  1751.         }
  1752.     }
  1753.  
  1754.     cctx.beginPath();
  1755.     cctx.font = '0.8em "Droid Sans"';
  1756.     cctx.fillStyle = "hsla(0, 0%, 50%, " + labelopacity.toString() + ")";
  1757.  
  1758.     cctx.textAlign = "center";
  1759.     cctx.textBaseline = "bottom";
  1760.     for(var x = Math.ceil(xmin / xstep) * xstep; x < xmax; x += xstep) {
  1761.         if(x === xmin) continue;
  1762.  
  1763.         var xc = Math.floor(cw * (x - xmin) / (xmax - xmin)) + 0.5;
  1764.         cctx.moveTo(xc, 0.5);
  1765.         cctx.lineTo(xc, ch - 0.5);
  1766.         cctx.fillText(x.toString(), xc, ch - 0.5);
  1767.     }
  1768.  
  1769.     cctx.textAlign = "left";
  1770.     cctx.textBaseline = "middle";
  1771.     for(var y = Math.ceil(ymin / ystep) * ystep; y < ymax; y += ystep) {
  1772.         if(y === ymin) continue;
  1773.  
  1774.         var yc = Math.floor(ch * (y - ymin) / (ymax - ymin)) + 0.5;
  1775.         cctx.moveTo(0.5, ch - yc);
  1776.         cctx.lineTo(cw - 0.5, ch - yc);
  1777.         cctx.fillText(y.toString(), 2.5, ch - yc);
  1778.     }
  1779.  
  1780.     cctx.strokeStyle = "hsla(0, 0%, 50%, " + axisopacity.toString() + ")";
  1781.     cctx.stroke();
  1782. };
  1783.  
  1784. /**
  1785.  * Get the average DPS of a turret-like weapon.
  1786.  *
  1787.  * http://wiki.eveuniversity.org/Turret_Damage
  1788.  *
  1789.  * @param dps the raw DPS of the turret
  1790.  * @param trackingspeed tracking speed of the turret, in rad/s
  1791.  * @param sigresolution signature resolution of the turret, in meters
  1792.  * @param range optimal range of the turret, in meters
  1793.  * @param falloff falloff range of the turret, in meters
  1794.  * @param tsr target signature radius, in meters
  1795.  * @param tv target velocity, in m/s
  1796.  * @param td target distance, in km
  1797.  */
  1798. osmium_turret_damage_f = function(dps, trackingspeed, sigresolution, range, falloff, tsr, tv, td) {
  1799.     if(tv == 0 && td == 0) td = .001;
  1800.     if(tsr == 0) return 0;
  1801.  
  1802.     var cth = Math.pow(
  1803.         0.5,
  1804.         Math.pow(
  1805.             ((tv / (1000 * td)) / trackingspeed) * (sigresolution / tsr),
  1806.             2
  1807.         ) + Math.pow(
  1808.             Math.max(0, (1000 * td) - range) / falloff,
  1809.             2
  1810.         )
  1811.     );
  1812.  
  1813.     return (
  1814.         Math.min(cth, 0.01) * 3 + Math.max(cth - 0.01, 0) * (0.49 + (cth + 0.01) / 2)
  1815.     ) * dps;
  1816. };
  1817.  
  1818. /**
  1819.  * Get the average DPS of a missile-like weapon.
  1820.  *
  1821.  * http://wiki.eveuniversity.org/Missile_Damage
  1822.  *
  1823.  * @param dps the raw DPS of the missile launcher
  1824.  * @param maxrange the maximum range of the missile
  1825.  * @param expradius explosion radius of the missile
  1826.  * @param expvelocity explosion velocity of the missile
  1827.  * @param drf damage reduction factor
  1828.  * @param drs damage reduction sensitivity
  1829.  * @param tsr target signature radius, in meters
  1830.  * @param tv target velocity, in m/s
  1831.  * @param td target distance, in km
  1832.  */
  1833. osmium_missile_damage_f = function(dps, maxrange, expradius, expvelocity, drf, drs, tsr, tv, td) {
  1834.     if(1000 * td > maxrange || dps == 0) return 0;
  1835.  
  1836.     return dps * Math.min(
  1837.         1,
  1838.         tsr / expradius,
  1839.         (tsr != 0 && expvelocity != 0) ?
  1840.             Math.pow((tsr / expradius) * (expvelocity / tv), Math.log(drf) / Math.log(drs))
  1841.             : 0
  1842.     );
  1843. };
  1844.  
  1845. /** Get the average DPS of a fitted type. */
  1846. osmium_get_dps_from_type_internal = function(a, tsr, tv, td) {
  1847.     if(!("damagetype" in a)) return 0;
  1848.  
  1849.     if(a.damagetype === "combatdrone" || a.damagetype === "fighter" || a.damagetype === "fighterbomber") {
  1850.         if(a.damagetype === "combatdrone" && "controlrange" in a && 1000 * td > a.controlrange) return 0;
  1851.  
  1852.         if(a.maxvelocity == 0) {
  1853.             /* Sentry drone */
  1854.             return osmium_turret_damage_f(
  1855.                 a.damage / a.duration,
  1856.                 a.trackingspeed, a.sigradius, a.range, a.falloff,
  1857.                 tsr, tv, td
  1858.             );
  1859.         }
  1860.  
  1861.         /* XXX: this is a very simplistic model, totally inaccurate
  1862.          * guesswork. Critique & improvements most welcomed! */
  1863.  
  1864.         /* Drone tries to keep orbit at flyrange m @ cruisespeed m/s */
  1865.         /* After a full cycle, assume the drone will use MWD to
  1866.          * reenter orbit distance */
  1867.         var ddur = a.duration;
  1868.  
  1869.         if(tv > a.cruisespeed) {
  1870.             if(tv >= a.maxvelocity) {
  1871.                 /* Drone will never catch up */
  1872.                 ddur = Infinity;
  1873.             } else {
  1874.                 ddur += (tv - a.cruisespeed) * a.duration / (a.maxvelocity - tv);
  1875.             }
  1876.         }
  1877.  
  1878.         if(a.damagetype === "fighterbomber") {
  1879.             return osmium_missile_damage_f(
  1880.                 a.damage / ddur,
  1881.                 a.maxrange, a.expradius, a.expvelocity, a.drf, a.drs,
  1882.                 tsr, tv, a.flyrange / 1000.0
  1883.             );
  1884.         }
  1885.  
  1886.         return osmium_turret_damage_f(
  1887.             a.damage / ddur,
  1888.             a.trackingspeed, a.sigradius, a.range, a.falloff,
  1889.             tsr, a.cruisespeed, a.flyrange / 1000.0
  1890.         );
  1891.     }
  1892.  
  1893.     if(a.damagetype === "turret") {
  1894.         return osmium_turret_damage_f(
  1895.             a.damage / a.duration,
  1896.             a.trackingspeed, a.sigradius, a.range, a.falloff,
  1897.             tsr, tv, td
  1898.         );
  1899.     }
  1900.  
  1901.     if(a.damagetype === "missile") {
  1902.         return osmium_missile_damage_f(
  1903.             a.damage / a.duration,
  1904.             a.maxrange, a.expradius, a.expvelocity, a.drf, a.drs,
  1905.             tsr, tv, td
  1906.         );
  1907.     }
  1908.  
  1909.     if(a.damagetype === "smartbomb") {
  1910.         if(1000 * td > a.maxrange) return 0;
  1911.         return a.damage / a.duration;
  1912.     }
  1913.  
  1914.     return 0;
  1915. };
  1916.  
  1917. /** @internal */
  1918. osmium_get_dps_internal = function(ia, args) {
  1919.     var dps = 0;
  1920.     for(var j = 0; j < ia.length; ++j) {
  1921.         dps += osmium_get_dps_from_type_internal(ia[j].raw, args[0], args[1], args[2]);
  1922.     }
  1923.     return 1000 * dps;
  1924. };
  1925.  
  1926. /**
  1927.  * Draw a line graph for every set of attributes in ia_map, using colors in color_map.
  1928.  *
  1929.  * @param ctx the root element to append the canvas and labels into
  1930.  * @param xlabel the X label
  1931.  * @param xmin the minimum X value
  1932.  * @param xmax the maximum X value
  1933.  * @param genfunc_x a function which takes the X coordinate and returns an array [ tsr, tv, td ]
  1934.  * @param dpsmin the minimum Y value
  1935.  * @param dpsmax the maximum Y value, leave null to autodetect
  1936.  */
  1937. osmium_draw_dps_graph_1d = function(ia_map, color_map, ctx,
  1938.                                     xlabel, xmin, xmax, genfunc_x, dpsmin, dpsmax) {
  1939.     ctx.empty();
  1940.  
  1941.     var canvas = document.createElement('canvas');
  1942.     var cctx = canvas.getContext('2d');
  1943.     var cw, ch;
  1944.     canvas = $(canvas);
  1945.     ctx.append($(document.createElement('div')).addClass('cctx').append(canvas));
  1946.     canvas.attr('width', cw = canvas.width());
  1947.     canvas.attr('height', ch = canvas.height());
  1948.  
  1949.     osmium_graph_gen_labels(ctx, canvas, xlabel, "Damage per second");
  1950.  
  1951.     var x, dps, px, py;
  1952.  
  1953.     if(!dpsmax) {
  1954.         dpsmax = 10;
  1955.  
  1956.         for(var i = 0; i <= cw; ++i) {
  1957.             x = xmin + (i / cw) * (xmax - xmin);
  1958.  
  1959.             for(var k in ia_map) {
  1960.                 if(!("ia" in ia_map[k])) continue;
  1961.                 dpsmax = Math.max(dpsmax, osmium_get_dps_internal(ia_map[k].ia, genfunc_x(x)));
  1962.             }
  1963.         }
  1964.  
  1965.         dpsmax *= 1.05;
  1966.     }
  1967.  
  1968.     osmium_graph_draw_grid(cctx, cw, ch, xmin, xmax, 8, dpsmin, dpsmax, 4, 0.15, 0.5);
  1969.  
  1970.     for(var k in ia_map) {
  1971.         if(!("ia" in ia_map[k])) continue;
  1972.         cctx.beginPath();
  1973.         cctx.moveTo(0, 0);
  1974.  
  1975.         for(var i = 0; i <= cw; ++i) {
  1976.             x = xmin + (i / cw) * (xmax - xmin);
  1977.             dps = osmium_get_dps_internal(ia_map[k].ia, genfunc_x(x));
  1978.             px = i + 0.5;
  1979.             py = Math.floor(ch * (1 - (dps - dpsmin) / (dpsmax - dpsmin))) + 0.5;
  1980.  
  1981.             if(i === 0) {
  1982.                 cctx.moveTo(px, py);
  1983.             } else {
  1984.                 cctx.lineTo(px, py);
  1985.             }
  1986.         }
  1987.  
  1988.         cctx.strokeStyle = color_map[k];
  1989.         cctx.lineWidth = 3;
  1990.         cctx.stroke();
  1991.     }
  1992. };
  1993.  
  1994. /**
  1995.  * Draw a 2d graph. Most parameters are similar to the 1d version.
  1996.  *
  1997.  * @param genfunc_xy a function that takes two parameters (the X,Y
  1998.  * coordinates) and returns an array [ tsr, tv, td ].
  1999.  *
  2000.  * @param cololfunc a function that takes one parameter, a map of
  2001.  * array [ DPS, MaxDPS ] values and returns a color.
  2002.  *
  2003.  * @param pixelsize the size of the rectangles drawn on the
  2004.  * graph. Higher values means less rectangles to draw, but results in
  2005.  * a blockier graph.
  2006.  *
  2007.  * @returns global maximum dps
  2008.  */
  2009. osmium_draw_dps_graph_2d = function(ia_map, colorfunc, ctx,
  2010.                                     xlabel, xmin, xmax, ylabel, ymin, ymax,
  2011.                                     genfunc_xy, pixelsize) {
  2012.     ctx.empty();
  2013.  
  2014.     var canvas = document.createElement('canvas');
  2015.     var cctx = canvas.getContext('2d');
  2016.     var cw, ch;
  2017.     canvas = $(canvas);
  2018.     ctx.append($(document.createElement('div')).addClass('cctx').append(canvas));
  2019.     canvas.attr('width', cw = canvas.width());
  2020.     canvas.attr('height', ch = canvas.height());
  2021.  
  2022.     osmium_graph_gen_labels(ctx, canvas, xlabel, ylabel);
  2023.  
  2024.     cctx.moveTo(0, ch);
  2025.  
  2026.     var x, y, px, py, localmax = {}, globalmax = 10, hps = pixelsize / 2;
  2027.  
  2028.     for(var k in ia_map) {
  2029.         if(!("ia" in ia_map[k])) continue;
  2030.         var ia = ia_map[k].ia;
  2031.         localmax[k] = 0;
  2032.    
  2033.         for(var i = 0; i <= cw; i += pixelsize) {
  2034.             x = xmin + ((i + hps) / cw) * (xmax - xmin);
  2035.  
  2036.             for(var j = 0; j <= ch; j += pixelsize) {
  2037.                 y = ymin + ((j + hps) / ch) * (ymax - ymin);
  2038.                 localmax[k] = Math.max(localmax[k], osmium_get_dps_internal(ia, genfunc_xy(x, y)));
  2039.             }
  2040.         }
  2041.  
  2042.         globalmax = Math.max(localmax[k], globalmax);
  2043.     }
  2044.  
  2045.     var dps = {};
  2046.  
  2047.     for(var k in ia_map) {
  2048.         dps[k] = [ 0, Math.max(1, localmax[k]) ];
  2049.     }
  2050.  
  2051.     for(var i = 0; i <= cw; i += pixelsize) {
  2052.         x = xmin + ((i + hps) / cw) * (xmax - xmin);
  2053.  
  2054.         for(var j = 0; j <= ch; j += pixelsize) {
  2055.             y = ymin + ((j + hps) / ch) * (ymax - ymin);
  2056.  
  2057.             for(var k in ia_map) {
  2058.                 if(!("ia" in ia_map[k])) continue;
  2059.  
  2060.                 dps[k][0] = osmium_get_dps_internal(ia_map[k].ia, genfunc_xy(x, y));
  2061.             }
  2062.  
  2063.             cctx.fillStyle = colorfunc(dps);
  2064.             cctx.fillRect(i, ch - j, pixelsize, pixelsize);
  2065.         }
  2066.     }
  2067.  
  2068.     osmium_graph_draw_grid(cctx, cw, ch, xmin, xmax, 8, ymin, ymax, 4, 0.15, 0.75);
  2069.  
  2070.     return globalmax;
  2071. };
  2072.  
  2073. /** Draw a legend for colored 2d graphs. */
  2074. osmium_draw_dps_legend = function(ctx, maxdps, heatfunc) {
  2075.     ctx.find('div.cctx').addClass('twodim');
  2076.  
  2077.     var lcanvas = document.createElement('canvas');
  2078.     var lctx = lcanvas.getContext('2d');
  2079.     var lw, lh;
  2080.     lcanvas = $(lcanvas);
  2081.     ctx.append($(document.createElement('div')).addClass('legend').append(lcanvas));
  2082.     lcanvas.attr('width', lw = lcanvas.width());
  2083.     lcanvas.attr('height', lh = lcanvas.height());
  2084.  
  2085.     for(var i = 0; i <= lh; ++i) {
  2086.         lctx.fillStyle = heatfunc(i / lh);
  2087.         lctx.fillRect(0, lh - i, 100, 1);
  2088.     }
  2089.  
  2090.     var dlabel = $(document.createElement('span')).text('DPS');
  2091.     ctx.append(dlabel);
  2092.     var lpos = lcanvas.parent().offset();
  2093.     dlabel.offset({
  2094.         top: lpos.top + lcanvas.parent().height() + 5,
  2095.         left: lpos.left + lcanvas.parent().width() / 2 - dlabel.width() / 2
  2096.     });
  2097.  
  2098.     lpos = lcanvas.offset();
  2099.     var nlabels = 6;
  2100.     for(var i = 0; i <= nlabels; ++i) {
  2101.         dlabel = $(document.createElement('span')).addClass('dpslabel')
  2102.             .text(Math.round((i / nlabels) * maxdps).toString());
  2103.         ctx.append(dlabel);
  2104.         dlabel.offset({
  2105.             top: Math.min(
  2106.                 Math.max(
  2107.                     lpos.top + lcanvas.height() * (1 - i / nlabels) - dlabel.height() / 2,
  2108.                     lpos.top
  2109.                 ),
  2110.                 lpos.top + lcanvas.height() - dlabel.height()
  2111.             ),
  2112.             left: lpos.left - dlabel.width() - 4
  2113.         });
  2114.     }
  2115. };
  2116. /* Osmium
  2117.  * Copyright (C) 2013 Romain "Artefact2" Dalmaso <artefact2@gmail.com>
  2118.  *
  2119.  * This program is free software: you can redistribute it and/or modify
  2120.  * it under the terms of the GNU Affero General Public License as published by
  2121.  * the Free Software Foundation, either version 3 of the License, or
  2122.  * (at your option) any later version.
  2123.  *
  2124.  * This program is distributed in the hope that it will be useful,
  2125.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  2126.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  2127.  * GNU Affero General Public License for more details.
  2128.  *
  2129.  * You should have received a copy of the GNU Affero General Public License
  2130.  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  2131.  */
  2132.  
  2133. osmium_gen_capacitor = function(capacity, current) {
  2134.     if(capacity === null) capacity = 0;
  2135.     if(current === null) current = 0;
  2136.  
  2137.     var nbranches = Math.min(10, Math.max(2, Math.floor(capacity / 50.0)));
  2138.     var nbubbles = 3;
  2139.     var bubblecapacity = (capacity > 0) ? (capacity / (nbubbles * nbranches)) : 0;
  2140.     /* Avoid using the same IDs on different <svg> elements in the
  2141.      * same page */
  2142.     var idsuffix = Math.random().toFixed(20).substring(2);
  2143.  
  2144.     var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
  2145.     svg.setAttribute('viewBox', '-1 -1 2 2');
  2146.     svg.setAttribute('class', 'capacitorwheel');
  2147.  
  2148.     var defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
  2149.  
  2150.     for(var t in { full: 1, empty: 1 }) {
  2151.         var lg = document.createElementNS('http://www.w3.org/2000/svg', 'linearGradient');
  2152.         lg.setAttribute('id', 'gradient-' + t + '-' + idsuffix);
  2153.         lg.setAttribute('class', t);
  2154.  
  2155.         var stop;
  2156.  
  2157.         stop = document.createElementNS('http://www.w3.org/2000/svg', 'stop');
  2158.         stop.setAttribute('offset', '0%');
  2159.         lg.appendChild(stop);
  2160.  
  2161.         stop = document.createElementNS('http://www.w3.org/2000/svg', 'stop');
  2162.         stop.setAttribute('offset', '33%');
  2163.         lg.appendChild(stop);
  2164.  
  2165.         stop = document.createElementNS('http://www.w3.org/2000/svg', 'stop');
  2166.         stop.setAttribute('offset', '67%');
  2167.         lg.appendChild(stop);
  2168.  
  2169.         stop = document.createElementNS('http://www.w3.org/2000/svg', 'stop');
  2170.         stop.setAttribute('offset', '100%');
  2171.         lg.appendChild(stop);
  2172.  
  2173.         defs.appendChild(lg);
  2174.     }
  2175.  
  2176.     svg.appendChild(defs);
  2177.  
  2178.     var progress = 0;
  2179.     for(var i = 0; i < nbranches; ++i) {
  2180.         var angle = -360 * i / nbranches;
  2181.         var g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
  2182.         g.setAttribute('transform', 'rotate(' + angle + ' 0 0)');
  2183.  
  2184.         for(var j = 0; j < nbubbles; ++j) {
  2185.             var bubble = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
  2186.             var w = 0.2 + j * 0.12;
  2187.             bubble.setAttribute('width', w + '');
  2188.             bubble.setAttribute('height', '0.15');
  2189.             bubble.setAttribute('x', (-w / 2) + '');
  2190.             bubble.setAttribute('y', (-0.9 + 0.8 * (nbubbles - j - 1) / nbubbles) + '');
  2191.             bubble.setAttribute('rx', '0.075');
  2192.             bubble.setAttribute('ry', '0.075');
  2193.             bubble.setAttribute(
  2194.                 'fill',
  2195.                 (progress < current) ?
  2196.                     ('url(#gradient-full-' + idsuffix + ')')
  2197.                     : ('url(#gradient-empty-' + idsuffix + ')')
  2198.             );
  2199.             g.appendChild(bubble);
  2200.  
  2201.             progress += bubblecapacity;
  2202.         }
  2203.  
  2204.         svg.appendChild(g);
  2205.     }
  2206.  
  2207.     var g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
  2208.     svg.appendChild(g);
  2209.  
  2210.     var title = document.createElementNS('http://www.w3.org/2000/svg', 'title');
  2211.     title.appendChild(document.createTextNode('Capacity: ' + capacity.toFixed(0) + ' GJ'));
  2212.     g.setAttribute('class', 'overlay');
  2213.     g.appendChild(title);
  2214.  
  2215.     var r = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
  2216.     r.setAttribute('x', '-1');
  2217.     r.setAttribute('y', '-1');
  2218.     r.setAttribute('width', '2');
  2219.     r.setAttribute('height', '2');
  2220.     g.appendChild(r);
  2221.  
  2222.     svg = $(svg);
  2223.  
  2224.     svg.data('capacity', capacity);
  2225.     svg.data('current', current);
  2226.  
  2227.     svg.on('redraw', function() {
  2228.         var t = $(this);
  2229.         t.replaceWith(
  2230.             osmium_gen_capacitor(t.data('capacity'), t.data('current'))
  2231.         );
  2232.     });
  2233.  
  2234.     return svg;
  2235. };
  2236. /* Osmium
  2237.  * Copyright (C) 2012, 2013, 2014 Romain "Artefact2" Dalmaso <artefact2@gmail.com>
  2238.  *
  2239.  * This program is free software: you can redistribute it and/or modify
  2240.  * it under the terms of the GNU Affero General Public License as published by
  2241.  * the Free Software Foundation, either version 3 of the License, or
  2242.  * (at your option) any later version.
  2243.  *
  2244.  * This program is distributed in the hope that it will be useful,
  2245.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  2246.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  2247.  * GNU Affero General Public License for more details.
  2248.  *
  2249.  * You should have received a copy of the GNU Affero General Public License
  2250.  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  2251.  */
  2252.  
  2253. osmium_undo_stack = [];
  2254. osmium_undo_stack_position = 0;
  2255. osmium_pushed_state_count = 0;
  2256. osmium_popstate_disablefor = 0;
  2257.  
  2258.  
  2259.  
  2260. /* Push the current CLF to the undo history. */
  2261. osmium_undo_push = function() {
  2262.     osmium_undo_stack.push($.extend(true, {}, osmium_clf));
  2263.     osmium_undo_stack_position = osmium_undo_stack.length - 1;
  2264.     osmium_set_history_undo();
  2265. };
  2266.  
  2267. /* Restore the previous CLF in the undo history. Negative values also
  2268.  * supported, in which case, use fallback_clf if going too far in the
  2269.  * future. */
  2270. osmium_undo_pop = function(nsteps, fallback_clf) {
  2271.     if(nsteps === undefined) nsteps = 1;
  2272.  
  2273.     if(osmium_undo_stack_position < nsteps) {
  2274.         /* No more history to undo */
  2275.         return false;
  2276.     }
  2277.  
  2278.     /*  Very similar to the "undo" feature of Emacs. Powerful and
  2279.      *  cannot lose data by undoing stuff then doing modifications. */
  2280.  
  2281.     osmium_undo_stack_position -= nsteps;
  2282.  
  2283.     if(fallback_clf !== undefined) {
  2284.         osmium_undo_stack[osmium_undo_stack_position] = $.extend(true, {}, fallback_clf);
  2285.     }
  2286.  
  2287.     var clf = osmium_undo_stack[osmium_undo_stack_position];
  2288.     if(clf === undefined) {
  2289.         return false;
  2290.     }
  2291.  
  2292.     osmium_clf = $.extend(true, {}, clf);
  2293.     osmium_undo_stack.push($.extend(true, {}, clf));
  2294.     osmium_set_history_undo();
  2295.     return true;
  2296. }
  2297.  
  2298. /* @internal */
  2299. osmium_set_history_undo = function() {
  2300.     if(!window.history || !window.history.pushState) return;
  2301.  
  2302.     /* This sucks, but popstate is async */
  2303.     osmium_popstate_disablefor += Math.max(0, osmium_pushed_state_count - osmium_undo_stack_position);
  2304.     while(osmium_pushed_state_count > osmium_undo_stack_position) {
  2305.         history.back();
  2306.         --osmium_pushed_state_count;
  2307.     }
  2308.  
  2309.     while(osmium_pushed_state_count < osmium_undo_stack_position) {
  2310.         ++osmium_pushed_state_count;
  2311.         history.pushState([
  2312.             osmium_pushed_state_count,
  2313.             osmium_undo_stack[osmium_pushed_state_count],
  2314.         ], null);
  2315.     }
  2316. };
  2317.  
  2318.  
  2319.  
  2320. $(function() {
  2321.     $(window).on('popstate', function(e) {
  2322.         if(osmium_popstate_disablefor > 0) {
  2323.             --osmium_popstate_disablefor;
  2324.             return;
  2325.         }
  2326.  
  2327.         var state = e.originalEvent.state;
  2328.         if(state === null) state = [ 0, undefined ];
  2329.         var nsteps = osmium_undo_stack_position - state[0];
  2330.  
  2331.         osmium_pushed_state_count -= nsteps;
  2332.         if(osmium_undo_pop(nsteps, state[1]) === false) return;
  2333.         osmium_commit_clf();
  2334.         osmium_user_initiated_push(false);
  2335.         osmium_gen();
  2336.         osmium_user_initiated_pop();
  2337.     });
  2338. });
  2339.  
  2340.  
  2341.  
  2342. osmium_register_keyboard_command('ctrl+z', 'undo', 'Undo the last change made to the loadout.', function() {
  2343.     if(osmium_undo_pop() === false) return false;
  2344.     osmium_commit_clf();
  2345.     osmium_user_initiated_push(false);
  2346.     osmium_gen();
  2347.     osmium_user_initiated_pop();
  2348.     return false;
  2349. });
  2350.  
  2351. osmium_register_keyboard_command(null, 'debug-log-clf-undo-stack', 'Print the undo history to the Javascript console.', function() {
  2352.     console.log(osmium_undo_stack_position, osmium_undo_stack);
  2353. });
  2354. /* Osmium
  2355.  * Copyright (C) 2012, 2013, 2014, 2015 Romain "Artefact2" Dalmaso <artefact2@gmail.com>
  2356.  * Copyright (C) 2013 Josiah Boning <jboning@gmail.com>
  2357.  *
  2358.  * This program is free software: you can redistribute it and/or modify
  2359.  * it under the terms of the GNU Affero General Public License as published by
  2360.  * the Free Software Foundation, either version 3 of the License, or
  2361.  * (at your option) any later version.
  2362.  *
  2363.  * This program is distributed in the hope that it will be useful,
  2364.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  2365.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  2366.  * GNU Affero General Public License for more details.
  2367.  *
  2368.  * You should have received a copy of the GNU Affero General Public License
  2369.  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  2370.  */
  2371.  
  2372. /* Whenever you change this script, update the information in about.php. */
  2373. /*<<< require external //cdnjs.cloudflare.com/ajax/libs/jqueryui/1.10.3/jquery-ui.min.js >>>*/
  2374. /*<<< require external /static-1/jquery.jsPlumb-1.6.0-min.js >>>*/
  2375. /*<<< require external /static-1/rawdeflate.min.js >>>*/
  2376.  
  2377. /*<<< require snippet settings >>>*/
  2378. /*<<< require snippet mousetrap >>>*/
  2379. /*<<< require snippet keyboard >>>*/
  2380. /*<<< require snippet localstorage_fallback >>>*/
  2381. /*<<< require snippet new_loadout-fattribs >>>*/
  2382. /*<<< require snippet show_info >>>*/
  2383. /*<<< require snippet context_menu >>>*/
  2384. /*<<< require snippet formatted_attributes >>>*/
  2385. /*<<< require snippet graph_common >>>*/
  2386. /*<<< require snippet capacitor >>>*/
  2387. /*<<< require snippet loadout_undo >>>*/
  2388.  
  2389.  
  2390.  
  2391. osmium_user_initiated = false;
  2392. osmium_user_initiated_stack = [];
  2393.  
  2394. osmium_clfspinner = undefined;
  2395. osmium_clfspinner_level = 0;
  2396.  
  2397. osmium_must_send_clf = false;
  2398. osmium_sending_clf = false;
  2399. osmium_next_clf_opts = undefined;
  2400.  
  2401. osmium_load_static_client_data = function(staticver, onsuccess) {
  2402.     var idx = 'osmium_static_client_data_' + staticver;
  2403.  
  2404.     var onsuccess2 = function(json) {
  2405.         osmium_groups = json.groups;
  2406.         osmium_types = json.groups.types;
  2407.         osmium_charges = json.charges;
  2408.         osmium_metagroups = json.metagroups;
  2409.         osmium_module_states = json.modulestates;
  2410.         osmium_module_state_names = json.modulestatenames;
  2411.         osmium_slot_types = json.slottypes;
  2412.         osmium_ship_slots = json.shipslots;
  2413.         osmium_chargedmg = json.chargedmg;
  2414.         osmium_targetclass = json.targetclass;
  2415.         osmium_damage_profiles = json.dmgprofiles;
  2416.         osmium_booster_side_effects = json.boostersideeffects;
  2417.         osmium_modes = json.modes;
  2418.  
  2419.         /* Module states as they are defined in the CLF specification */
  2420.         osmium_states = ['offline', 'online', 'active', 'overloaded'];
  2421.  
  2422.         return onsuccess(json);
  2423.     };
  2424.  
  2425.     try {
  2426.         for(var i = 0; i < staticver; ++i) {
  2427.             localStorage.removeItem('osmium_static_client_data_' + i);
  2428.         }
  2429.  
  2430.         var cdata = localStorage.getItem(idx);
  2431.         if(cdata !== null) {
  2432.             return onsuccess2(JSON.parse(cdata));
  2433.         }
  2434.     } catch(e) { /* Incognito mode probably */ }
  2435.  
  2436.     $.ajax({
  2437.         type: 'GET',
  2438.         url: osmium_relative + '/static-' + staticver + '/cache/clientdata.json',
  2439.         dataType: 'json',
  2440.         error: function(xhr, error, httperror) {
  2441.             if(xhr.readyState === 0 || xhr.status === 0) {
  2442.                 /* User probably clicked cancel button or is closing
  2443.                  * the page */
  2444.                 return;
  2445.             }
  2446.  
  2447.             alert('Could not fetch static client data: ' + error + ' (' + httperror
  2448.                   + '). Try refreshing the page and report if the problem persists.');
  2449.         },
  2450.         success: function(json) {
  2451.             try { localStorage.setItem(idx, JSON.stringify(json)); }
  2452.             catch(e) { /* Incognito mode probably */ }
  2453.             return onsuccess2(json);
  2454.         }
  2455.     });
  2456. };
  2457.  
  2458. osmium_user_initiated_push = function(value) {
  2459.     osmium_user_initiated_stack.push(osmium_user_initiated);
  2460.     osmium_user_initiated = value;
  2461. };
  2462.  
  2463. osmium_user_initiated_pop = function() {
  2464.     osmium_user_initiated = osmium_user_initiated_stack.pop();
  2465. };
  2466.  
  2467.  
  2468.  
  2469. osmium_register_keyboard_command(
  2470.     'ctrl+l', 'regen-from-clf',
  2471.     'Regenerate most DOM elements from the CLF.',
  2472.     function() {
  2473.         osmium_gen();
  2474.         return false;
  2475.     }
  2476. );
  2477.  
  2478. osmium_register_keyboard_command(
  2479.     null, 'debug-log-clf',
  2480.     'Log the current CLF to the Javascript console.',
  2481.     function() {
  2482.         console.log(osmium_clf);
  2483.         console.log(JSON.stringify(osmium_clf));
  2484.         return false;
  2485.     }
  2486. );
  2487.  
  2488.  
  2489.  
  2490. /**
  2491.  * Synchronize the CLF with the server and update the attribute list,
  2492.  * etc. It is safe to call this function repeatedly in a short amount
  2493.  * of time, it has built-in rate limiting. Requires osmium_clftype and
  2494.  * to be set. Will call osmium_on_clf_payload if defined with the
  2495.  * payload as parameter. Will also call osmium_on_clftoken_change if
  2496.  * defined when the clf token changes, with the old and new tokens as
  2497.  * parameters.
  2498.  *
  2499.  * If specified, opts is an object which can contain any of the following properties:
  2500.  *
  2501.  * - params: send these additional params to process_clf
  2502.  * - before: function(), called before actually sending the CLF
  2503.  * - after: function(), called when process_clf is done
  2504.  * - success: function(payload), called when process_clf is done and didn't throw an error
  2505.  */
  2506. osmium_commit_clf = function(opts) {
  2507.     osmium_must_send_clf = true;
  2508.  
  2509.     if(osmium_sending_clf) {
  2510.         if(opts !== undefined) {
  2511.             osmium_next_clf_opts = opts;
  2512.         }
  2513.  
  2514.         return;
  2515.     }
  2516.     osmium_sending_clf = true;
  2517.  
  2518.     osmium_send_clf(opts);
  2519. };
  2520.  
  2521. /** @internal */
  2522. osmium_send_clf = function(opts) {
  2523.     if(!osmium_must_send_clf) {
  2524.         osmium_sending_clf = false;
  2525.         return;
  2526.     }
  2527.     osmium_must_send_clf = false;
  2528.  
  2529.     if(opts === undefined) {
  2530.         opts = osmium_next_clf_opts;
  2531.         osmium_next_clf_opts = undefined;
  2532.     }
  2533.  
  2534.     if(opts === undefined) {
  2535.         opts = {};
  2536.     }
  2537.  
  2538.     var postopts = $.extend({
  2539.         type: osmium_clftype,
  2540.         'o___csrf': osmium_token,
  2541.         relative: osmium_relative,
  2542.         clf: osmium_compress_json(osmium_clf),
  2543.         clftoken: osmium_clftoken
  2544.     }, (("params" in opts) ? opts.params : {}));
  2545.  
  2546.     osmium_clfspinner_push();
  2547.  
  2548.     if("before" in opts) opts.before();
  2549.  
  2550.     $.ajax({
  2551.         type: 'POST',
  2552.         url: osmium_relative + '/internal/syncclf',
  2553.         data: postopts,
  2554.         dataType: 'json',
  2555.         complete: function() {
  2556.             osmium_clfspinner_pop();
  2557.             if("after" in opts) {
  2558.                 opts.after();
  2559.             }
  2560.         },
  2561.         error: function(xhr, error, httperror) {
  2562.             if(xhr.readyState === 0 || xhr.status === 0) {
  2563.                 return;
  2564.             }
  2565.  
  2566.             alert('Could not sync loadout with remote: ' + error + ' (' + httperror
  2567.                   + '). This shouldn\'t normally happen, try again or refresh the page. Please report if the problem persists.');
  2568.             setTimeout(osmium_send_clf, 500);
  2569.         },
  2570.         success: function(payload) {
  2571.             if(osmium_clftoken !== payload.clftoken) {
  2572.                 if(typeof(osmium_on_clf_token_change) === 'function') {
  2573.                     osmium_on_clf_token_change(osmium_clftoken, payload.clftoken);
  2574.                 }
  2575.                 osmium_clftoken = payload.clftoken;
  2576.             }
  2577.  
  2578.             osmium_capacitors = payload.capacitors;
  2579.             osmium_ia = payload.ia;
  2580.  
  2581.             if("slots" in payload) osmium_clf_slots = payload.slots;
  2582.             if("hardpoints" in payload) osmium_clf_hardpoints = payload.hardpoints;
  2583.            
  2584.             osmium_update_slotcounts();
  2585.  
  2586.             for(var key in osmium_capacitors) {
  2587.                 osmium_regen_remote_capacitor(key);
  2588.             }
  2589.  
  2590.             $("tr.error.clferror").removeClass('error').removeClass('clferror');
  2591.             $("tr.error_message.clferror").remove();
  2592.             $("p.error_box.clferror").remove();
  2593.             $(".clferror").removeClass('clferror');
  2594.  
  2595.             if("form-errors" in payload) {
  2596.                 for(var i = 0; i < payload['form-errors'].length; ++i) {
  2597.                     var err = payload['form-errors'][i];
  2598.  
  2599.                     var tr = $(document.createElement('tr'));
  2600.                     var td = $(document.createElement('td'));
  2601.                     var p = $(document.createElement('p'));
  2602.  
  2603.                     tr.addClass('error_message').addClass('clferror');
  2604.                     td.attr('colspan', '2');
  2605.                     p.text(err[2]);
  2606.                     td.append(p);
  2607.                     tr.append(td);
  2608.  
  2609.                     $(err[1]).closest('tr').addClass('error').addClass('clferror').before(tr);
  2610.                     if(err[0]) {
  2611.                         $("a[href='#"  + err[0] +  "']").click();
  2612.                     }
  2613.                 }
  2614.             }
  2615.  
  2616.             if("p-errors" in payload) {
  2617.                 for(var i = 0; i < payload['p-errors'].length; ++i) {
  2618.                     var err = payload['p-errors'][i];
  2619.  
  2620.                     var p = $(document.createElement('p'));
  2621.                     p.addClass('error_box').addClass('clferror').text(err[2]);
  2622.  
  2623.                     $(err[1]).before(p);
  2624.                     if(err[0]) {
  2625.                         $("a[href='#"  + err[0] +  "']").click();
  2626.                     }
  2627.                 }
  2628.             }
  2629.  
  2630.             $('div#computed_attributes').empty().append($(payload.attributes).children());
  2631.             osmium_clf_rawattribs = payload.rawattribs;
  2632.             osmium_gen_fattribs();
  2633.             osmium_init_fattribs();
  2634.  
  2635.             $("section#modules div.slots li.hasattribs, section#drones div.drones li.hasattribs")
  2636.                 .removeClass('hasattribs')
  2637.                 .children('small.attribs').remove()
  2638.             ;
  2639.  
  2640.             var s, el;
  2641.  
  2642.             for(var i = 0; i < osmium_ia.length; ++i) {
  2643.                 if(!("fshort" in osmium_ia[i])) continue;
  2644.  
  2645.                 s = $(document.createElement('small'));
  2646.                 s.text(osmium_ia[i].fshort);
  2647.                 if("flong" in osmium_ia[i]) {
  2648.                     s.prop('title', osmium_ia[i].flong);
  2649.                 }
  2650.                 s.addClass('attribs');
  2651.  
  2652.                 if(osmium_ia[i].location[0] === "module") {
  2653.                     el = $("section#modules div.slots." + osmium_ia[i].location[1] + " li")
  2654.                         .filter(function() {
  2655.                             return $(this).data('index') == osmium_ia[i].location[2];
  2656.                         });
  2657.                 } else if(osmium_ia[i].location[0] === "drone") {
  2658.                     el = $("section#drones div.drones.space li").filter(function() {
  2659.                         return $(this).data('typeid') == osmium_ia[i].location[1];
  2660.                     });
  2661.                 } else {
  2662.                     continue;
  2663.                 }
  2664.  
  2665.                 el.addClass('hasattribs').append(s);
  2666.             }
  2667.  
  2668.             $("section#modules div.slots li > span.charge.hasncycles").removeClass('hasncycles')
  2669.                 .children('span.ncycles').remove();
  2670.             for(var i = 0; i < payload.ncycles.length; ++i) {
  2671.                 var s = $(document.createElement('span'));
  2672.                 s.text(payload.ncycles[i][2]);
  2673.                 s.prop('title', 'Number of module cycles before having to reload');
  2674.                 s.addClass('ncycles');
  2675.  
  2676.                 $("section#modules div.slots." + payload.ncycles[i][0] + " li").filter(function() {
  2677.                     return $(this).data('index') == payload.ncycles[i][1];
  2678.                 }).children('span.charge').addClass('hasncycles').append(s);
  2679.             }
  2680.  
  2681.             osmium_highlight_missing_prereqs(payload.missingprereqs);
  2682.  
  2683.             $("section#drones small.bayusage").text(
  2684.                 osmium_clf_rawattribs.dronecapacityused
  2685.                     + ' / ' + osmium_clf_rawattribs.dronecapacity + ' m³'
  2686.             ).toggleClass(
  2687.                 'overflow',
  2688.                 osmium_clf_rawattribs.dronecapacityused > osmium_clf_rawattribs.dronecapacity
  2689.             );
  2690.             $("section#drones small.bandwidth").text(
  2691.                 osmium_clf_rawattribs.dronebandwidthused
  2692.                     + ' / ' + osmium_clf_rawattribs.dronebandwidth + ' Mbps'
  2693.             ).toggleClass(
  2694.                 'overflow',
  2695.                 osmium_clf_rawattribs.dronebandwidthused > osmium_clf_rawattribs.dronebandwidth
  2696.             );
  2697.             var ndrones = 0;
  2698.             var dp = osmium_clf.drones[osmium_clf['X-Osmium-current-dronepresetid']];
  2699.             if("inspace" in dp) {
  2700.                 for(var i = 0; i < dp.inspace.length; ++i) {
  2701.                     ndrones += dp.inspace[i].quantity;
  2702.                 }
  2703.             }
  2704.             $("section#drones small.maxdrones").text(
  2705.                 ndrones + ' / ' + osmium_clf_rawattribs.maxactivedrones
  2706.             ).toggleClass(
  2707.                 'overflow',
  2708.                 ndrones > osmium_clf_rawattribs.maxactivedrones
  2709.             );
  2710.             osmium_clf_rawattribs.activedrones = ndrones;
  2711.  
  2712.             if(typeof(osmium_on_clf_payload) === 'function') {
  2713.                 osmium_on_clf_payload(payload);
  2714.             }
  2715.  
  2716.             if("success" in opts) opts.success(payload);
  2717.             setTimeout(osmium_send_clf, 500);
  2718.         }
  2719.     });
  2720. };
  2721.  
  2722. osmium_compress_json = function(json) {
  2723.     return btoa(RawDeflate.deflate(
  2724.         JSON.stringify(json).replace(/[\u007F-\uFFFF]/g, function(m) {
  2725.             /* Thanks to Jason S. for this neat code, see
  2726.              * http://stackoverflow.com/a/4901205/615776 */
  2727.             return "\\u" + ('0000' + m.charCodeAt(0).toString(16)).slice(-4);
  2728.         })
  2729.     ));
  2730. };
  2731.  
  2732. osmium_clfspinner_push = function() {
  2733.     if(osmium_clfspinner_level === 0) {
  2734.         if(osmium_clfspinner === undefined) {
  2735.             osmium_clfspinner = $(document.createElement('span'))
  2736.                 .prop('id', 'clfspinner')
  2737.                 .hide()
  2738.             ;
  2739.  
  2740.             osmium_clfspinner
  2741.                 .append($(document.createElement('span')).addClass('spinner'))
  2742.                 .append(' Osmium is thinking!')
  2743.             ;
  2744.  
  2745.             $('body').append(osmium_clfspinner);
  2746.         }
  2747.  
  2748.         osmium_clfspinner.fadeIn(100);
  2749.     }
  2750.  
  2751.     ++osmium_clfspinner_level;
  2752. };
  2753.  
  2754. osmium_clfspinner_pop = function() {
  2755.     --osmium_clfspinner_level;
  2756.  
  2757.     if(osmium_clfspinner_level === 0) {
  2758.         osmium_clfspinner.fadeOut(1000);
  2759.     }
  2760. };
  2761.  
  2762. osmium_load_common_data = function() {
  2763.     var d = $("div#osmium-data");
  2764.  
  2765.     osmium_cdatastaticver = d.data('cdatastaticver');
  2766.     osmium_staticver = d.data('staticver');
  2767.     osmium_relative = d.data('relative');
  2768.     osmium_token = d.data('token');
  2769.     osmium_clftoken = d.data('clftoken');
  2770.     osmium_clf = d.data('clf');
  2771.     osmium_custom_damage_profiles = d.data('customdamageprofiles');
  2772.     osmium_skillsets = d.data('skillsets');
  2773. };
  2774.  
  2775. osmium_highlight_missing_prereqs = function(missingprereqs) {
  2776.     $("section#modules div.slots > ul > li, section#drones div.drones > ul > li, section#implants div.implants > ul > li").each(function() {
  2777.         var li = $(this);
  2778.  
  2779.         var s = li.children('span.name');
  2780.         if(li.data('typeid') in missingprereqs) {
  2781.             s.addClass('missingskill');
  2782.          } else {
  2783.              s.removeClass('missingskill');
  2784.         }
  2785.  
  2786.         if(!li.hasClass('hascharge')) return;
  2787.         s = li.children('span.charge').children('span.name');
  2788.         if(li.data('chargetypeid') in missingprereqs) {
  2789.             s.addClass('missingskill');
  2790.         } else {
  2791.             s.removeClass('missingskill');
  2792.         }
  2793.     });
  2794.  
  2795.     if("ship" in osmium_clf && "typeid" in osmium_clf.ship) {
  2796.         var s = $("section#ship h1 > strong > span.name")
  2797.         if(osmium_clf.ship.typeid in missingprereqs) {
  2798.             s.addClass('missingskill');
  2799.         } else {
  2800.             s.removeClass('missingskill');
  2801.         }
  2802.     }
  2803. };
  2804. /* Osmium
  2805.  * Copyright (C) 2013 Romain "Artefact2" Dalmaso <artefact2@gmail.com>
  2806.  *
  2807.  * This program is free software: you can redistribute it and/or modify
  2808.  * it under the terms of the GNU Affero General Public License as published by
  2809.  * the Free Software Foundation, either version 3 of the License, or
  2810.  * (at your option) any later version.
  2811.  *
  2812.  * This program is distributed in the hope that it will be useful,
  2813.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  2814.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  2815.  * GNU Affero General Public License for more details.
  2816.  *
  2817.  * You should have received a copy of the GNU Affero General Public License
  2818.  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  2819.  */
  2820.  
  2821. osmium_append_loadoutid_uri = function(append) {
  2822.     var public_re = /\/loadout\/([1-9][0-9]*)([^?#\/]*)/;
  2823.     var private_re = /\/loadout\/private\/([1-9][0-9]*)([^?#\/]*)/;
  2824.     var m;
  2825.  
  2826.     window.location.hash = '';
  2827.  
  2828.     if((m = window.location.href.match(public_re)) !== null) {
  2829.         window.location.replace(window.location.href.replace(public_re, "/loadout/$1" + append));
  2830.     } else if((m = window.location.href.match(private_re)) !== null) {
  2831.         window.location.replace(window.location.href.replace(private_re, "/loadout/private/$1" + append));
  2832.     }
  2833. };
  2834.  
  2835. osmium_init_presets = function() {
  2836.     /* TODO: don't make a page reload, instead replace the URI with
  2837.      * the history API and get the new formatted preset descriptions
  2838.      * from the CLF update payload, and correctly set the preset
  2839.      * descriptions in osmium_gen_presets() */
  2840.  
  2841.     $('section#presets select#spreset').change(function() {
  2842.         osmium_append_loadoutid_uri('P' + $(this).val() + 'D' + osmium_clf['X-Osmium-current-dronepresetid']);
  2843.     });
  2844.  
  2845.     $('section#presets select#scpreset').change(function() {
  2846.         osmium_append_loadoutid_uri(
  2847.             'P' + osmium_clf['X-Osmium-current-presetid']
  2848.                 + 'C' + $(this).val()
  2849.                 + 'D' + osmium_clf['X-Osmium-current-dronepresetid']
  2850.         );
  2851.     });
  2852.  
  2853.     $('section#presets select#sdpreset').change(function() {
  2854.         osmium_append_loadoutid_uri('P' + osmium_clf['X-Osmium-current-presetid'] + 'D' + $(this).val());
  2855.     });
  2856. };
  2857. /* Osmium
  2858.  * Copyright (C) 2012, 2013, 2014 Romain "Artefact2" Dalmaso <artefact2@gmail.com>
  2859.  *
  2860.  * This program is free software: you can redistribute it and/or modify
  2861.  * it under the terms of the GNU Affero General Public License as published by
  2862.  * the Free Software Foundation, either version 3 of the License, or
  2863.  * (at your option) any later version.
  2864.  *
  2865.  * This program is distributed in the hope that it will be useful,
  2866.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  2867.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  2868.  * GNU Affero General Public License for more details.
  2869.  *
  2870.  * You should have received a copy of the GNU Affero General Public License
  2871.  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  2872.  */
  2873.  
  2874. osmium_gen_control = function() {
  2875.     var submit = $("section#control input#submit_loadout");
  2876.     if(!submit.hasClass('force')) {
  2877.         submit
  2878.             .prop('disabled', 'disabled')
  2879.             .prop('title', 'Select a ship before submitting your loadout.')
  2880.         ;
  2881.     }
  2882.  
  2883.     if(!("metadata" in osmium_clf)) return;
  2884.     if(!("X-Osmium-update-reason" in osmium_clf.metadata)) return;
  2885.     $("section#control input#ureason").val(osmium_clf.metadata['X-Osmium-update-reason']);
  2886. };
  2887.  
  2888. osmium_init_control = function() {
  2889.     $("section#control form").submit(function() {
  2890.         return false;
  2891.     });
  2892.  
  2893.  
  2894.     var lockcontrol = function() {
  2895.         var s = $("section#control");
  2896.         s.find('input, select').prop('disabled', true);
  2897.         s.append($(document.createElement('span')).addClass('spinner'));
  2898.     };
  2899.  
  2900.     var unlockcontrol = function() {
  2901.         var s = $("section#control");
  2902.         s.find('input, select').prop('disabled', false);
  2903.         s.find('span.spinner').remove();
  2904.     };
  2905.  
  2906.     $("section#control input#export_loadout").click(function() {
  2907.         var b = $(this);
  2908.         var exporttype = $("section#control select#export_type").val();
  2909.  
  2910.         osmium_commit_clf({
  2911.             params: {
  2912.                 'export': true,
  2913.                 'exportfmt': exporttype
  2914.             },
  2915.             success: function(payload) {
  2916.                 osmium_modal_rotextarea('Exported loadout (' + exporttype + ')', payload['export-payload']);
  2917.             },
  2918.             before: lockcontrol,
  2919.             after: unlockcontrol,
  2920.         });
  2921.     });
  2922.  
  2923.     $("section#control input#submit_loadout").click(function() {
  2924.         var b = $(this);
  2925.  
  2926.         osmium_commit_clf({
  2927.             params: {
  2928.                 submit: true,
  2929.             },
  2930.             success: function(payload) {
  2931.                 if("submit-error" in payload) {
  2932.                     alert(payload['submit-error']);
  2933.                 } else if("submit-loadout-uri" in payload) {
  2934.                     window.location.replace(payload['submit-loadout-uri']);
  2935.                 }
  2936.             },
  2937.             before: lockcontrol,
  2938.             after: unlockcontrol,
  2939.         });
  2940.     });
  2941.  
  2942.     $("section#control input#ureason").change(function() {
  2943.         if(!("metadata" in osmium_clf)) {
  2944.             osmium_clf.metadata = {};
  2945.         }
  2946.  
  2947.         osmium_clf.metadata['X-Osmium-update-reason'] = $("section#control input#ureason").val();
  2948.         osmium_commit_clf();
  2949.         osmium_undo_push();
  2950.     });
  2951. };
  2952.  
  2953. osmium_loadout_can_be_submitted = function() {
  2954.     var submit = $("section#control input#submit_loadout");
  2955.     if(!submit.hasClass('force')) {
  2956.         submit
  2957.             .removeProp('disabled')
  2958.             .prop('title', '')
  2959.         ;
  2960.     }
  2961. };
  2962. /* Osmium
  2963.  * Copyright (C) 2012, 2013, 2014 Romain "Artefact2" Dalmaso <artefact2@gmail.com>
  2964.  *
  2965.  * This program is free software: you can redistribute it and/or modify
  2966.  * it under the terms of the GNU Affero General Public License as published by
  2967.  * the Free Software Foundation, either version 3 of the License, or
  2968.  * (at your option) any later version.
  2969.  *
  2970.  * This program is distributed in the hope that it will be useful,
  2971.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  2972.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  2973.  * GNU Affero General Public License for more details.
  2974.  *
  2975.  * You should have received a copy of the GNU Affero General Public License
  2976.  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  2977.  */
  2978.  
  2979. /*<<< require snippet new_loadout-control >>>*/
  2980.  
  2981. osmium_gen_ship = function() {
  2982.     var section = $('section#ship');
  2983.     var p = osmium_clf.presets[osmium_clf['X-Osmium-current-presetid']];
  2984.     var img, h, small, shipname, groupname;
  2985.  
  2986.     if("ship" in osmium_clf && "typeid" in osmium_clf.ship) {
  2987.         groupname = osmium_types[osmium_clf.ship.typeid][5];
  2988.         shipname = osmium_types[osmium_clf.ship.typeid][1];
  2989.  
  2990.         img = $(document.createElement('img'));
  2991.         img.prop('src', '//image.eveonline.com/Render/' + osmium_clf.ship.typeid + '_256.png');
  2992.  
  2993.         osmium_loadout_can_be_submitted();
  2994.  
  2995.         var availslots = osmium_ship_slots[osmium_clf.ship.typeid];
  2996.         osmium_clf_slots = {
  2997.             high: availslots[0],
  2998.             medium: availslots[1],
  2999.             low: availslots[2],
  3000.             rig: availslots[3],
  3001.             subsystem: availslots[4]
  3002.         };
  3003.         osmium_clf_hardpoints = {
  3004.             turret: 0,
  3005.             launcher: 0
  3006.         };
  3007.     } else {
  3008.         groupname = '';
  3009.         shipname = '「No ship selected」';
  3010.  
  3011.         img = $(document.createElement('div'));
  3012.         img.addClass('notype');
  3013.  
  3014.         osmium_clf_slots = {
  3015.             high: 8,
  3016.             medium: 8,
  3017.             low: 8,
  3018.             rig: 3,
  3019.             subsystem: 5
  3020.         };
  3021.         osmium_clf_hardpoints = {
  3022.             turret: 0,
  3023.             launcher: 0
  3024.         };
  3025.     }
  3026.  
  3027.     h = $(document.createElement('h1'));
  3028.     h.append(img);
  3029.  
  3030.     small = $(document.createElement('small')).addClass('groupname').text(groupname);
  3031.     if(('X-mode' in p) && ('typeid' in p['X-mode'])) {
  3032.         small.append(' (' + osmium_modes[osmium_clf.ship.typeid][p['X-mode'].typeid] + ')');
  3033.     }
  3034.    
  3035.     h.append(small);
  3036.     h.append($(document.createElement('strong')).append(
  3037.         $(document.createElement('span')).addClass('name').text(shipname)
  3038.     ).prop('title', shipname));
  3039.  
  3040.     section.children('h1').remove();
  3041.     section.append(h);
  3042. };
  3043.  
  3044. osmium_init_ship = function() {
  3045.     osmium_ctxmenu_bind($("section#ship"), function() {
  3046.         var menu = osmium_ctxmenu_create();
  3047.  
  3048.         osmium_ctxmenu_add_option(menu, "Undo (C-z)", function() {
  3049.             osmium_undo_pop();
  3050.             osmium_commit_clf();
  3051.             osmium_user_initiated_push(false);
  3052.             osmium_gen();
  3053.             osmium_user_initiated_pop();
  3054.         }, { icon: [ 1, 13, 64, 64 ] });
  3055.  
  3056.         osmium_ctxmenu_add_separator(menu);
  3057.  
  3058.         if("ship" in osmium_clf && "typeid" in osmium_clf.ship
  3059.            && osmium_clf.ship.typeid in osmium_modes) {
  3060.             var p = osmium_clf.presets[osmium_clf['X-Osmium-current-presetid']];
  3061.             var mtid = ('X-mode' in p) && ('typeid' in p['X-mode']) ? p['X-mode'].typeid : 0;
  3062.            
  3063.             osmium_ctxmenu_add_subctxmenu(menu, 'Ship modes', function() {
  3064.                 var smenu = osmium_ctxmenu_create();
  3065.                 var modes = osmium_modes[osmium_clf.ship.typeid];
  3066.  
  3067.                 for(var t in modes) {
  3068.                     osmium_ctxmenu_add_option(smenu, modes[t], (function(t) {
  3069.                         return function() {
  3070.                             osmium_clf.presets[osmium_clf['X-Osmium-current-presetid']]['X-mode'] = { typeid: t };
  3071.                             osmium_gen_ship();
  3072.                             osmium_undo_push();
  3073.                             osmium_commit_clf();
  3074.                         };
  3075.                     })(t), { toggled: t == mtid });
  3076.                 }
  3077.  
  3078.                 return smenu;
  3079.             }, {});
  3080.         }
  3081.  
  3082.         osmium_ctxmenu_add_subctxmenu(menu, "Reload times", function() {
  3083.             var smenu = osmium_ctxmenu_create();
  3084.  
  3085.             osmium_ctxmenu_add_option(smenu, "Include in capacitor time", function() {
  3086.                 osmium_clf.metadata['X-Osmium-capreloadtime'] = !osmium_clf.metadata['X-Osmium-capreloadtime'];
  3087.                 osmium_undo_push();
  3088.                 osmium_commit_clf();
  3089.             }, { toggled: osmium_clf.metadata['X-Osmium-capreloadtime'] });
  3090.  
  3091.             osmium_ctxmenu_add_option(smenu, "Include in DPS", function() {
  3092.                 osmium_clf.metadata['X-Osmium-dpsreloadtime'] = !osmium_clf.metadata['X-Osmium-dpsreloadtime'];
  3093.                 osmium_undo_push();
  3094.                 osmium_commit_clf();
  3095.             }, { toggled: osmium_clf.metadata['X-Osmium-dpsreloadtime'] });
  3096.  
  3097.             osmium_ctxmenu_add_option(smenu, "Include in sustained tank", function() {
  3098.                 osmium_clf.metadata['X-Osmium-tankreloadtime'] = !osmium_clf.metadata['X-Osmium-tankreloadtime'];
  3099.                 osmium_undo_push();
  3100.                 osmium_commit_clf();
  3101.             }, { toggled: osmium_clf.metadata['X-Osmium-tankreloadtime'] });
  3102.  
  3103.             return smenu;
  3104.         }, {});
  3105.  
  3106.         osmium_ctxmenu_add_separator(menu);
  3107.  
  3108.         osmium_ctxmenu_add_option(menu, "DPS graphs…", function() {
  3109.             var hdr = $(document.createElement('header')).append(
  3110.                 $(document.createElement('h2')).text('Damage per second graph ')
  3111.                     .append($(document.createElement('a'))
  3112.                             .text('(compare and tweak…)')
  3113.                             .prop('href', osmium_relative + '/compare/dps/s,0,'
  3114.                                   + encodeURIComponent(window.location.href)
  3115.                                   + ',' + encodeURIComponent(osmium_clf.metadata.title)))
  3116.             );
  3117.             var form = $(document.createElement('form'))
  3118.                 .prop('id', 'm-dpsg');
  3119.             var table = $(document.createElement('table'));
  3120.             var tbody = $(document.createElement('tbody'));
  3121.             var tr = $(document.createElement('tr'));
  3122.             tbody.append(tr);
  3123.             table.append(tbody);
  3124.             form.append(table);
  3125.  
  3126.             var td, label, input;
  3127.  
  3128.             td = $(document.createElement('td')).addClass('signatureradius');
  3129.             label = $(document.createElement('label'))
  3130.                 .prop('for', 'm-dpsg-signatureradius')
  3131.                 .text('Target signature radius: ')
  3132.             ;
  3133.             input = $(document.createElement('input'))
  3134.                 .prop('type', 'text')
  3135.                 .prop('id', 'm-dpsg-signatureradius')
  3136.             ;
  3137.             td.append([ label, input, " m" ]);
  3138.             tr.append(td);
  3139.  
  3140.             td = $(document.createElement('td')).addClass('velocity');
  3141.             label = $(document.createElement('label'))
  3142.                 .prop('for', 'm-dpsg-velocity')
  3143.                 .text('Target velocity: ')
  3144.             ;
  3145.             input = $(document.createElement('input'))
  3146.                 .prop('type', 'text')
  3147.                 .prop('id', 'm-dpsg-velocity')
  3148.             ;
  3149.             td.append([ label, input, " m/s" ]);
  3150.             tr.append(td);
  3151.  
  3152.             td = $(document.createElement('td')).addClass('distance');
  3153.             label = $(document.createElement('label'))
  3154.                 .prop('for', 'm-dpsg-distance')
  3155.                 .text('Target distance: ')
  3156.             ;
  3157.             input = $(document.createElement('input'))
  3158.                 .prop('type', 'text')
  3159.                 .prop('id', 'm-dpsg-distance')
  3160.             ;
  3161.             td.append([ label, input, " km" ]);
  3162.             tr.append(td);
  3163.  
  3164.             td = $(document.createElement('td'));
  3165.             input = $(document.createElement('input'))
  3166.                 .prop('type', 'submit')
  3167.                 .val('Generate graph')
  3168.             ;
  3169.             td.append(input);
  3170.             tr.append(td);
  3171.  
  3172.             var ctx = $(document.createElement('div')).prop('id', 'm-dpsg-ctx');
  3173.  
  3174.             form.on('submit', function(e) {
  3175.                 e.preventDefault();
  3176.  
  3177.                 var tsr, tv, td;
  3178.                 tsr = parseFloat(form.find('td.signatureradius input').val());
  3179.                 tv = parseFloat(form.find('td.velocity input').val());
  3180.                 td = parseFloat(form.find('td.distance input').val());
  3181.  
  3182.                 var nans = 0;
  3183.                 if(isNaN(tsr)) ++nans;
  3184.                 if(isNaN(tv)) ++nans;
  3185.                 if(isNaN(td))  ++nans;
  3186.  
  3187.                 if(nans !== 1 && nans !== 2) {
  3188.                     alert('You must fill exactly one or two fields with number values.');
  3189.                     return false;
  3190.                 }
  3191.  
  3192.                 ctx.empty();
  3193.  
  3194.                 if(nans === 1) {
  3195.                     var genfunc, xlabel, xmax;
  3196.                     var limits = osmium_probe_boundaries_from_ia(osmium_ia, tsr, tv, td);
  3197.  
  3198.                     if(isNaN(tsr)) {
  3199.                         genfunc = function(x) { return [ x, tv, td ]; };
  3200.                         xlabel = "Target signature radius (m)";
  3201.                         xmax = limits[0];
  3202.                     } else if(isNaN(tv)) {
  3203.                         genfunc = function(x) { return [ tsr, x, td ]; };
  3204.                         xlabel = "Target velocity (m/s)";
  3205.                         xmax = limits[1];
  3206.                     } else if(isNaN(td)) {
  3207.                         genfunc = function(x) { return [ tsr, tv, x ]; };
  3208.                         xlabel = "Target distance (km)";
  3209.                         xmax = limits[2];
  3210.                     }
  3211.  
  3212.                     osmium_draw_dps_graph_1d(
  3213.                         { foo: { ia: osmium_ia } },
  3214.                         { foo: "hsl(0, 100%, 50%)" },
  3215.                         ctx, xlabel, 0, xmax, genfunc,
  3216.                         0, null
  3217.                     );
  3218.                 } else if(nans === 2) {
  3219.                     var genfunc, xlabel, ylabel, xmax, ymax, b;
  3220.                     b = osmium_probe_boundaries_from_ia(osmium_ia, tsr, tv, td);
  3221.  
  3222.                     if(!isNaN(tsr)) {
  3223.                         genfunc = function(x, y) { return [ tsr, y, x ]; };
  3224.                         ylabel = "Target velocity (m/s)";
  3225.                         xlabel = "Target distance (km)";
  3226.                         ymax = b[1]; xmax = b[2];
  3227.                     } else if(!isNaN(tv)) {
  3228.                         genfunc = function(x, y) { return [ y, tv, x ]; };
  3229.                         ylabel = "Target signature radius (m)";
  3230.                         xlabel = "Target distance (km)";
  3231.                         ymax = b[0]; xmax = b[2];
  3232.                     } else if(!isNaN(td)) {
  3233.                         genfunc = function(x, y) { return [ y, x, td ]; };
  3234.                         ylabel = "Target signature radius (m)";
  3235.                         xlabel = "Target velocity (m/s)";
  3236.                         ymax = b[0]; xmax = b[1];
  3237.                     }
  3238.  
  3239.                     var maxdps = osmium_draw_dps_graph_2d(
  3240.                         { foo: { ia: osmium_ia } },
  3241.                         function(fracs, cmap) {
  3242.                             for(var k in fracs) {
  3243.                                 return osmium_heat_color(fracs[k][0] / fracs[k][1]);
  3244.                             }
  3245.                             return osmium_heat_color(0);
  3246.                         },
  3247.                         ctx,
  3248.                         xlabel, 0, xmax,
  3249.                         ylabel, 0, ymax,
  3250.                         genfunc,
  3251.                         4
  3252.                     );
  3253.  
  3254.                     osmium_draw_dps_legend(ctx, maxdps, osmium_heat_color);
  3255.                 }
  3256.  
  3257.                 return false;
  3258.             });
  3259.  
  3260.             var limits = osmium_probe_boundaries_from_ia(osmium_ia, NaN, NaN, NaN);
  3261.             form.find('td.signatureradius input').val(Math.round(limits[0] / 3).toString());
  3262.  
  3263.             osmium_modal([ hdr, form, ctx ]);
  3264.             form.trigger('submit');
  3265.         }, {});
  3266.  
  3267.         if("ship" in osmium_clf && "typeid" in osmium_clf.ship) {
  3268.             osmium_ctxmenu_add_separator(menu);
  3269.  
  3270.             if(!osmium_loadout_readonly) {
  3271.                 osmium_add_generic_browse(menu, osmium_clf.ship.typeid);
  3272.             }
  3273.  
  3274.             osmium_ctxmenu_add_option(menu, "Show ship info", function() {
  3275.                 osmium_showinfo({ type: "ship" });
  3276.             }, { icon: osmium_showinfo_sprite_position, 'default': true });
  3277.  
  3278.             osmium_ctxmenu_add_option(menu, "Show IGB info", function() {
  3279.                 osmium_showinfo_igb({ type: "ship" });
  3280.             }, { icon: osmium_showinfo_sprite_position, 'default': true });
  3281.         }
  3282.  
  3283.         return menu;
  3284.     });
  3285. };
  3286. /* Osmium
  3287.  * Copyright (C) 2012, 2013, 2014 Romain "Artefact2" Dalmaso <artefact2@gmail.com>
  3288.  *
  3289.  * This program is free software: you can redistribute it and/or modify
  3290.  * it under the terms of the GNU Affero General Public License as published by
  3291.  * the Free Software Foundation, either version 3 of the License, or
  3292.  * (at your option) any later version.
  3293.  *
  3294.  * This program is distributed in the hope that it will be useful,
  3295.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  3296.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  3297.  * GNU Affero General Public License for more details.
  3298.  *
  3299.  * You should have received a copy of the GNU Affero General Public License
  3300.  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  3301.  */
  3302.  
  3303. osmium_gen_modules = function() {
  3304.     var p = osmium_clf.presets[osmium_clf['X-Osmium-current-presetid']];
  3305.     var cpid = osmium_clf['X-Osmium-current-chargepresetid'];
  3306.     var m, i, j, type, c, chargeid;
  3307.     var old_ias = {};
  3308.     var old_ncycles = {};
  3309.  
  3310.     $('section#modules > div.slots > ul > li.hasattribs > small.attribs').each(function() {
  3311.         var s = $(this);
  3312.         var li = s.parent();
  3313.         var typeid = li.data('typeid');
  3314.         var index = li.data('index');
  3315.  
  3316.         if(!(typeid in old_ias)) old_ias[typeid] = {};
  3317.         old_ias[typeid][index] = s.clone();
  3318.     });
  3319.  
  3320.     $('section#modules > div.slots > ul > li > span.charge > span.ncycles').each(function() {
  3321.         var s = $(this);
  3322.         var li = s.parent().parent();
  3323.         var typeid = li.data('typeid');
  3324.         var index = li.data('index');
  3325.  
  3326.         if(!(typeid in old_ncycles)) old_ncycles[typeid] = {};
  3327.         old_ncycles[typeid][index] = s.clone();
  3328.     });
  3329.  
  3330.     $('section#modules > div.slots > ul > li').not('.placeholder').remove();
  3331.  
  3332.     if(!("modules" in p)) p.modules = [];
  3333.  
  3334.     for(i = 0; i < p.modules.length; ++i) {
  3335.         m = p.modules[i];
  3336.         chargeid = null;
  3337.         if("charges" in m) {
  3338.             for(j = 0; j < m.charges.length; ++j) {
  3339.                 if(!("cpid" in m.charges[j])) {
  3340.                     m.charges[j].cpid = 0;
  3341.                 }
  3342.  
  3343.                 if(m.charges[j].cpid == cpid) {
  3344.                     chargeid = m.charges[j].typeid;
  3345.                     break;
  3346.                 }
  3347.             }
  3348.         }
  3349.  
  3350.         var li = osmium_add_module(m.typeid, m.index, m.state, chargeid);
  3351.  
  3352.         if(m.typeid in old_ias && m.index in old_ias[m.typeid]) {
  3353.             li.addClass('hasattribs')
  3354.                 .append(old_ias[m.typeid][m.index]);
  3355.         }
  3356.  
  3357.         if(m.typeid in old_ncycles && m.index in old_ncycles[m.typeid]) {
  3358.             li.children('span.charge')
  3359.                 .addClass('hasncycles')
  3360.                 .append(old_ncycles[m.typeid][m.index]);
  3361.         }
  3362.     }
  3363.  
  3364.     for(type in osmium_clf_slots) {
  3365.         $('section#modules > div.slots.' + type).data('type', type);
  3366.     }
  3367.  
  3368.     osmium_update_slotcounts();
  3369.     osmium_projected_regen_local();
  3370. };
  3371.  
  3372. osmium_init_modules = function() {
  3373.     $("section#modules > div.slots > h3 > span > small.groupcharges").on('click', function() {
  3374.         var t = $(this);
  3375.         var slotsdiv = t.parents("div.slots");
  3376.  
  3377.         if(slotsdiv.hasClass('grouped')) {
  3378.             slotsdiv.removeClass('grouped').addClass('ungrouped');
  3379.             t.text('Charges are not grouped');
  3380.         } else {
  3381.             slotsdiv.removeClass('ungrouped').addClass('grouped');
  3382.             t.text('Charges are grouped');
  3383.         }
  3384.  
  3385.         t.prop('title', t.text());
  3386.     }).each(function() {
  3387.         var t = $(this);
  3388.         t.prop('title', t.text());
  3389.     });
  3390.  
  3391.     if(osmium_loadout_readonly) return;
  3392.  
  3393.     $("section#modules > div.slots > ul").sortable({
  3394.         items: "li:not(.placeholder)",
  3395.         placeholder: "sortable-placeholder",
  3396.         start: function() {
  3397.             $("section#modules > div.slots").css('opacity', 0.5);
  3398.             $(this).addClass('sorting').parent().css('opacity', 1.0);
  3399.         },
  3400.         stop: function() {
  3401.             $("section#modules > div.slots").css('opacity', 1.0);
  3402.             $(this).removeClass('sorting');
  3403.         },
  3404.         update: function() {
  3405.             var z = 0;
  3406.             var map = {};
  3407.  
  3408.             $("section#modules > div.slots").find('li').each(function() {
  3409.                 var t = $(this).data('typeid');
  3410.                 var i = $(this).data('index');
  3411.  
  3412.                 if(!t) return;
  3413.                 if(!(t in map)) map[t] = {};
  3414.  
  3415.                 map[t][i] = z;
  3416.                 ++z;
  3417.             });
  3418.  
  3419.             osmium_clf.presets[osmium_clf['X-Osmium-current-presetid']].modules.sort(function(x, y) {
  3420.                 return map[x.typeid][x.index] - map[y.typeid][y.index];
  3421.             });
  3422.  
  3423.             osmium_commit_clf();
  3424.             osmium_undo_push();
  3425.         }
  3426.     });
  3427. };
  3428.  
  3429. osmium_update_slotcounts = function() {
  3430.     $('section#modules > div.slots').each(function() {
  3431.         var t = $(this);
  3432.         osmium_update_overflow(t);
  3433.         osmium_maybe_hide_slot_type(t);
  3434.     });
  3435. };
  3436.  
  3437. osmium_maybe_hide_slot_type = function(slotsdiv) {
  3438.     if(slotsdiv.children('ul').find('li').length > 0) {
  3439.         slotsdiv.show();
  3440.     } else {
  3441.         slotsdiv.hide();
  3442.     }
  3443. };
  3444.  
  3445. osmium_update_overflow = function(slotsdiv) {
  3446.     var smallcount = slotsdiv.find('h3 > span > small.counts');
  3447.     var used = slotsdiv.children('ul').children('li').not('.placeholder').length;
  3448.     var total = osmium_clf_slots[slotsdiv.data('type')];
  3449.  
  3450.     smallcount.text(used + '/' + total);
  3451.  
  3452.     var placeholders = slotsdiv.find('li.placeholder').length;
  3453.     var ideal = total - used;
  3454.     while(placeholders < ideal) {
  3455.         osmium_add_placeholder_module(slotsdiv);
  3456.         ++placeholders;
  3457.     }
  3458.     while(placeholders > ideal) {
  3459.         slotsdiv.find('li.placeholder').last().remove();
  3460.         --placeholders;
  3461.     }
  3462.  
  3463.     if(used > total) {
  3464.         smallcount.addClass('overflow');
  3465.         slotsdiv.children('ul').children('li')
  3466.             .removeClass('overflow')
  3467.             .slice(total - used).addClass('overflow');
  3468.     } else {
  3469.         smallcount.removeClass('overflow');
  3470.         slotsdiv.children('ul').children('li.overflow').removeClass('overflow');
  3471.     }
  3472. };
  3473.  
  3474. osmium_add_module = function(typeid, index, state, chargeid) {
  3475.     var m = osmium_types[typeid];
  3476.     var div = $('section#modules > div.' + m[3]);
  3477.     var ul = div.children('ul');
  3478.     var placeholders = ul.children('li.placeholder');
  3479.     var li, img, a, stateimg;
  3480.     var stateful, hascharges;
  3481.  
  3482.     if(state === null) {
  3483.         if(osmium_module_states[typeid][2]) {
  3484.             /* Active state, if possible */
  3485.             state = osmium_states[2];
  3486.         } else if(osmium_module_states[typeid][1]) {
  3487.             /* Online state */
  3488.             state = osmium_states[1];
  3489.         } else {
  3490.             /* Offline state */
  3491.             state = osmium_states[0];
  3492.         }
  3493.     }
  3494.  
  3495.     li = $(document.createElement('li'));
  3496.     li.data('typeid', typeid);
  3497.     li.data('slottype', m[3]);
  3498.     li.data('index', index);
  3499.     li.data('state', state);
  3500.     li.append($(document.createElement('span')).addClass('name').text(m[1]));
  3501.     li.prop('title', m[1]);
  3502.  
  3503.     img = $(document.createElement('img'));
  3504.     img.prop('src', '//image.eveonline.com/Type/' + typeid + '_64.png');
  3505.  
  3506.     li.prepend(img);
  3507.  
  3508.     if(hascharges = (typeid in osmium_charges)) {
  3509.         li.on('remove_charge_nogroupcheck', function() {
  3510.             var span = li.children('span.charge');
  3511.             var charge = span.children('span.name');
  3512.  
  3513.             li.data('chargetypeid', null);
  3514.             span.children('img, span.mainsprite').replaceWith(
  3515.                 osmium_sprite('', 0, 28, 32, 32, 32, 32)
  3516.             );
  3517.             charge.empty();
  3518.             charge.append($(document.createElement('em')).text('(No charge)'));
  3519.  
  3520.             var modules = osmium_clf.presets[osmium_clf['X-Osmium-current-presetid']].modules;
  3521.             for(var i = 0; i < modules.length; ++i) {
  3522.                 if(modules[i].index === index && modules[i].typeid === typeid) {
  3523.                     /* Removing nonexistent charge */
  3524.                     if(!("charges" in modules[i])) break;
  3525.  
  3526.                     var charges = modules[i].charges;
  3527.                     var curcpid = osmium_clf['X-Osmium-current-chargepresetid'];
  3528.                     var cpid;
  3529.                     for(var j = 0; j < charges.length; ++j) {
  3530.                         if(!("cpid" in charges[j])) {
  3531.                             cpid = 0; /* The spec says a value of 0 must be assumed */
  3532.                         } else {
  3533.                             cpid = charges[j].cpid;
  3534.                         }
  3535.  
  3536.                         if(curcpid == cpid) {
  3537.                             osmium_clf.presets[osmium_clf['X-Osmium-current-presetid']]
  3538.                                 .modules[i].charges.splice(j, 1);
  3539.                             break;
  3540.                         }
  3541.                     }
  3542.                     break;
  3543.                 }
  3544.             }
  3545.         }).on('remove_charge', function() {
  3546.             if(li.parents('div.slots').hasClass('grouped')) {
  3547.                 li.parents('div.slots').find('li.hascharge').filter(function() {
  3548.                     var t = $(this);
  3549.                     return t.data('typeid') === li.data('typeid')
  3550.                         && t.data('chargetypeid') === li.data('chargetypeid');
  3551.                 }).trigger('remove_charge_nogroupcheck');
  3552.             } else {
  3553.                 li.trigger('remove_charge_nogroupcheck');
  3554.             }
  3555.         });
  3556.  
  3557.         var charge = $(document.createElement('span'));
  3558.         charge.addClass('charge');
  3559.         charge.text(',').append($(document.createElement('br')));
  3560.         charge.append($(document.createElement('img')).prop('alt', ''));
  3561.         charge.append($(document.createElement('span')).addClass('name'));
  3562.  
  3563.         li.append(charge);
  3564.         li.addClass('hascharge');
  3565.  
  3566.         if(chargeid === null) {
  3567.             li.trigger('remove_charge_nogroupcheck');
  3568.         } else {
  3569.             osmium_add_charge(li, chargeid);
  3570.         }
  3571.  
  3572.         if(!osmium_loadout_readonly) {
  3573.             charge.on('dblclick', function(e) {
  3574.                 /* When dblclicking the charge, remove the charge but not the module */
  3575.                 li.trigger('remove_charge');
  3576.                 osmium_commit_clf();
  3577.                 osmium_undo_push();
  3578.                 e.stopPropagation();
  3579.                 return false;
  3580.             });
  3581.         }
  3582.     }
  3583.  
  3584.     if(stateful = osmium_slot_types[m[3]][2]) {
  3585.         a = $(document.createElement('a'));
  3586.         stateimg = $(document.createElement('img'));
  3587.         a.addClass('toggle_state');
  3588.         stateimg.prop('alt', '');
  3589.         a.append(stateimg);
  3590.         li.append(a);
  3591.  
  3592.         li.on('state_changed', function() {
  3593.             var a = li.children('a.toggle_state');
  3594.             var s = osmium_module_state_names[li.data('state')];
  3595.             a.empty();
  3596.  
  3597.             a.append(osmium_sprite(
  3598.                 s[0],
  3599.                 s[1][0],
  3600.                 s[1][1],
  3601.                 s[1][2],
  3602.                 s[1][3],
  3603.                 16, 16
  3604.             ));
  3605.  
  3606.             a.prop('title', s[0]);
  3607.         }).trigger('state_changed');
  3608.  
  3609.         a.on('click', function() {
  3610.             var a = $(this);
  3611.             var li = a.parent();
  3612.             var state = li.data('state');
  3613.             var nextstate;
  3614.  
  3615.             a.blur();
  3616.  
  3617.             if(state === 'offline') {
  3618.                 nextstate = (osmium_module_states[typeid][1] ? 'online' : 'offline');
  3619.             } else if(state === 'online') {
  3620.                 nextstate = (osmium_module_states[typeid][2] ? 'active' : 'offline');
  3621.             } else if(state === 'active') {
  3622.                 nextstate = (osmium_module_states[typeid][3] ? 'overloaded' : 'offline');
  3623.             } else if(state === 'overloaded') {
  3624.                 nextstate = 'offline';
  3625.             }
  3626.  
  3627.             osmium_set_module_state(li, nextstate);
  3628.             osmium_commit_clf();
  3629.             osmium_undo_push();
  3630.             return false;
  3631.         }).on('contextmenu', function() {
  3632.             var a = $(this);
  3633.             var li = a.parent();
  3634.             var state = li.data('state');
  3635.             var prevstate;
  3636.  
  3637.             a.blur();
  3638.  
  3639.             if(state === 'offline') {
  3640.                 if(osmium_module_states[typeid][3]) {
  3641.                     prevstate = 'overloaded';
  3642.                 } else if(osmium_module_states[typeid][2]) {
  3643.                     prevstate = 'active';
  3644.                 } else if(osmium_module_states[typeid][1]) {
  3645.                     prevstate = 'online';
  3646.                 } else {
  3647.                     prevstate = 'offline';
  3648.                 }
  3649.             } else if(state === 'online') {
  3650.                 prevstate = 'offline';
  3651.             } else if(state === 'active') {
  3652.                 prevstate = 'online';
  3653.             } else if(state === 'overloaded') {
  3654.                 prevstate = 'active';
  3655.             }
  3656.  
  3657.             osmium_set_module_state(li, prevstate);
  3658.             osmium_commit_clf();
  3659.             osmium_undo_push();
  3660.             return false;
  3661.         }).on('dblclick', function(e) {
  3662.             /* Prevent dblclick fire on the <li> itself */
  3663.  
  3664.             e.stopPropagation();
  3665.             return false;
  3666.         });
  3667.     }
  3668.  
  3669.     if(placeholders.length > 0) {
  3670.         placeholders.first().before(li);
  3671.     } else {
  3672.         ul.append(li);
  3673.     }
  3674.  
  3675.     if(osmium_user_initiated) {
  3676.         $('a[href="#modules"]').parent().click();
  3677.     }
  3678.  
  3679.     li.on('remove_module', function() {
  3680.         var modules = osmium_clf.presets[osmium_clf['X-Osmium-current-presetid']].modules;
  3681.         var li = $(this);
  3682.         var slotsdiv = li.closest('div.slots');
  3683.  
  3684.         if(osmium_types[typeid][8] === 1) {
  3685.             osmium_user_initiated_push(false);
  3686.             var lis = $("section#projected div.pr-loadout.projected-local").find('li').filter(function() {
  3687.                 var sli = $(this);
  3688.                 return sli.data('typeid') === typeid && sli.data('index') === index;
  3689.             });
  3690.  
  3691.             if(lis.length !== 1) {
  3692.                 alert('could not detach module – please report!');
  3693.                 console.log(typeid, index, modules);
  3694.                 return;
  3695.             }
  3696.  
  3697.             var fli = lis.first();
  3698.             jsPlumb.select({ source: fli }).detach();
  3699.  
  3700.             osmium_user_initiated_pop();
  3701.         }
  3702.  
  3703.         var found = false;
  3704.         for(var i = 0; i < modules.length; ++i) {
  3705.             if(modules[i].index === index && modules[i].typeid === typeid) {
  3706.                 osmium_clf.presets[osmium_clf['X-Osmium-current-presetid']].modules.splice(i, 1);
  3707.                 found = true;
  3708.                 break;
  3709.             }
  3710.         }
  3711.  
  3712.         if(!found) {
  3713.             alert('could not delete module in CLF – please report!');
  3714.             return;
  3715.         }
  3716.  
  3717.         li.remove();
  3718.  
  3719.         if(slotsdiv.find('li.overflow').length > 0) {
  3720.             slotsdiv.find('li.overflow').first().removeClass('overflow');
  3721.         } else {
  3722.             osmium_add_placeholder_module(slotsdiv);
  3723.         }
  3724.  
  3725.         if(osmium_types[typeid][8] === 1) {
  3726.             osmium_user_initiated_push(false);
  3727.             osmium_projected_regen_local();
  3728.             osmium_user_initiated_pop();
  3729.         }
  3730.     });
  3731.  
  3732.     osmium_ctxmenu_bind(li, function() {
  3733.         var menu = osmium_ctxmenu_create();
  3734.  
  3735.         if(!osmium_loadout_readonly) {
  3736.             osmium_ctxmenu_add_option(menu, "Unfit module", function() {
  3737.                 li.trigger('remove_module');
  3738.                 osmium_commit_clf();
  3739.                 osmium_undo_push();
  3740.             }, { 'default': true });
  3741.  
  3742.             osmium_ctxmenu_add_option(menu, "Unfit all of the same type", function() {
  3743.                 li.parent().find('li').filter(function() {
  3744.                     return $(this).data('typeid') === typeid
  3745.                 }).trigger('remove_module');
  3746.  
  3747.                 osmium_commit_clf();
  3748.                 osmium_undo_push();
  3749.                 osmium_update_slotcounts();
  3750.             }, {});
  3751.  
  3752.             osmium_ctxmenu_add_separator(menu);
  3753.         }
  3754.  
  3755.         if(hascharges) {
  3756.             osmium_ctxmenu_add_subctxmenu(menu, "Charges", function() {
  3757.                 var chargemenu = osmium_ctxmenu_create();
  3758.  
  3759.                 var charges_mg_pt = {}; /* metagroup -> parenttypeid -> chargetypeid */
  3760.                 var charges_mg_pt_sorted = {}; /* metagroup -> [ [ parenttypeid, chargetypeids ], … ] */
  3761.                 var mgcount = 0;
  3762.  
  3763.                 var append_charges = function(menu, chargetypeids, showhelpers) {
  3764.                     showhelpers = showhelpers & (
  3765.                         chargetypeids.length >= 2 && (chargetypeids[0] in osmium_chargedmg)
  3766.                             && osmium_chargedmg[chargetypeids[0]] > 0
  3767.                     );
  3768.  
  3769.                     if(showhelpers) {
  3770.                         osmium_ctxmenu_add_option(menu, "More damage", function() {}, { enabled: false });
  3771.                         osmium_ctxmenu_add_separator(menu);
  3772.                     }
  3773.  
  3774.                     for(var i = 0; i < chargetypeids.length; ++i) {
  3775.                         osmium_ctxmenu_add_option(
  3776.                             menu,
  3777.                             osmium_types[chargetypeids[i]][1],
  3778.                             (function(chargeid) {
  3779.                                 return function() {
  3780.                                     var modules = osmium_clf.presets[
  3781.                                         osmium_clf['X-Osmium-current-presetid']
  3782.                                     ].modules;
  3783.  
  3784.                                     for(var i = 0; i < modules.length; ++i) {
  3785.                                         if(modules[i].typeid === typeid && modules[i].index === index) {
  3786.                                             osmium_auto_add_charge_to_location(i, chargeid);
  3787.                                             osmium_commit_clf();
  3788.                                             osmium_undo_push();
  3789.                                             return;
  3790.                                         }
  3791.                                     }
  3792.                                 };
  3793.                             })(chargetypeids[i]),
  3794.                             { icon: "//image.eveonline.com/Type/" + chargetypeids[i] + "_64.png" }
  3795.                         );
  3796.                     }
  3797.  
  3798.                     if(showhelpers) {
  3799.                         osmium_ctxmenu_add_separator(menu);
  3800.                         osmium_ctxmenu_add_option(menu, "Less damage", function() {}, { enabled: false });
  3801.                     }
  3802.                 };
  3803.  
  3804.                 var pgroup = function(menu, charges_pt) {
  3805.                     var showhelpers = charges_pt.length >= 2 && (charges_pt[0][1][0] in osmium_chargedmg)
  3806.                         && osmium_chargedmg[charges_pt[0][1][0]] > 0;
  3807.  
  3808.                     if(showhelpers) {
  3809.                         osmium_ctxmenu_add_option(menu, "More damage", function() {}, { enabled: false });
  3810.                         osmium_ctxmenu_add_separator(menu);
  3811.                     }
  3812.  
  3813.                     for(var z = 0; z < charges_pt.length; ++z) {
  3814.                         if(charges_pt[z][1].length === 1) {
  3815.                             append_charges(menu, charges_pt[z][1], false);
  3816.                         } else {
  3817.                             var c = osmium_types[charges_pt[z][0]];
  3818.  
  3819.                             osmium_ctxmenu_add_subctxmenu(menu, c[1], (function(typeids) {
  3820.                                 return function() {
  3821.                                     var smenu = osmium_ctxmenu_create();
  3822.                                     append_charges(smenu, typeids, true);
  3823.                                     return smenu;
  3824.                                 };
  3825.                             })(charges_pt[z][1]), {
  3826.                                 icon: "//image.eveonline.com/Type/" + c[0] + "_64.png"
  3827.                             });
  3828.                         }
  3829.                     }
  3830.  
  3831.                     if(showhelpers) {
  3832.                         osmium_ctxmenu_add_separator(menu);
  3833.                         osmium_ctxmenu_add_option(menu, "Less damage", function() {}, { enabled: false });
  3834.                     }
  3835.                 };
  3836.  
  3837.                 compare_damage = function(a, b) {
  3838.                     var x = (a in osmium_chargedmg) ? osmium_chargedmg[a] : 0;
  3839.                     var y = (b in osmium_chargedmg) ? osmium_chargedmg[b] : 0;
  3840.                     return y < x ? -1 : (y > x ? 1 : 0);
  3841.                 };
  3842.  
  3843.                 for(var i = 0; i < osmium_charges[typeid].length; ++i) {
  3844.                     var t = osmium_types[osmium_charges[typeid][i]];
  3845.                     var parent = t[7] > 0 ? t[7] : t[0];
  3846.  
  3847.                     if(!(t[4] in charges_mg_pt)) {
  3848.                         charges_mg_pt[t[4]] = {};
  3849.                         charges_mg_pt_sorted[t[4]] = {};
  3850.                         ++mgcount;
  3851.                     }
  3852.                     if(!(parent in charges_mg_pt[t[4]])) {
  3853.                         charges_mg_pt[t[4]][parent] = [];
  3854.                     }
  3855.                     charges_mg_pt[t[4]][parent].push(t[0]);
  3856.                 }
  3857.  
  3858.                 for(var mg in charges_mg_pt) {
  3859.                     var arr = [];
  3860.  
  3861.                     for(var pt in charges_mg_pt[mg]) {
  3862.                         charges_mg_pt[mg][pt].sort(compare_damage);
  3863.                         arr.push([ pt, charges_mg_pt[mg][pt] ]);
  3864.                     }
  3865.  
  3866.                     arr.sort(function(a, b) {
  3867.                         return compare_damage(a[1][0], b[1][0]);
  3868.                     });
  3869.  
  3870.                     charges_mg_pt_sorted[mg] = arr;
  3871.                 }
  3872.  
  3873.                 if(mgcount === 1) {
  3874.                     for(var g in charges_mg_pt_sorted) {
  3875.                         pgroup(chargemenu, charges_mg_pt_sorted[g]);
  3876.                     }
  3877.                 } else {
  3878.                     for(var g in charges_mg_pt_sorted) {
  3879.                         osmium_ctxmenu_add_subctxmenu(chargemenu, osmium_metagroups[g], (function(group) {
  3880.                             return function() {
  3881.                                 var submenu = osmium_ctxmenu_create();
  3882.                                 pgroup(submenu, group);
  3883.                                 return submenu;
  3884.                             };
  3885.                         })(charges_mg_pt_sorted[g]), {
  3886.                             icon: "//image.eveonline.com/Type/" + charges_mg_pt_sorted[g][0][0] + "_64.png"
  3887.                         });
  3888.                     }
  3889.                 }
  3890.  
  3891.                 return chargemenu;
  3892.             }, { icon: "//image.eveonline.com/Type/" + osmium_charges[typeid][0] + "_64.png" });
  3893.  
  3894.             osmium_ctxmenu_add_option(menu, "Remove charge", function() {
  3895.                 li.trigger('remove_charge');
  3896.                 osmium_commit_clf();
  3897.                 osmium_undo_push();
  3898.             }, { icon: [ 0, 28, 32, 32 ] });
  3899.  
  3900.             osmium_ctxmenu_add_separator(menu);
  3901.         }
  3902.  
  3903.         if(stateful) {
  3904.             if(osmium_module_states[typeid][0]) {
  3905.                 osmium_ctxmenu_add_option(menu, "Offline module", function() {
  3906.                     osmium_set_module_state(li, "offline");
  3907.                     osmium_commit_clf();
  3908.                     osmium_undo_push();
  3909.                 }, { icon: osmium_module_state_names['offline'][1] });
  3910.             }
  3911.             if(osmium_module_states[typeid][1]) {
  3912.                 osmium_ctxmenu_add_option(menu, "Online module", function() {
  3913.                     osmium_set_module_state(li, "online");
  3914.                     osmium_commit_clf();
  3915.                     osmium_undo_push();
  3916.                 }, { icon: osmium_module_state_names['online'][1] });
  3917.             }
  3918.             if(osmium_module_states[typeid][2]) {
  3919.                 osmium_ctxmenu_add_option(menu, "Activate module", function() {
  3920.                     osmium_set_module_state(li, "active");
  3921.                     osmium_commit_clf();
  3922.                     osmium_undo_push();
  3923.                 }, { icon: osmium_module_state_names['active'][1] });
  3924.             }
  3925.             if(osmium_module_states[typeid][3]) {
  3926.                 osmium_ctxmenu_add_option(menu, "Toggle overload", function() {
  3927.                     if(li.data('state') !== "overloaded") {
  3928.                         osmium_set_module_state(li, "overloaded");
  3929.                     } else {
  3930.                         osmium_set_module_state(li, "active");
  3931.                     }
  3932.                     osmium_commit_clf();
  3933.                     osmium_undo_push();
  3934.                 }, { icon: osmium_module_state_names['overloaded'][1] });
  3935.             }
  3936.  
  3937.             osmium_ctxmenu_add_separator(menu);
  3938.  
  3939.             osmium_ctxmenu_add_option(menu, "Overload rack", function() {
  3940.                 if(li.data('state') !== "overloaded") {
  3941.                     li.closest('div.slots').find('li').each(function() {
  3942.                         var t = $(this);
  3943.                         var typeid = t.data('typeid');
  3944.                         if(!(typeid in osmium_module_states)) return;
  3945.  
  3946.                         if(osmium_module_states[typeid][3]) {
  3947.                             osmium_set_module_state(t, "overloaded");
  3948.                         }
  3949.                     });
  3950.                 } else {
  3951.                     li.closest('div.slots').find('li').each(function() {
  3952.                         var t = $(this);
  3953.                         if(t.data('state') !== 'overloaded') return;
  3954.                         osmium_set_module_state(t, "active");
  3955.                     });
  3956.                 }
  3957.  
  3958.                 osmium_commit_clf();
  3959.                 osmium_undo_push();
  3960.             }, { icon: osmium_module_state_names['overloaded'][1] });
  3961.  
  3962.             osmium_ctxmenu_add_separator(menu);
  3963.         }
  3964.  
  3965.         if(!osmium_loadout_readonly) {
  3966.             if(hascharges && li.data('chargetypeid') !== null) {
  3967.                 osmium_ctxmenu_add_heading(menu, "Module");
  3968.             }
  3969.            
  3970.             osmium_add_generic_browse(menu, typeid);
  3971.             osmium_ctxmenu_add_option(menu, "Show module info", function() {
  3972.                 osmium_showinfo({
  3973.                     type: "module",
  3974.                     slottype: li.data('slottype'),
  3975.                     index: li.data('index')
  3976.                 });
  3977.             }, { icon: osmium_showinfo_sprite_position, 'default': osmium_loadout_readonly });
  3978.            
  3979.             if(hascharges && li.data('chargetypeid') !== null) {
  3980.                 osmium_ctxmenu_add_separator(menu);
  3981.                 osmium_ctxmenu_add_heading(menu, "Charge");
  3982.                
  3983.                 osmium_add_generic_browse(menu, li.data('chargetypeid'));
  3984.                 osmium_ctxmenu_add_option(menu, "Show charge info", function() {
  3985.                     osmium_showinfo({
  3986.                         type: "charge",
  3987.                         slottype: li.data('slottype'),
  3988.                         index: li.data('index')
  3989.                     });
  3990.                 }, { icon: osmium_showinfo_sprite_position });
  3991.             }
  3992.         } else {
  3993.             osmium_ctxmenu_add_option(menu, "Show module info", function() {
  3994.                 osmium_showinfo({
  3995.                     type: "module",
  3996.                     slottype: li.data('slottype'),
  3997.                     index: li.data('index')
  3998.                 });
  3999.             }, { icon: osmium_showinfo_sprite_position, 'default': osmium_loadout_readonly });
  4000.  
  4001.             if(hascharges && li.data('chargetypeid') !== null) {
  4002.                 osmium_ctxmenu_add_option(menu, "Show charge info", function() {
  4003.                     osmium_showinfo({
  4004.                         type: "charge",
  4005.                         slottype: li.data('slottype'),
  4006.                         index: li.data('index')
  4007.                     });
  4008.                 }, { icon: osmium_showinfo_sprite_position });
  4009.             }
  4010.         }
  4011.  
  4012.         return menu;
  4013.     });
  4014.  
  4015.     return li;
  4016. };
  4017.  
  4018. osmium_add_placeholder_module = function(slotsdiv) {
  4019.     var ul = slotsdiv.children('ul');
  4020.     var li;
  4021.     var type = slotsdiv.data('type');
  4022.  
  4023.     li = $(document.createElement('li'));
  4024.     li.addClass('placeholder');
  4025.     li.text('Unused ' + type + ' slot');
  4026.  
  4027.     li.prepend(osmium_sprite(
  4028.         '',
  4029.         osmium_slot_types[type][1][0],
  4030.         osmium_slot_types[type][1][1],
  4031.         osmium_slot_types[type][1][2],
  4032.         osmium_slot_types[type][1][3],
  4033.         32, 32
  4034.     ));
  4035.     ul.append(li);
  4036. };
  4037.  
  4038. osmium_set_module_state = function(li, newstate) {
  4039.     var index = li.data('index');
  4040.     var typeid = li.data('typeid');
  4041.  
  4042.     li.data('state', newstate);
  4043.  
  4044.     var modules = osmium_clf.presets[osmium_clf['X-Osmium-current-presetid']].modules;
  4045.     for(var i = 0; i < modules.length; ++i) {
  4046.         if(modules[i].index === index && modules[i].typeid === typeid) {
  4047.             osmium_clf.presets[osmium_clf['X-Osmium-current-presetid']]
  4048.                 .modules[i].state = newstate;
  4049.             break;
  4050.         }
  4051.     }
  4052.  
  4053.     li.trigger('state_changed');
  4054. };
  4055.  
  4056. osmium_add_charge = function(li, chargetypeid) {
  4057.     var span = li.children('span.charge');
  4058.     var img = $(document.createElement('img'));
  4059.     var charge = span.children('span.name');
  4060.  
  4061.     li.data('chargetypeid', chargetypeid);
  4062.     img.prop('src', '//image.eveonline.com/Type/' + chargetypeid + '_64.png');
  4063.     img.prop('alt', '');
  4064.     span.children('img, span.mainsprite').replaceWith(img);
  4065.  
  4066.     charge.empty();
  4067.     charge.text(osmium_types[chargetypeid][1]);
  4068.     charge.prop('title', osmium_types[chargetypeid][1]);
  4069. };
  4070.  
  4071. osmium_add_charge_by_location = function(locationtypeid, locationindex, chargetypeid) {
  4072.     var li = $('section#modules > div.slots li.hascharge').filter(function() {
  4073.         var t = $(this);
  4074.         return t.data('typeid') === locationtypeid && t.data('index') === locationindex;
  4075.     }).first();
  4076.  
  4077.     return osmium_add_charge(li, chargetypeid);
  4078. };
  4079.  
  4080. osmium_get_best_location_for_charge = function(typeid) {
  4081.     var location = null;
  4082.     var candidatelevel;
  4083.  
  4084.     for(var i = 0; i < osmium_clf.presets[osmium_clf['X-Osmium-current-presetid']]
  4085.         .modules.length; ++i) {
  4086.         m = osmium_clf.presets[osmium_clf['X-Osmium-current-presetid']].modules[i];
  4087.  
  4088.         var validcharge = false, currentchargeid = null;
  4089.         if(!(m.typeid in osmium_charges)) {
  4090.             /* Module can't accept charges */
  4091.             continue;
  4092.         }
  4093.         for(var j = 0; j < osmium_charges[m.typeid].length; ++j) {
  4094.             if(osmium_charges[m.typeid][j] === typeid) {
  4095.                 validcharge = true;
  4096.                 break;
  4097.             }
  4098.         }
  4099.         if(!validcharge) continue;
  4100.  
  4101.         if(location === null) {
  4102.             /* As a fallback, fit the charge to the first suitable location */
  4103.             location = i;
  4104.             candidatelevel = 0; /* This candidate is not very good */
  4105.         }
  4106.  
  4107.         if(!("charges" in m)) {
  4108.             /* The module has no charges and can accept the charge, perfect */
  4109.             location = i;
  4110.             break;
  4111.         }
  4112.  
  4113.         var charges = osmium_clf.presets[osmium_clf['X-Osmium-current-presetid']]
  4114.             .modules[i].charges;
  4115.         var cpid;
  4116.  
  4117.         /* Check if charge already present */
  4118.         for(var j = 0; j < charges.length; ++j) {
  4119.             if("cpid" in charges[j]) {
  4120.                 cpid = charges[j].cpid;
  4121.             } else {
  4122.                 cpid = 0;
  4123.             }
  4124.  
  4125.             if(cpid == osmium_clf['X-Osmium-current-chargepresetid']) {
  4126.                 currentchargeid = charges[j].typeid;
  4127.                 break;
  4128.             }
  4129.         }
  4130.         if(currentchargeid === null) {
  4131.             /* The module has no charge in this preset and can accept the charge */
  4132.             location = i;
  4133.             break;
  4134.         } else if(currentchargeid !== typeid && candidatelevel < 1) {
  4135.             /* The module has a different charge, but it's still a
  4136.              * better candidate than the fallback */
  4137.             location = i;
  4138.             candidatelevel = 1;
  4139.         }
  4140.     }
  4141.  
  4142.     return location;
  4143. };
  4144.  
  4145. osmium_auto_add_charge_to_location = function(location, typeid) {
  4146.     var m = osmium_clf.presets[osmium_clf['X-Osmium-current-presetid']]
  4147.         .modules[location];
  4148.     var moduletypeid = osmium_clf.presets[osmium_clf['X-Osmium-current-presetid']]
  4149.         .modules[location].typeid;
  4150.     var moduletype = osmium_types[moduletypeid][3];
  4151.     var previouschargeid = null;
  4152.  
  4153.     if(!("charges" in m)) {
  4154.         osmium_clf.presets[osmium_clf['X-Osmium-current-presetid']]
  4155.             .modules[location].charges = [];
  4156.     }
  4157.  
  4158.     var charges = osmium_clf.presets[osmium_clf['X-Osmium-current-presetid']]
  4159.         .modules[location].charges;
  4160.     var cpid;
  4161.  
  4162.     /* Remove previous charge (if there is one) */
  4163.     for(var j = 0; j < charges.length; ++j) {
  4164.         if("cpid" in charges[j]) {
  4165.             cpid = charges[j].cpid;
  4166.         } else {
  4167.             cpid = 0;
  4168.         }
  4169.  
  4170.         if(cpid !== osmium_clf['X-Osmium-current-chargepresetid']) {
  4171.             continue;
  4172.         }
  4173.  
  4174.         previouschargeid = charges[j].typeid;
  4175.  
  4176.         osmium_clf.presets[osmium_clf['X-Osmium-current-presetid']]
  4177.             .modules[location].charges.splice(j, 1);
  4178.         break;
  4179.     }
  4180.  
  4181.     /* Finally, add the new charge */
  4182.     osmium_clf.presets[osmium_clf['X-Osmium-current-presetid']]
  4183.         .modules[location].charges.push({
  4184.             typeid: typeid,
  4185.             cpid: osmium_clf['X-Osmium-current-chargepresetid']
  4186.         });
  4187.  
  4188.     osmium_add_charge_by_location(m.typeid, m.index, typeid);
  4189.  
  4190.     if($("section#modules > div.slots." + moduletype).hasClass('grouped')) {
  4191.         /* Also add this charge to identical modules with identical charges */
  4192.  
  4193.         var curchargeid;
  4194.         var curchargeidx;
  4195.         for(var i = 0; i < osmium_clf.presets[osmium_clf['X-Osmium-current-presetid']]
  4196.             .modules.length; ++i) {
  4197.             curchargeid = null;
  4198.             curchargeidx = 0;
  4199.  
  4200.             m = osmium_clf.presets[osmium_clf['X-Osmium-current-presetid']].modules[i];
  4201.             if(m.typeid !== moduletypeid) {
  4202.                 continue;
  4203.             }
  4204.  
  4205.             if(!("charges" in m)) {
  4206.                 osmium_clf.presets[osmium_clf['X-Osmium-current-presetid']]
  4207.                     .modules[i].charges = [];
  4208.             }
  4209.  
  4210.             charges = osmium_clf.presets[osmium_clf['X-Osmium-current-presetid']]
  4211.                 .modules[i].charges;
  4212.  
  4213.             for(var j = 0; j < charges.length; ++j) {
  4214.                 if("cpid" in charges[j]) {
  4215.                     cpid = charges[j].cpid;
  4216.                 } else {
  4217.                     cpid = 0;
  4218.                 }
  4219.  
  4220.                 if(cpid !== osmium_clf['X-Osmium-current-chargepresetid']) {
  4221.                     continue;
  4222.                 }
  4223.  
  4224.                 curchargeid = charges[j].typeid;
  4225.                 curchargeidx = j;
  4226.                 break;
  4227.             }
  4228.  
  4229.             if(curchargeid !== previouschargeid) {
  4230.                 continue;
  4231.             }
  4232.  
  4233.             osmium_clf.presets[osmium_clf['X-Osmium-current-presetid']]
  4234.                 .modules[i].charges.splice(curchargeidx, 1);
  4235.  
  4236.             osmium_clf.presets[osmium_clf['X-Osmium-current-presetid']]
  4237.                 .modules[i].charges.push({
  4238.                     typeid: typeid,
  4239.                     cpid: osmium_clf['X-Osmium-current-chargepresetid']
  4240.                 });
  4241.  
  4242.             osmium_add_charge_by_location(m.typeid, m.index, typeid);
  4243.         }
  4244.     }
  4245. };
  4246. /* Osmium
  4247.  * Copyright (C) 2013, 2014 Romain "Artefact2" Dalmaso <artefact2@gmail.com>
  4248.  *
  4249.  * This program is free software: you can redistribute it and/or modify
  4250.  * it under the terms of the GNU Affero General Public License as published by
  4251.  * the Free Software Foundation, either version 3 of the License, or
  4252.  * (at your option) any later version.
  4253.  *
  4254.  * This program is distributed in the hope that it will be useful,
  4255.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  4256.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  4257.  * GNU Affero General Public License for more details.
  4258.  *
  4259.  * You should have received a copy of the GNU Affero General Public License
  4260.  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  4261.  */
  4262.  
  4263. osmium_gen_drones = function() {
  4264.     var drones = {
  4265.         bay: {},
  4266.         space: {}
  4267.     };
  4268.     var old_ias = {};
  4269.  
  4270.     var dronep = osmium_clf.drones[osmium_clf['X-Osmium-current-dronepresetid']];
  4271.  
  4272.     for(var p in dronep) {
  4273.         if(p !== 'inbay' && p !== 'inspace') continue;
  4274.         var map = (p === 'inbay') ? drones.bay : drones.space;
  4275.  
  4276.         for(var i = 0; i < dronep[p].length; ++i) {
  4277.             if(dronep[p][i].quantity == 0) continue;
  4278.             if(!(dronep[p][i].typeid in map)) {
  4279.                 map[dronep[p][i].typeid] = 0;
  4280.             }
  4281.  
  4282.             map[dronep[p][i].typeid] += dronep[p][i].quantity;
  4283.         }
  4284.     }
  4285.  
  4286.     $('section#drones div.drones.space li.hasattribs > small.attribs').each(function() {
  4287.         var s = $(this);
  4288.         old_ias[s.parent().data('typeid')] = s.clone();
  4289.     });
  4290.  
  4291.     for(var p in drones) {
  4292.         var dronesdiv = $("section#drones div.drones." + p);
  4293.         var ul = dronesdiv.children('ul');
  4294.         ul.empty();
  4295.  
  4296.         for(var t in drones[p]) {
  4297.             var li = $(document.createElement('li'));
  4298.             var img = $(document.createElement('img'));
  4299.             var m = osmium_types[t];
  4300.             var qty = drones[p][t];
  4301.             var other = (p === 'bay') ? 'space' : 'bay';
  4302.  
  4303.             li.data('typeid', m[0]);
  4304.             li.data('location', p);
  4305.             li.data('quantity', qty);
  4306.             li.append($(document.createElement('span')).addClass('name').text(m[1]));
  4307.             li.prop('title', m[1]);
  4308.             li.prepend($(document.createElement('strong')).addClass('qty').text(qty + '×'));
  4309.  
  4310.             img.prop('src', '//image.eveonline.com/Type/' + t + '_64.png');
  4311.             img.prop('alt', '');
  4312.  
  4313.             li.prepend(img);
  4314.  
  4315.             if(t in old_ias) {
  4316.                 li.append(old_ias[t]);
  4317.             }
  4318.  
  4319.             ul.append(li);
  4320.  
  4321.             osmium_ctxmenu_bind(li, (function(t, p, qty, other) {
  4322.                 return function() {
  4323.                     var menu = osmium_ctxmenu_create();
  4324.  
  4325.                     if(!osmium_loadout_readonly) {
  4326.                         osmium_ctxmenu_add_option(menu, "Remove 1 drone", function() {
  4327.                             osmium_remove_drone_from_clf(t, 1, p);
  4328.                             osmium_gen_drones();
  4329.                             osmium_undo_push();
  4330.                             osmium_commit_clf();
  4331.                         }, { 'default': true });
  4332.  
  4333.                         if(qty > 5) {
  4334.                             osmium_ctxmenu_add_option(menu, "Remove 5 drones", function() {
  4335.                                 osmium_remove_drone_from_clf(t, 5, p);
  4336.                                 osmium_gen_drones();
  4337.                                 osmium_undo_push();
  4338.                                 osmium_commit_clf();
  4339.                             }, { });
  4340.                         }
  4341.  
  4342.                         if(qty > 1) {
  4343.                             osmium_ctxmenu_add_option(menu, "Remove " + qty + " drones", function() {
  4344.                                 osmium_remove_drone_from_clf(t, qty, p);
  4345.                                 osmium_gen_drones();
  4346.                                 osmium_undo_push();
  4347.                                 osmium_commit_clf();
  4348.                             }, { });
  4349.                         }
  4350.  
  4351.                         osmium_ctxmenu_add_separator(menu);
  4352.                     }
  4353.  
  4354.                     osmium_ctxmenu_add_option(menu, "Move 1 to " + other, function() {
  4355.                         osmium_add_drone_to_clf(t, 1, other);
  4356.                         osmium_remove_drone_from_clf(t, 1, p);
  4357.                         osmium_gen_drones();
  4358.                         osmium_undo_push();
  4359.                         osmium_commit_clf();
  4360.                     }, { });
  4361.  
  4362.                     if(qty > 5) {
  4363.                         osmium_ctxmenu_add_option(menu, "Move 5 to " + other, function() {
  4364.                             osmium_add_drone_to_clf(t, 5, other);
  4365.                             osmium_remove_drone_from_clf(t, 5, p);
  4366.                             osmium_gen_drones();
  4367.                             osmium_undo_push();
  4368.                             osmium_commit_clf();
  4369.                         }, { });
  4370.                     }
  4371.  
  4372.                     if(qty > 1) {
  4373.                         osmium_ctxmenu_add_option(menu, "Move " + qty + " to " + other, function() {
  4374.                             osmium_add_drone_to_clf(t, qty, other);
  4375.                             osmium_remove_drone_from_clf(t, qty, p);
  4376.                             osmium_gen_drones();
  4377.                             osmium_undo_push();
  4378.                             osmium_commit_clf();
  4379.                         }, { });
  4380.                     }
  4381.  
  4382.                     osmium_ctxmenu_add_separator(menu);
  4383.  
  4384.                     if(!osmium_loadout_readonly) {
  4385.                         osmium_ctxmenu_add_option(menu, "Clear all drones in " + p, function() {
  4386.                             osmium_clf.drones[osmium_clf['X-Osmium-current-dronepresetid']]['in' + p] = [];
  4387.                             osmium_gen_drones();
  4388.                             osmium_undo_push();
  4389.                             osmium_commit_clf();
  4390.                         }, { });
  4391.  
  4392.                         osmium_ctxmenu_add_separator(menu);
  4393.                     }
  4394.  
  4395.                     if(!osmium_loadout_readonly) {
  4396.                         osmium_add_generic_browse(menu, t);
  4397.                     }
  4398.  
  4399.                     osmium_ctxmenu_add_option(menu, "Show drone info", function() {
  4400.                         osmium_showinfo({
  4401.                             type: "drone",
  4402.                             typeid: t
  4403.                         });
  4404.                     }, { icon: osmium_showinfo_sprite_position, 'default': osmium_loadout_readonly });
  4405.  
  4406.                     return menu;
  4407.                 };
  4408.             })(m[0], p, qty, other));
  4409.         }
  4410.  
  4411.         if(ul.children('li').length == 0) {
  4412.             /* Add placeholder entry */
  4413.             var li = $(document.createElement('li'));
  4414.             li.text('No drones in ' + p);
  4415.             li.addClass('placeholder');
  4416.             li.prepend(osmium_sprite('', 0, 13, 64, 64, 32, 32));
  4417.             ul.append(li);
  4418.         }
  4419.     }
  4420.  
  4421.     if(osmium_user_initiated) {
  4422.         $('a[href="#drones"]').parent().click();
  4423.     }
  4424. };
  4425.  
  4426. osmium_init_drones = function() {
  4427.     $("section#drones > div.drones > ul").sortable({
  4428.         items: "li:not(.placeholder)",
  4429.         placeholder: "sortable-placeholder",
  4430.         connectWith: "section#drones > div.drones > ul",
  4431.         remove: function(e, ui) {
  4432.             var d = ui.item;
  4433.             var t = d.data('typeid');
  4434.             var loc = d.data('location');
  4435.             var q = d.data('quantity');
  4436.  
  4437.             var drones = osmium_clf.drones[osmium_clf['X-Osmium-current-dronepresetid']]['in' + loc];
  4438.  
  4439.             for(var i = 0; i < drones.length; ++i) {
  4440.                 if(drones[i].typeid != t) continue;
  4441.  
  4442.                 drones[i].quantity -= q;
  4443.                 if(drones[i].quantity <= 0) {
  4444.                     drones.splice(i, 1);
  4445.                 }
  4446.             }
  4447.         },
  4448.         receive: function(e, ui) {
  4449.             var d = ui.item;
  4450.             var t = d.data('typeid');
  4451.             var loc = (d.data('location') === 'bay') ? 'space' : 'bay';
  4452.             var q = d.data('quantity');
  4453.             var dp = osmium_clf.drones[osmium_clf['X-Osmium-current-dronepresetid']];
  4454.             var key = 'in' + loc;
  4455.  
  4456.             if(!(key in dp)) dp[key] = [];
  4457.             drones = dp[key];
  4458.             d.data('location', loc);
  4459.  
  4460.             for(var i = 0; i < drones.length; ++i) {
  4461.                 if(drones[i].typeid != t) continue;
  4462.  
  4463.                 drones[i].quantity += q;
  4464.                 osmium_gen_drones();
  4465.                 osmium_commit_clf();
  4466.                 osmium_undo_push();
  4467.                 return;
  4468.             }
  4469.  
  4470.             drones.push({
  4471.                 typeid: t,
  4472.                 quantity: q
  4473.             });
  4474.             osmium_gen_drones();
  4475.             osmium_commit_clf();
  4476.             osmium_undo_push();
  4477.         }
  4478.     });
  4479. };
  4480.  
  4481. osmium_remove_drone_from_clf = function(typeid, qty, location) {
  4482.     var dp = osmium_clf.drones[osmium_clf['X-Osmium-current-dronepresetid']];
  4483.     dp = (location === 'space') ? dp.inspace : dp.inbay;
  4484.     var toremove = qty;
  4485.  
  4486.     for(var i = dp.length - 1; i >= 0; --i) {
  4487.         if(toremove == 0) break;
  4488.         if(dp[i].typeid != typeid) continue;
  4489.  
  4490.         if(dp[i].quantity > qty) {
  4491.             /* Easy case */
  4492.             dp[i].quantity -= qty;
  4493.             toremove = 0;
  4494.             break;
  4495.         } else {
  4496.             /* Annoying case */
  4497.             toremove -= dp[i].quantity;
  4498.             dp.splice(i, 1);
  4499.         }
  4500.     }
  4501. };
  4502.  
  4503. osmium_add_drone_to_clf = function(typeid, qty, location) {
  4504.     var dp = osmium_clf.drones[osmium_clf['X-Osmium-current-dronepresetid']];
  4505.     if(!("inspace" in dp)) dp.inspace = [];
  4506.     if(!("inbay" in dp)) dp.inbay = [];
  4507.  
  4508.     dp = (location === 'space') ? dp.inspace : dp.inbay;
  4509.  
  4510.     for(var i = 0; i < dp.length; ++i) {
  4511.         if(dp[i].typeid === typeid) {
  4512.             dp[i].quantity += qty;
  4513.             return;
  4514.         }
  4515.     }
  4516.  
  4517.     dp.push({
  4518.         typeid: typeid,
  4519.         quantity: qty
  4520.     });
  4521. };
  4522. /* Osmium
  4523.  * Copyright (C) 2013, 2014 Romain "Artefact2" Dalmaso <artefact2@gmail.com>
  4524.  *
  4525.  * This program is free software: you can redistribute it and/or modify
  4526.  * it under the terms of the GNU Affero General Public License as published by
  4527.  * the Free Software Foundation, either version 3 of the License, or
  4528.  * (at your option) any later version.
  4529.  *
  4530.  * This program is distributed in the hope that it will be useful,
  4531.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  4532.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  4533.  * GNU Affero General Public License for more details.
  4534.  *
  4535.  * You should have received a copy of the GNU Affero General Public License
  4536.  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  4537.  */
  4538.  
  4539. osmium_gen_implants = function() {
  4540.     var p = osmium_clf.presets[osmium_clf['X-Osmium-current-presetid']];
  4541.     var i_ul = $("section#implants > div.implants > ul");
  4542.     var b_ul = $("section#implants > div.boosters > ul");
  4543.  
  4544.     i_ul.empty(); b_ul.empty();
  4545.  
  4546.     if('implants' in p && p.implants.length > 0) {
  4547.         p.implants.sort(function(x, y) {
  4548.             return parseInt(osmium_types[x.typeid][3], 10) - parseInt(osmium_types[y.typeid][3], 10);
  4549.         });
  4550.  
  4551.         for(var i = 0; i < p.implants.length; ++i) {
  4552.             var t = p.implants[i].typeid;
  4553.             var imp = osmium_types[t];
  4554.             var li = $(document.createElement('li'));
  4555.             var img = $(document.createElement('img'));
  4556.             var span = $(document.createElement('span'));
  4557.  
  4558.             img.prop('src', '//image.eveonline.com/Type/' + t + '_64.png');
  4559.             img.prop('alt', '');
  4560.  
  4561.             li.append($(document.createElement('span')).addClass('name').text(imp[1]));
  4562.             li.prop('title', imp[1]);
  4563.             li.prepend(img);
  4564.             span.text(', implant slot ' + imp[3]).addClass('slot');
  4565.             li.append(span);
  4566.             li.data('typeid', t);
  4567.  
  4568.             osmium_ctxmenu_bind(li, (function(li, t) {
  4569.                 return function() {
  4570.                     var menu = osmium_ctxmenu_create();
  4571.  
  4572.                     if(!osmium_loadout_readonly) {
  4573.                         osmium_ctxmenu_add_option(menu, "Remove implant", function() {
  4574.                             var p_imp = osmium_clf.presets[osmium_clf['X-Osmium-current-presetid']].implants;
  4575.  
  4576.                             for(var z = 0; z < p_imp.length; ++z) {
  4577.                                 if(t === p_imp[z].typeid) {
  4578.                                     var ul = li.parent();
  4579.  
  4580.                                     p_imp.splice(z, 1);
  4581.                                     osmium_undo_push();
  4582.                                     osmium_commit_clf();
  4583.                                     li.remove();
  4584.  
  4585.                                     if(ul.children('li').length === 0) {
  4586.                                         osmium_gen_implants();
  4587.                                     }
  4588.                                     break;
  4589.                                 }
  4590.                             }
  4591.                         }, { 'default': true });
  4592.  
  4593.                         osmium_ctxmenu_add_separator(menu);
  4594.  
  4595.                         osmium_add_generic_browse(menu, t);
  4596.                     }
  4597.  
  4598.                     osmium_ctxmenu_add_option(menu, "Show implant info", function() {
  4599.                         osmium_showinfo({ type: 'implant', typeid: t });
  4600.                     }, { icon: osmium_showinfo_sprite_position, "default": osmium_loadout_readonly });
  4601.  
  4602.                     return menu;
  4603.                 };
  4604.             })(li, t));
  4605.  
  4606.             i_ul.append(li);
  4607.         }
  4608.     } else {
  4609.         var li = $(document.createElement('li'));
  4610.         li.addClass('placeholder');
  4611.         li.text('No implants');
  4612.         i_ul.append(li);
  4613.     }
  4614.  
  4615.     if('boosters' in p && p.boosters.length > 0) {
  4616.         p.boosters.sort(function(x, y) {
  4617.             return parseInt(osmium_types[x.typeid][3], 10) - parseInt(osmium_types[y.typeid][3], 10);
  4618.         });
  4619.  
  4620.         for(var i = 0; i < p.boosters.length; ++i) {
  4621.             var t = p.boosters[i].typeid;
  4622.             var imp = osmium_types[t];
  4623.             var li = $(document.createElement('li'));
  4624.             var img = $(document.createElement('img'));
  4625.             var span = $(document.createElement('span'));
  4626.  
  4627.             img.prop('src', '//image.eveonline.com/Type/' + t + '_64.png');
  4628.             img.prop('alt', '');
  4629.  
  4630.             li.text(imp[1]);
  4631.             li.prop('title', imp[1]);
  4632.             li.prepend(img);
  4633.             span.text(', booster slot ' + imp[3]).addClass('slot');
  4634.             li.append(span);
  4635.  
  4636.             osmium_ctxmenu_bind(li, (function(li, t) {
  4637.                 return function() {
  4638.                     var menu = osmium_ctxmenu_create();
  4639.  
  4640.                     if(!osmium_loadout_readonly) {
  4641.                         osmium_ctxmenu_add_option(menu, "Remove booster", function() {
  4642.                             var p_bst = osmium_clf.presets[osmium_clf['X-Osmium-current-presetid']].boosters;
  4643.  
  4644.                             for(var z = 0; z < p_bst.length; ++z) {
  4645.                                 if(t === p_bst[z].typeid) {
  4646.                                     var ul = li.parent();
  4647.  
  4648.                                     p_bst.splice(z, 1);
  4649.                                     osmium_undo_push();
  4650.                                     osmium_commit_clf();
  4651.                                     li.remove();
  4652.  
  4653.                                     if(ul.children('li').length === 0) {
  4654.                                         osmium_gen_implants();
  4655.                                     }
  4656.                                     break;
  4657.                                 }
  4658.                             }
  4659.                         }, { 'default': true });
  4660.  
  4661.                         osmium_ctxmenu_add_separator(menu);
  4662.                     }
  4663.  
  4664.                     osmium_ctxmenu_add_subctxmenu(menu, "Side effects", function() {
  4665.                         var smenu = osmium_ctxmenu_create();
  4666.  
  4667.                         if(t in osmium_booster_side_effects) {
  4668.                             for(var z = 0; z < osmium_booster_side_effects[t].length; ++z) {
  4669.                                 var effectid = osmium_booster_side_effects[t][z][0];
  4670.                                 var effectname = osmium_booster_side_effects[t][z][1];
  4671.                                 var clfbooster = osmium_clf.presets[osmium_clf['X-Osmium-current-presetid']]
  4672.                                     .boosters;
  4673.                                 for(var k = 0; k < clfbooster.length; ++k) {
  4674.                                     if(clfbooster[k].typeid === t) {
  4675.                                         clfbooster = clfbooster[k];
  4676.                                     }
  4677.                                 }
  4678.  
  4679.                                 osmium_ctxmenu_add_option(
  4680.                                     smenu, osmium_format_penalty_effectname(effectname),
  4681.                                     (function(effectid, clfbooster) {
  4682.                                         return function() {
  4683.                                             if("X-sideeffects" in clfbooster) {
  4684.                                                 var index = $.inArray(effectid, clfbooster['X-sideeffects']);
  4685.  
  4686.                                                 if(index !== -1) {
  4687.                                                     clfbooster['X-sideeffects'].splice(index, 1);
  4688.                                                 } else {
  4689.                                                     clfbooster['X-sideeffects'].push(effectid);
  4690.                                                 }
  4691.                                             } else {
  4692.                                                 clfbooster['X-sideeffects'] = [ effectid ];
  4693.                                             }
  4694.  
  4695.                                             osmium_undo_push();
  4696.                                             osmium_commit_clf();
  4697.                                         };
  4698.                                     })(effectid, clfbooster),
  4699.                                     {
  4700.                                         toggled: "X-sideeffects" in clfbooster
  4701.                                             && $.inArray(effectid, clfbooster['X-sideeffects']) !== -1
  4702.                                     }
  4703.                                 );
  4704.                             }
  4705.                         } else {
  4706.                             osmium_ctxmenu_add_option(smenu, "No side effects", function() {}, { enabled: false });
  4707.                         }
  4708.  
  4709.                         return smenu;
  4710.                     }, {});
  4711.  
  4712.                     osmium_ctxmenu_add_separator(menu);
  4713.  
  4714.                     osmium_ctxmenu_add_option(menu, "Show booster info", function() {
  4715.                         osmium_showinfo({ type: 'implant', typeid: t });
  4716.                     }, { icon: osmium_showinfo_sprite_position, "default": osmium_loadout_readonly });
  4717.  
  4718.                     return menu;
  4719.                 };
  4720.             })(li, t));
  4721.  
  4722.             b_ul.append(li);
  4723.         }
  4724.     } else {
  4725.         var li = $(document.createElement('li'));
  4726.         li.addClass('placeholder');
  4727.         li.text('No boosters');
  4728.         b_ul.append(li);
  4729.     }
  4730. };
  4731.  
  4732. osmium_init_implants = function() {
  4733.    
  4734. };
  4735.  
  4736. osmium_format_penalty_effectname = function(name) {
  4737.     var s = name.split(/([A-Z])/);
  4738.     var words = [];
  4739.     words.push(s.shift());
  4740.     while(s.length > 0) {
  4741.         words.push(s.shift() + s.shift());
  4742.     }
  4743.  
  4744.     if(words[0] === "booster") words.shift();
  4745.     var p = $.inArray("Penalty", words);
  4746.     if(p !== -1) words = words.slice(0, p);
  4747.  
  4748.     return words.join(" ");
  4749. };
  4750. /* Osmium
  4751.  * Copyright (C) 2015 Romain "Artefact2" Dalmaso <artefact2@gmail.com>
  4752.  *
  4753.  * This program is free software: you can redistribute it and/or modify
  4754.  * it under the terms of the GNU Affero General Public License as published by
  4755.  * the Free Software Foundation, either version 3 of the License, or
  4756.  * (at your option) any later version.
  4757.  *
  4758.  * This program is distributed in the hope that it will be useful,
  4759.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  4760.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  4761.  * GNU Affero General Public License for more details.
  4762.  *
  4763.  * You should have received a copy of the GNU Affero General Public License
  4764.  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  4765.  */
  4766.  
  4767. osmium_gen_beacons = function() {
  4768.     var ul = $("section#area > div > ul");
  4769.     var clfp = osmium_clf.presets[osmium_clf['X-Osmium-current-presetid']];
  4770.  
  4771.     ul.empty();
  4772.  
  4773.     if(!('X-beacons' in clfp) || clfp['X-beacons'].length === 0) {
  4774.         ul.append(
  4775.             $(document.createElement('li'))
  4776.                 .addClass('placeholder')
  4777.                 .text('No area effects')
  4778.         );
  4779.         return;
  4780.     }
  4781.  
  4782.     for(var i = 0; i < clfp['X-beacons'].length; ++i) {
  4783.         var btypeid = clfp['X-beacons'][i];
  4784.         var beacon = osmium_types[btypeid];
  4785.         var li;
  4786.  
  4787.         ul.append(
  4788.             li = $(document.createElement('li'))
  4789.                 .prop('title', beacon[1])
  4790.                 .append(
  4791.                     $(document.createElement('img'))
  4792.                         .prop('src', '//image.eveonline.com/Type/' + btypeid +'_64.png')
  4793.                         .prop('alt', '')
  4794.                 )
  4795.                 .append(
  4796.                     $(document.createElement('span'))
  4797.                         .addClass('name')
  4798.                         .text(beacon[1])
  4799.                 )
  4800.         );
  4801.  
  4802.         osmium_ctxmenu_bind(li, (function(li, typeid) {
  4803.             return function() {
  4804.                 var menu = osmium_ctxmenu_create();
  4805.  
  4806.                 if(!osmium_loadout_readonly) {
  4807.                     osmium_ctxmenu_add_option(menu, "Remove beacon", function() {
  4808.                         var clfpb = osmium_clf.presets[osmium_clf['X-Osmium-current-presetid']]['X-beacons'];
  4809.  
  4810.                         for(var z = 0; z < clfpb.length; ++z) {
  4811.                             if(typeid === clfpb[z]) {
  4812.                                 var ul = li.parent();
  4813.  
  4814.                                 clfpb.splice(z, 1);
  4815.                                 osmium_undo_push();
  4816.                                 osmium_commit_clf();
  4817.                                 li.remove();
  4818.  
  4819.                                 if(ul.children('li').length === 0) {
  4820.                                     osmium_gen_beacons();
  4821.                                 }
  4822.                                 break;
  4823.                             }
  4824.                         }
  4825.                     }, { 'default': true });
  4826.  
  4827.                     osmium_ctxmenu_add_separator(menu);
  4828.  
  4829.                     osmium_add_generic_browse(menu, typeid);
  4830.                 }
  4831.  
  4832.                 osmium_ctxmenu_add_option(menu, "Show beacon info", function() {
  4833.                     osmium_showinfo({ type: 'beacon', typeid: typeid });
  4834.                 }, { icon: osmium_showinfo_sprite_position, "default": osmium_loadout_readonly });
  4835.                
  4836.                 return menu;
  4837.             };
  4838.         })(li, btypeid));
  4839.     }
  4840. };
  4841.  
  4842. osmium_init_beacons = function() {
  4843.  
  4844. };
  4845. /* Osmium
  4846.  * Copyright (C) 2013, 2014 Romain "Artefact2" Dalmaso <artefact2@gmail.com>
  4847.  *
  4848.  * This program is free software: you can redistribute it and/or modify
  4849.  * it under the terms of the GNU Affero General Public License as published by
  4850.  * the Free Software Foundation, either version 3 of the License, or
  4851.  * (at your option) any later version.
  4852.  *
  4853.  * This program is distributed in the hope that it will be useful,
  4854.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  4855.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  4856.  * GNU Affero General Public License for more details.
  4857.  *
  4858.  * You should have received a copy of the GNU Affero General Public License
  4859.  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  4860.  */
  4861.  
  4862. osmium_gen_remote = function() {
  4863.     osmium_gen_fleet();
  4864.     osmium_gen_projected();
  4865. };
  4866.  
  4867. osmium_gen_fleet = function() {
  4868.     var selects = $("section#fleet select");
  4869.     selects.empty();
  4870.  
  4871.     for(var i = 0; i < osmium_skillsets.length; ++i) {
  4872.         selects.append(
  4873.             $(document.createElement('option'))
  4874.                 .text(osmium_skillsets[i])
  4875.                 .prop('value', osmium_skillsets[i])
  4876.         );
  4877.     }
  4878.  
  4879.     $("section#fleet").find('input, select')
  4880.         .not("[type='checkbox']").prop('disabled', true);
  4881.  
  4882.     if(!("X-Osmium-fleet" in osmium_clf)) {
  4883.         osmium_clf['X-Osmium-fleet'] = {};
  4884.     }
  4885.  
  4886.     for(var t in osmium_clf['X-Osmium-fleet']) {
  4887.         $("section#fleet select#" + t + "_skillset").val(osmium_clf['X-Osmium-fleet'][t].skillset);
  4888.         $("section#fleet input#" + t + "_fit").val(osmium_clf['X-Osmium-fleet'][t].fitting);
  4889.         $("section#fleet input#" + t + "_enabled").prop('checked', true).change();
  4890.         $("section#fleet").find("input, select").filter("." + t).prop('disabled', false);
  4891.     }
  4892. };
  4893.  
  4894. osmium_projected_clean = function() {
  4895.     jsPlumb.unbind();
  4896.     jsPlumb.doWhileSuspended(function() {
  4897.         jsPlumb.detachEveryConnection();
  4898.         jsPlumb.deleteEveryEndpoint();
  4899.         jsPlumb.unmakeEverySource();
  4900.         jsPlumb.unmakeEveryTarget();
  4901.     });
  4902. }
  4903.  
  4904. osmium_gen_projected = function() {
  4905.     jsPlumb.Defaults.Container = $("section#projected form#projected-list");
  4906.     jsPlumb.Defaults.Endpoints = [ "Blank", "Blank" ];
  4907.  
  4908.     osmium_projected_clean();
  4909.  
  4910.     jsPlumb.bind("connection", function(info) {
  4911.         var src = $(info.source), tgt = $(info.target);
  4912.  
  4913.         if(src.closest('div.pr-loadout').get(0) === tgt.get(0)) {
  4914.             /* Disallow self-projection. While libdogma can handle it
  4915.              * in theory, it doesn't make a lot of sense to allow it
  4916.              * since real dogma dosen't allow it. */
  4917.             jsPlumb.detach(info.connection);
  4918.             return;
  4919.         }
  4920.  
  4921.         /* A module can only target one entity at a time, so remove
  4922.          * other connections */
  4923.         jsPlumb.doWhileSuspended(function() {
  4924.             jsPlumb.select({ source: info.source }).each(function(conn) {
  4925.                 if(conn !== info.connection) {
  4926.                     jsPlumb.detach(conn);
  4927.                 }
  4928.             });
  4929.         });
  4930.  
  4931.         /* Update the target in CLF */
  4932.         var tkey = tgt.data('key');
  4933.         var skey = src.closest('div.pr-loadout').data('key');
  4934.         var stid = src.data('typeid');
  4935.         var sidx = src.data('index');
  4936.         var clf;
  4937.  
  4938.         if(skey === 'local') {
  4939.             clf = osmium_clf;
  4940.         } else {
  4941.             clf = osmium_clf['X-Osmium-remote'][skey];
  4942.         }
  4943.  
  4944.         var m = clf.presets[clf['X-Osmium-current-presetid']].modules;
  4945.         for(var i = 0; i < m.length; ++i) {
  4946.             if(m[i].typeid === stid && m[i].index === sidx) {
  4947.                 m[i]['X-Osmium-target'] = tkey;
  4948.                 osmium_commit_undo_deferred();
  4949.                 src.addClass('hastarget');
  4950.                 src.append(
  4951.                     $(document.createElement('div'))
  4952.                         .addClass('bghue')
  4953.                         .css('background-color', src.closest('div.pr-loadout').css('border-color'))
  4954.                 );
  4955.                 return;
  4956.             }
  4957.         }
  4958.  
  4959.         alert('Could not create connection in CLF – please report!');
  4960.     });
  4961.     jsPlumb.bind("connectionDetached", function(info) {
  4962.         var src = $(info.source);
  4963.  
  4964.         /* Delete the target in CLF */
  4965.         var skey = src.closest('div.pr-loadout').data('key');
  4966.         var stid = src.data('typeid');
  4967.         var sidx = src.data('index');
  4968.         var clf;
  4969.  
  4970.         if(skey === 'local') {
  4971.             clf = osmium_clf;
  4972.         } else {
  4973.             clf = osmium_clf['X-Osmium-remote'][skey];
  4974.         }
  4975.  
  4976.         var m = clf.presets[clf['X-Osmium-current-presetid']].modules;
  4977.         for(var i = 0; i < m.length; ++i) {
  4978.             if(m[i].typeid === stid && m[i].index === sidx) {
  4979.                 m[i]['X-Osmium-target'] = false;
  4980.                 osmium_commit_undo_deferred();
  4981.                 src.children('div.bghue').remove();
  4982.                 src.removeClass('hastarget');
  4983.                 return;
  4984.             }
  4985.         }
  4986.  
  4987.         alert('Could not delete connection in CLF – please report!');
  4988.         console.log('connection not found in CLF', stid, sidx, m);
  4989.     });
  4990.  
  4991.     var list = $("section#projected form#projected-list");
  4992.     list.empty();
  4993.  
  4994.     var local = osmium_create_projected("local", osmium_clf, 0);
  4995.     list.append(local);
  4996.  
  4997.     if(!("X-Osmium-remote" in osmium_clf)) {
  4998.         osmium_clf['X-Osmium-remote'] = {};
  4999.     }
  5000.  
  5001.     var c = 0;
  5002.     var remotes = {};
  5003.     remotes.local = osmium_clf;
  5004.  
  5005.     for(var key in osmium_clf['X-Osmium-remote']) {
  5006.         var pfit = osmium_clf['X-Osmium-remote'][key];
  5007.         list.append(osmium_create_projected(key, pfit, ++c));
  5008.         remotes[key] = pfit;
  5009.     }
  5010.  
  5011.     for(var key in remotes) {
  5012.         var pclf = remotes[key];
  5013.  
  5014.         if(!("X-Osmium-current-presetid" in pclf)) continue;
  5015.         if(!("presets" in pclf)) continue;
  5016.         if(!(pclf['X-Osmium-current-presetid'] in pclf.presets)) continue;
  5017.         if(!("modules" in pclf.presets[pclf['X-Osmium-current-presetid']])) continue;
  5018.  
  5019.         var modules = pclf.presets[pclf['X-Osmium-current-presetid']].modules;
  5020.  
  5021.         for(var i = 0; i < modules.length; ++i) {
  5022.             var m = modules[i];
  5023.             if("X-Osmium-target" in m && m['X-Osmium-target'] !== false) {
  5024.                 var source = $('section#projected div.pr-loadout.projected-' + key)
  5025.                     .find('li').filter(function() {
  5026.                         var li = $(this);
  5027.                         return li.data('typeid') === m.typeid && li.data('index') === m.index
  5028.                     }).first();
  5029.  
  5030.                 var target = $('section#projected div.pr-loadout.projected-' + m['X-Osmium-target']);
  5031.  
  5032.                 if(source.length !== 1) {
  5033.                     alert('Could not find source from CLF connection, please report!');
  5034.                     continue;
  5035.                 }
  5036.                 if(target.length !== 1) {
  5037.                     alert('Could not find target from CLF connection, please report!');
  5038.                     continue;
  5039.                 }
  5040.  
  5041.                 jsPlumb.connect({ source: source, target: target });
  5042.             }
  5043.         }
  5044.     }
  5045. };
  5046.  
  5047. osmium_init_remote = function() {
  5048.     osmium_init_fleet();
  5049.     osmium_init_projected();
  5050. };
  5051.  
  5052. osmium_init_fleet = function() {
  5053.     $("section#fleet").on('change', "input[type='checkbox']", function() {
  5054.         var c = $(this);
  5055.         var tr = c.closest('tr');
  5056.         var table = tr.closest('table');
  5057.         var type = tr.data('type');
  5058.  
  5059.         if(!("X-Osmium-fleet" in osmium_clf)) {
  5060.             osmium_clf['X-Osmium-fleet'] = {};
  5061.         }
  5062.         var fleet = osmium_clf['X-Osmium-fleet'];
  5063.  
  5064.         if(c.is(':checked')) {
  5065.             fleet[type] = {
  5066.                 skillset: table.find('select#' + type + '_skillset').val(),
  5067.                 fitting: table.find('input#' + type + '_fit').val()
  5068.             };
  5069.             table.find('input, select')
  5070.                 .filter('.' + type).prop('disabled', false);
  5071.         } else {
  5072.             delete(fleet[type]);
  5073.             table.find('input, select')
  5074.                 .filter('.' + type).not("[type='checkbox']")
  5075.                 .prop('disabled', true);
  5076.         }
  5077.  
  5078.         if(osmium_user_initiated) {
  5079.             osmium_undo_push();
  5080.             osmium_commit_clf();
  5081.         }
  5082.     }).on('change', 'select', function() {
  5083.         var s = $(this);
  5084.         var tr = s.closest('tr');
  5085.         var table = tr.closest('table');
  5086.         var checkbox = table.find("input#" + tr.data('type') + "_enabled");
  5087.  
  5088.         if(!checkbox.is(':checked')) {
  5089.             return;
  5090.         }
  5091.  
  5092.         checkbox.trigger('change');
  5093.     }).on('click', 'input.set', function() {
  5094.         var s = $(this);
  5095.         var tr = s.closest('tr');
  5096.         var table = tr.closest('table');
  5097.         var checkbox = table.find("input#" + tr.data('type') + "_enabled");
  5098.  
  5099.         if(!checkbox.is(':checked')) {
  5100.             return;
  5101.         }
  5102.  
  5103.         checkbox.trigger('change');
  5104.     }).on('click', 'input.clear', function() {
  5105.         var s = $(this);
  5106.         var tr = s.closest('tr');
  5107.         var table = tr.closest('table');
  5108.         var checkbox = table.find("input#" + tr.data('type') + "_enabled");
  5109.  
  5110.         if(!checkbox.is(':checked')) {
  5111.             return;
  5112.         }
  5113.  
  5114.         table.find('input#' + tr.data('type') + '_fit').val('');
  5115.         checkbox.trigger('change');
  5116.     }).on('keypress', 'input.fit', function(e) {
  5117.         if(e.which != 13) return;
  5118.         e.preventDefault();
  5119.  
  5120.         var s = $(this);
  5121.         var tr = s.closest('tr');
  5122.         var table = tr.closest('table');
  5123.         var checkbox = table.find("input#" + tr.data('type') + "_enabled");
  5124.  
  5125.         if(!checkbox.is(':checked')) {
  5126.             return false;
  5127.         }
  5128.  
  5129.         checkbox.trigger('change');
  5130.         return false;
  5131.     });
  5132. };
  5133.  
  5134. osmium_add_projected = function(remotefit, target) {
  5135.     if(!("X-Osmium-remote" in osmium_clf)) {
  5136.         osmium_clf['X-Osmium-remote'] = {};
  5137.     }
  5138.  
  5139.     var key = 1;
  5140.     while((key + '') in osmium_clf['X-Osmium-remote']) ++key;
  5141.     key = key + '';
  5142.  
  5143.     osmium_clf['X-Osmium-remote'][key] = {
  5144.         fitting: '',
  5145.         skillset: 'All V'
  5146.     };
  5147.  
  5148.     var newproj = osmium_create_projected(
  5149.         key, { },
  5150.         $("section#projected > form#projected-list > div.pr-loadout").length
  5151.     );
  5152.  
  5153.     $("section#projected > form#projected-list").append(newproj);
  5154.  
  5155.     if(remotefit !== undefined) {
  5156.         osmium_clf['X-Osmium-remote'][key].fitting = remotefit;
  5157.     }
  5158.  
  5159.     if(remotefit !== undefined && target !== undefined) {
  5160.         var target = $("section#projected div.pr-loadout.projected-" + target);
  5161.         osmium_projected_regen_remote(key, function() {
  5162.             $("section#projected div.pr-loadout.projected-" + key).find('ul > li').each(function() {
  5163.                 jsPlumb.connect({ source: $(this), target: target });
  5164.             });
  5165.         });
  5166.     } else {
  5167.         osmium_commit_undo_deferred();
  5168.     }
  5169.  
  5170.     return newproj;
  5171. };
  5172.  
  5173. osmium_init_projected = function() {
  5174.     if(!osmium_loadout_readonly) {
  5175.         $("section#projected input#createprojected").on('click', function() {
  5176.             osmium_add_projected('').trigger('dblclick');
  5177.         });
  5178.     }
  5179.  
  5180.     $("section#remote").on('made_visible', function() {
  5181.         osmium_regen_offsets();
  5182.         jsPlumb.repaintEverything();
  5183.  
  5184.         $("section#remote").off('made_visible');
  5185.     });
  5186.  
  5187.     $("section#projected input#projectedfstoggle").on('click', function() {
  5188.         var section = $("section#projected");
  5189.         var fs = section.hasClass('fs');
  5190.  
  5191.         $("body").scrollTop(0);
  5192.  
  5193.         section.animate({ opacity: 0 }, 250, function() {
  5194.             jsPlumb.doWhileSuspended(function() {
  5195.                 jsPlumb.toggleDraggable(
  5196.                     section.find('div.pr-loadout')
  5197.                         .draggable("option", "disabled", fs)
  5198.                 );
  5199.  
  5200.                 section.toggleClass('fs');
  5201.                 var exitfs = function() {
  5202.                     $("section#projected input#projectedfstoggle").click();
  5203.                     return false;
  5204.                 };
  5205.  
  5206.                 if(fs) {
  5207.                     osmium_unregister_keyboard_command('exit-fullscreen-projected');
  5208.                     $("div#fsbg").remove();
  5209.                 } else {
  5210.                     var bg = $(document.createElement('div'));
  5211.                     bg.prop('id', 'fsbg');
  5212.                     bg.click(exitfs);
  5213.                     $("section#projected").prepend(bg);
  5214.                     osmium_register_keyboard_command(
  5215.                         'esc', 'exit-fullscreen-projected', 'Exit fullscreen projected mode.', exitfs
  5216.                     );
  5217.                 }
  5218.  
  5219.                 /* Swap fixed and draggable positions */
  5220.                 $("section#projected div.pr-loadout").each(function() {
  5221.                     var t = $(this);
  5222.                     var otop = t.css('top');
  5223.                     var oleft = t.css('left');
  5224.  
  5225.                     t.css('top', t.data('top')).data('top', otop);
  5226.                     t.css('left', t.data('left')).data('left', oleft);
  5227.                 });
  5228.  
  5229.                 osmium_regen_offsets();
  5230.  
  5231.                 /* Auto-rearrange if necessary */
  5232.                 var localtop = $("section#projected div.pr-loadout.projected-local").css('top');
  5233.                 if(section.hasClass('fs') && (!localtop || localtop === 'auto' || localtop === '0px')) {
  5234.                     if($("section#projected div.pr-loadout").length <= 8) {
  5235.                         $("section#projected a#rearrange-circle").click();
  5236.                     } else {
  5237.                         $("section#projected a#rearrange-grid").click();
  5238.                     }
  5239.                 }
  5240.             });
  5241.  
  5242.             section.animate({ opacity: 1 }, 250);      
  5243.         });
  5244.     });
  5245.  
  5246.     $("section#projected a#rearrange-circle").on('click', function() {
  5247.         var s = $("section#projected");
  5248.         var f = s.children("form#projected-list");
  5249.  
  5250.         var so = s.offset();
  5251.         var mx = ($(window).width() - so.left) / 2;
  5252.         var my = ($(window).height() - so.top) / 2;
  5253.         var m = Math.min(mx, my);
  5254.         var divs = f.find('div.pr-loadout');
  5255.  
  5256.         jsPlumb.doWhileSuspended(function() {
  5257.             divs.each(function() {
  5258.                 var d = $(this);
  5259.                 var angle = d.index() / divs.length * 2 * Math.PI;
  5260.                 var w = d.width();
  5261.                 var h = d.height();
  5262.  
  5263.                 d.offset({
  5264.                     left: (so.left + mx - (m - d.outerWidth() / 2 - 32) * Math.cos(angle) - w / 2).toFixed(0),
  5265.                     top: (so.top + my - (m - d.outerHeight() / 2 - 32) * Math.sin(angle) - h / 2).toFixed(0)
  5266.                 });
  5267.             });
  5268.         });
  5269.     });
  5270.  
  5271.     $("section#projected a#rearrange-grid").on('click', function() {
  5272.         var s = $("section#projected");
  5273.         var f = s.children("form#projected-list");
  5274.  
  5275.         var so = s.offset();
  5276.         var mx = ($(window).width() - so.left);
  5277.         var my = ($(window).height() - so.top - 30);
  5278.         var divs = f.find('div.pr-loadout');
  5279.  
  5280.         var maxw = 1, maxh = 1;
  5281.         var rows = 1, cols = 1;
  5282.         var cellw = 1, cellh = 1;
  5283.  
  5284.         divs.each(function() {
  5285.             var d = $(this);
  5286.             var w = d.outerWidth();
  5287.             var h = d.outerHeight();
  5288.  
  5289.             if(w > maxw) maxw = w;
  5290.             if(h > maxh) maxh = h;
  5291.         });
  5292.  
  5293.         /* Add some padding */
  5294.         maxw += 50;
  5295.         maxh += 50;
  5296.  
  5297.         var maxrows = Math.max(1, Math.floor(my / maxh));
  5298.         var maxcols = Math.max(1, Math.floor(mx / maxw));
  5299.  
  5300.         while((rows * cols) < divs.length) {
  5301.             if(cols < maxcols) {
  5302.                 ++cols;
  5303.             }
  5304.  
  5305.             if((rows * cols) >= divs.length) break;
  5306.  
  5307.             ++rows;
  5308.         }
  5309.  
  5310.         if(rows <= maxrows) {
  5311.             /* Everything can fit */
  5312.             cellw = Math.floor(mx / cols);
  5313.             cellh = Math.floor(my / rows);
  5314.         } else {
  5315.             /* Not enough space, use vertical scrolling */
  5316.             cols = Math.max(1, Math.floor(mx / maxw));
  5317.             cellw = Math.floor(mx / cols);
  5318.             cellh = maxh;
  5319.         }
  5320.  
  5321.         jsPlumb.doWhileSuspended(function() {
  5322.             divs.each(function() {
  5323.                 var d = $(this);
  5324.                 var i = d.index();
  5325.  
  5326.                 d.offset({
  5327.                     left: (so.left + (i % cols) * cellw + 10 * Math.cos(7 * i)
  5328.                            + cellw / 2 - d.width() / 2).toFixed(0),
  5329.                     top: (so.top + 30 + Math.floor(i / cols) * cellh + 10 * Math.sin(7 * i)
  5330.                           + cellh / 2 - d.height() / 2).toFixed(0)
  5331.                 });
  5332.             });
  5333.         });
  5334.     });
  5335.  
  5336.     osmium_register_keyboard_command(null, 'debug-jsplumb-repaint', 'Call jsPlumb.repaintEverything().',
  5337.                                      function() {
  5338.                                          jsPlumb.repaintEverything();
  5339.                                          return false;
  5340.                                      });
  5341.  
  5342.     osmium_register_keyboard_command(
  5343.         null, 'debug-jsplumb-recalculate-offsets', 'Recalculate jsPlumb offsets of remotes.',
  5344.         function() {
  5345.             osmium_regen_offsets();
  5346.         }
  5347.     );
  5348. };
  5349.  
  5350. osmium_create_projected = function(key, clf, index) {
  5351.     var proj = $(document.createElement('div'));
  5352.     var ul = $(document.createElement('ul'));
  5353.  
  5354.     var angle;
  5355.     if(index === undefined) {
  5356.         angle = (360 * Math.random()).toFixed(0);
  5357.     } else if(index === 0) {
  5358.         angle = 0;
  5359.     } else {
  5360.         if(index < 1 || typeof index !== 'number') throw 'Invalid index parameter';
  5361.  
  5362.         var low = 1;
  5363.         var up = 2;
  5364.  
  5365.         while(!(low <= index && index < up)) {
  5366.             low = up;
  5367.             up = up + up;
  5368.         }
  5369.  
  5370.         angle = (360 * (2 * (index - low) + 1) / up).toFixed(0);
  5371.     }
  5372.  
  5373.     proj.data('hue', angle);
  5374.     proj.addClass('projected-' + key);
  5375.     proj.data('key', key);
  5376.     proj.addClass('pr-loadout');
  5377.     proj.css('border-color', 'hsl(' + angle + ', 25%, 50%)');
  5378.     proj.css('background-color', 'hsla(' + angle + ', 25%, 50%, 0.1)');
  5379.  
  5380.     proj.data('title', key === 'local' ? 'Local' : ('Remote #' + key));
  5381.  
  5382.     if("ship" in clf && "typeid" in clf.ship) {
  5383.         var img = $(document.createElement('img'));
  5384.         img.prop('alt', clf.ship.typename);
  5385.         img.prop('src', '//image.eveonline.com/Render/' + clf.ship.typeid + '_512.png');
  5386.         img.addClass('render');
  5387.         proj.append(img);
  5388.         proj.data('shiptypeid', clf.ship.typeid);
  5389.         proj.data('title', proj.data('title') + ': ' + osmium_types[clf.ship.typeid][1]);
  5390.     }
  5391.  
  5392.     if("presets" in clf && "X-Osmium-current-presetid" in clf
  5393.        && "modules" in clf.presets[clf['X-Osmium-current-presetid']]) {
  5394.         var p = clf.presets[clf['X-Osmium-current-presetid']].modules;
  5395.         var projectable = 0;
  5396.         var pindex = 0;
  5397.  
  5398.         for(var i = 0; i < p.length; ++i) {
  5399.             var t = osmium_types[p[i].typeid];
  5400.             if(t[8] !== 1) continue;
  5401.             ++projectable;
  5402.         }
  5403.  
  5404.         var size; /* Also diameter of the circle */
  5405.         if(projectable === 0) {
  5406.             size = 120;
  5407.         } else {
  5408.             size = 80 * Math.max(6, projectable) / Math.PI;
  5409.         }
  5410.  
  5411.         proj.css({
  5412.             width: size + 'px',
  5413.             height: size + 'px'
  5414.         });
  5415.  
  5416.         for(var i = 0; i < p.length; ++i) {
  5417.             var t = osmium_types[p[i].typeid];
  5418.             if(t[8] !== 1) continue;
  5419.  
  5420.             var source =
  5421.                 $(document.createElement('li'))
  5422.                 .data('typeid', p[i].typeid)
  5423.                 .data('index', p[i].index)
  5424.                 .append(
  5425.                     $(document.createElement('img'))
  5426.                         .prop('alt', t[1] + ' (#' + p[i].index + ')')
  5427.                         .prop('title', t[1] + ' (#' + p[i].index + ')')
  5428.                         .prop('src', '//image.eveonline.com/Type/' + t[0] + '_64.png')
  5429.                 );
  5430.  
  5431.             var angle = (pindex / projectable) * 2 * Math.PI;
  5432.             var top = -size * .5 * Math.cos(angle) - 28;
  5433.             var left = size * .5 * Math.sin(angle) - 28
  5434.  
  5435.             source.css({ top: top + 'px', left: left + 'px' });
  5436.  
  5437.             osmium_ctxmenu_bind(source, (function(source, clfsource) {
  5438.                 return function() {
  5439.                     var menu = osmium_ctxmenu_create();
  5440.                     var t = osmium_types[clfsource.typeid];
  5441.  
  5442.                     osmium_ctxmenu_add_option(menu, t[1], function() {}, {
  5443.                         enabled: false
  5444.                     });
  5445.  
  5446.                     osmium_ctxmenu_add_separator(menu);
  5447.  
  5448.                     osmium_ctxmenu_add_subctxmenu(menu, "Project on", function() {
  5449.                         var smenu = osmium_ctxmenu_create();
  5450.                         var ntargets = 0;
  5451.  
  5452.                         $('section#projected div.pr-loadout').each(function() {
  5453.                             var div = $(this);
  5454.                             var tkey = div.data('key');
  5455.                             if(tkey === key) return;
  5456.                             ++ntargets;
  5457.                             osmium_ctxmenu_add_option(smenu, div.data('title'), function() {
  5458.                                 jsPlumb.connect({
  5459.                                     source: source,
  5460.                                     target: div
  5461.                                 });
  5462.                             }, {});
  5463.                         });
  5464.  
  5465.                         if(ntargets === 0) {
  5466.                             osmium_ctxmenu_add_option(smenu, "No targets available", function() {}, {
  5467.                                 enabled: false
  5468.                             });
  5469.                         }
  5470.  
  5471.                         return smenu;
  5472.                     }, {});
  5473.  
  5474.                     osmium_ctxmenu_add_option(menu, "Clear target", function() {
  5475.                         jsPlumb.select({ source: source }).detach();
  5476.                     }, { 'default': true });
  5477.  
  5478.                     osmium_ctxmenu_add_separator(menu);
  5479.  
  5480.                     osmium_ctxmenu_add_option(menu, "Show module info", function() {
  5481.                         osmium_showinfo({
  5482.                             remote: key,
  5483.                             type: "module",
  5484.                             slottype: osmium_types[source.data('typeid')][3],
  5485.                             index: source.data('index')
  5486.                         });
  5487.                     }, { icon: osmium_showinfo_sprite_position });
  5488.  
  5489.                     return menu;
  5490.                 }
  5491.             })(source, p[i]));
  5492.  
  5493.             jsPlumb.makeSource(source, {
  5494.                 anchor: [ 0.5, 0.5 ],
  5495.                 paintStyle: { fillStyle: 'hsl(' + proj.data('hue') + ', 50%, 50%)' },
  5496.                 connectorStyle: {
  5497.                     strokeStyle: 'hsla(' + proj.data('hue') + ', 50%, 50%, 0.5)',
  5498.                     lineWidth: 5
  5499.                 }
  5500.             });
  5501.  
  5502.             ul.append(source);
  5503.             ++pindex;
  5504.         }
  5505.     }
  5506.  
  5507.     proj.append(ul);
  5508.     var cap = osmium_gen_capacitor(1000, 1000);
  5509.     proj.append(cap);
  5510.     osmium_regen_remote_capacitor(proj);
  5511.  
  5512.     jsPlumb.makeTarget(proj, {
  5513.         anchor: [ 0.5, 0.5 ],
  5514.         paintStyle: { fillStyle: 'hsl(' + proj.data('hue') + ', 50%, 50%)' }
  5515.     });
  5516.     jsPlumb.draggable(proj);
  5517.  
  5518.     if(!$("section#projected").hasClass('fs')) {
  5519.         jsPlumb.toggleDraggable(proj);
  5520.     }
  5521.  
  5522.     osmium_ctxmenu_bind(proj, function() {
  5523.         var menu = osmium_ctxmenu_create();
  5524.  
  5525.         osmium_ctxmenu_add_option(menu, proj.data('title'), function() {}, { enabled: false });
  5526.  
  5527.         osmium_ctxmenu_add_separator(menu);
  5528.  
  5529.         osmium_ctxmenu_add_subctxmenu(menu, "Use skills", (function(key) {
  5530.             return function() {
  5531.                 var smenu = osmium_ctxmenu_create();
  5532.                 var clf = key === 'local' ? osmium_clf : osmium_clf['X-Osmium-remote'][key];
  5533.  
  5534.                 for(var i = 0; i < osmium_skillsets.length; ++i) {
  5535.                     osmium_ctxmenu_add_option(smenu, osmium_skillsets[i], (function(sname) {
  5536.                         return function() {
  5537.                             if(key === 'local') {
  5538.                                 clf.metadata['X-Osmium-skillset'] = sname;
  5539.                             } else {
  5540.                                 clf.skillset = sname;
  5541.                             }
  5542.  
  5543.                             osmium_undo_push();
  5544.                             osmium_commit_clf();
  5545.                         };
  5546.                     })(osmium_skillsets[i]), {
  5547.                         toggled: (key === 'local' && clf.metadata['X-Osmium-skillset'] === osmium_skillsets[i])
  5548.                             || (key !== 'local' && "skillset" in clf && clf.skillset === osmium_skillsets[i])
  5549.                     });
  5550.                 }
  5551.  
  5552.                 return smenu;
  5553.             };
  5554.         })(proj.data('key')), { icon: "//image.eveonline.com/Type/3327_64.png" });
  5555.  
  5556.         if(proj.data('key') !== 'local') {
  5557.             osmium_ctxmenu_add_separator(menu);
  5558.         }
  5559.  
  5560.         if(proj.data('key') !== 'local' && !osmium_loadout_readonly) {
  5561.             osmium_ctxmenu_add_option(menu, "Edit fitting…", (function(key) {
  5562.                 return function() {
  5563.                     var hdr = $(document.createElement('header'));
  5564.                     hdr.append($(document.createElement('h2')).text(
  5565.                         'Edit remote loadout #' + key
  5566.                     ));
  5567.  
  5568.                     var form = $(document.createElement('form'));
  5569.                     var tbody = $(document.createElement('tbody'));
  5570.  
  5571.                     var input = $(document.createElement('input'))
  5572.                         .prop('type', 'text')
  5573.                         .prop('placeholder', 'Loadout URI, DNA string or gzclf:// data')
  5574.                         .prop('name', 'm-remote-fitting')
  5575.                         .prop('id', 'm-remote-fitting')
  5576.                     ;
  5577.  
  5578.                     if("X-Osmium-remote" in osmium_clf
  5579.                        && key in osmium_clf['X-Osmium-remote']
  5580.                        && 'fitting' in osmium_clf['X-Osmium-remote'][key]) {
  5581.                         input.val(osmium_clf['X-Osmium-remote'][key].fitting);
  5582.                     }
  5583.  
  5584.                     tbody.append(
  5585.                         $(document.createElement('tr'))
  5586.                             .append(
  5587.                                 $(document.createElement('th'))
  5588.                                     .append(
  5589.                                         $(document.createElement('label'))
  5590.                                             .text('Fitting')
  5591.                                             .prop('for', 'm-remote-fitting')
  5592.                                     )
  5593.                             )
  5594.                             .append(
  5595.                                 $(document.createElement('td'))
  5596.                                     .append(input)
  5597.                             )
  5598.                     );
  5599.  
  5600.                     var select = $(document.createElement('select'))
  5601.                         .prop('name', 'm-remote-skillset')
  5602.                         .prop('id', 'm-remote-skillset')
  5603.                         .on('change', function() {
  5604.                             osmium_clf['X-Osmium-remote'][key].skillset = $(this).val();
  5605.                             osmium_undo_push();
  5606.                             osmium_commit_clf();
  5607.                         })
  5608.                     ;
  5609.  
  5610.                     for(var i = 0; i < osmium_skillsets.length; ++i) {
  5611.                         select.append(
  5612.                             $(document.createElement('option'))
  5613.                                 .text(osmium_skillsets[i])
  5614.                                 .prop('value', osmium_skillsets[i])
  5615.                         );
  5616.                     }
  5617.  
  5618.                     if("X-Osmium-remote" in osmium_clf
  5619.                        && key in osmium_clf['X-Osmium-remote']
  5620.                        && 'skillset' in osmium_clf['X-Osmium-remote'][key]) {
  5621.                         select.val(osmium_clf['X-Osmium-remote'][key].skillset);
  5622.                     }
  5623.  
  5624.                     tbody.append(
  5625.                         $(document.createElement('tr'))
  5626.                             .append(
  5627.                                 $(document.createElement('th'))
  5628.                                     .append(
  5629.                                         $(document.createElement('label'))
  5630.                                             .text('Skills')
  5631.                                             .prop('for', 'm-remote-skillset')
  5632.                                     )
  5633.                             )
  5634.                             .append(
  5635.                                 $(document.createElement('td'))
  5636.                                     .append(select)
  5637.                             )
  5638.                     );
  5639.  
  5640.                     tbody.append(
  5641.                         $(document.createElement('tr'))
  5642.                             .append($(document.createElement('th')))
  5643.                             .append(
  5644.                                 $(document.createElement('td'))
  5645.                                     .addClass('l')
  5646.                                     .append(
  5647.                                         $(document.createElement('input'))
  5648.                                             .prop('type', 'submit')
  5649.                                             .prop('value', 'Use fitting')
  5650.                                     )
  5651.                             )
  5652.                     );
  5653.                    
  5654.                     form
  5655.                         .prop('id', 'm-remote')
  5656.                         .append(
  5657.                             $(document.createElement('table'))
  5658.                                 .append(tbody)
  5659.                         )
  5660.                         .on('submit', function(e) {
  5661.                             e.preventDefault();
  5662.                             var form = $(this);
  5663.  
  5664.                             form.find('input, select').prop('disabled', true);
  5665.                             form
  5666.                                 .find('input[type="submit"]')
  5667.                                 .after(
  5668.                                     $(document.createElement('span'))
  5669.                                         .addClass('spinner')
  5670.                                 )
  5671.                             ;
  5672.  
  5673.                             osmium_clf['X-Osmium-remote'][key].fitting =
  5674.                                 form.find('input#m-remote-fitting').val();
  5675.                             osmium_clf['X-Osmium-remote'][key].skillset =
  5676.                                 form.find('select#m-remote-skillset').val();
  5677.  
  5678.                             osmium_projected_regen_remote(key, function() {
  5679.                                 osmium_modal_clear();
  5680.                             }, function(errors) {
  5681.                                 form.find('span.spinner').remove();
  5682.                                 form.find('input, select').prop('disabled', false);
  5683.  
  5684.                                 form.find('tr.error_message').remove();
  5685.                                 form.find('tr.error').removeClass('error');
  5686.  
  5687.                                 for(var i = 0; i < errors.length; ++i) {
  5688.                                     input.closest('tr').addClass('error').before(
  5689.                                         $(document.createElement('tr'))
  5690.                                             .addClass('error_message')
  5691.                                             .append(
  5692.                                                 $(document.createElement('td'))
  5693.                                                     .prop('colspan', '2')
  5694.                                                     .append(
  5695.                                                         $(document.createElement('p')).text(errors[i])
  5696.                                                     )
  5697.                                             )
  5698.                                     );
  5699.                                 }
  5700.                             });
  5701.  
  5702.                             return false;
  5703.                         })
  5704.                     ;
  5705.  
  5706.                     osmium_modal([ hdr, form ]);
  5707.                 };
  5708.             })(key), { 'default': true });
  5709.  
  5710.             osmium_ctxmenu_add_option(menu, "Remove fitting", function() {
  5711.                 jsPlumb.doWhileSuspended(function() {
  5712.                     jsPlumb.detachAllConnections(proj);
  5713.                     proj.find('ul > li').each(function() {
  5714.                         jsPlumb.detachAllConnections($(this));
  5715.                     });
  5716.                     proj.remove();
  5717.                 });
  5718.  
  5719.                 delete osmium_clf['X-Osmium-remote'][key];
  5720.                 osmium_commit_undo_deferred();
  5721.             }, {});
  5722.         }
  5723.  
  5724.         if(proj.data('key') !== 'local' && osmium_loadout_readonly) {
  5725.             osmium_ctxmenu_add_option(menu, "View fitting", function() {
  5726.                 var loc = window.location.href.split("#")[0];
  5727.                 var match = loc.match(/^(.+?)\/remote\/(.+)$/);
  5728.  
  5729.                 if(match !== null) {
  5730.                     var localkey;
  5731.  
  5732.                     if(key.toString() === match[2]) {
  5733.                         window.location.assign(match[1]);
  5734.                         return;
  5735.                     } else {
  5736.                         localkey = key;
  5737.                     }
  5738.  
  5739.                     window.location.assign(match[1] + "/remote/" + encodeURIComponent(localkey));
  5740.                 } else {
  5741.                     window.location.assign(loc + '/remote/' + encodeURIComponent(key));
  5742.                 }
  5743.             }, {});
  5744.         }
  5745.  
  5746.         if(proj.data('shiptypeid')) {
  5747.             /* No point allowing show ship info on shallow pools */
  5748.  
  5749.             osmium_ctxmenu_add_separator(menu);
  5750.  
  5751.             osmium_ctxmenu_add_option(menu, "Show ship info", function() {
  5752.                 osmium_showinfo({
  5753.                     remote: key,
  5754.                     type: "ship"
  5755.                 });
  5756.             }, { icon: osmium_showinfo_sprite_position, 'default': osmium_loadout_readonly });
  5757.         }
  5758.  
  5759.         return menu;
  5760.     });
  5761.  
  5762.     return proj;
  5763. };
  5764.  
  5765. osmium_projected_regen_local = function() {
  5766.     var oldlocal = $("section#projected div.projected-local");
  5767.     if(oldlocal.length === 0) return; /* Not yet generated, will be done later */
  5768.  
  5769.     var local = osmium_create_projected("local", osmium_clf, oldlocal.index());
  5770.     osmium_projected_replace_graceful(oldlocal, local);
  5771. };
  5772.  
  5773. osmium_projected_regen_remote = function(key, onsuccess, onerror) {
  5774.     var t = $("section#projected div.pr-loadout.projected-" + key);
  5775.  
  5776.     osmium_commit_clf({
  5777.         params: { "remoteclf": t.data('key') },
  5778.         success: function(payload) {
  5779.             if(!("remote-clf" in payload)) return;
  5780.  
  5781.             osmium_clf['X-Osmium-remote'][t.data("key")] = payload['remote-clf'];
  5782.             osmium_undo_push();
  5783.  
  5784.             osmium_projected_replace_graceful(
  5785.                 t,
  5786.                 osmium_create_projected(
  5787.                     t.data('key'),
  5788.                     osmium_clf['X-Osmium-remote'][t.data('key')],
  5789.                     t.index()
  5790.                 )
  5791.             );
  5792.  
  5793.             if('remote-errors' in payload) {
  5794.                 if(typeof onerror === 'function') onerror(payload['remote-errors']);
  5795.             } else {
  5796.                 if(typeof onsuccess === 'function') onsuccess(payload);
  5797.             }
  5798.         }
  5799.     });
  5800. };
  5801.  
  5802. osmium_projected_replace_graceful = function(stale, fresh) {
  5803.     var cssprops = [ "left", "top", "right", "bottom" ];
  5804.     for(var i = 0; i < cssprops.length; ++i) {
  5805.         fresh.css(cssprops[i], stale.css(cssprops[i]));
  5806.     }
  5807.  
  5808.     jsPlumb.doWhileSuspended(function() {
  5809.         var newconnections = [];
  5810.  
  5811.         osmium_user_initiated_push(false);
  5812.         /* Keep all incoming projections */
  5813.         jsPlumb.select({
  5814.             target: stale
  5815.         }).each(function(conn) {
  5816.             var source = conn.source;
  5817.             jsPlumb.detach(conn);
  5818.             newconnections.push({
  5819.                 source: source,
  5820.                 target: fresh
  5821.             });
  5822.         });
  5823.         osmium_user_initiated_pop();
  5824.  
  5825.         osmium_user_initiated_push(false);
  5826.         /* Keep outgoing projections if modules match */
  5827.         stale.find('ul > li').each(function() {
  5828.             var source = $(this);
  5829.             var newsource = fresh.find('ul > li').filter(function() {
  5830.                 var t = $(this);
  5831.                 return t.data('typeid') == source.data('typeid') && t.data('index') == source.data('index');
  5832.             }).first();
  5833.  
  5834.             var connections = jsPlumb.select({
  5835.                 source: source
  5836.             });
  5837.  
  5838.             if(newsource.length === 1) {
  5839.                 /* Found a matching module, transfer all connections to it */
  5840.                 connections.each(function(conn) {
  5841.                     var target = conn.target;
  5842.                     jsPlumb.detach(conn);
  5843.                     newconnections.push({
  5844.                         source: newsource,
  5845.                         target: target
  5846.                     });
  5847.                 });
  5848.             } else {
  5849.                 /* Drop connections */
  5850.                 osmium_user_initiated_pop();
  5851.                 connections.detach();
  5852.                 osmium_user_initiated_push(false);
  5853.             }
  5854.  
  5855.             jsPlumb.unmakeSource(source);
  5856.         });
  5857.         osmium_user_initiated_pop();
  5858.  
  5859.         jsPlumb.unmakeTarget(stale);
  5860.         stale.before(fresh);
  5861.         stale.remove();
  5862.  
  5863.         for(var i = 0; i < newconnections.length; ++i) {
  5864.             jsPlumb.connect(newconnections[i]);
  5865.         }
  5866.     });
  5867.  
  5868.     jsPlumb.recalculateOffsets(fresh);
  5869. };
  5870.  
  5871. osmium_regen_remote_capacitor = function(key_or_element) {
  5872.     if(typeof osmium_capacitors !== "object") return;
  5873.  
  5874.     var c, s;
  5875.  
  5876.     if(typeof key_or_element !== "object") {
  5877.         if(!(key_or_element in osmium_capacitors)) return;
  5878.         s = $("section#projected div.pr-loadout.projected-" + key_or_element);
  5879.         c = osmium_capacitors[key_or_element];
  5880.     } else {
  5881.         s = key_or_element;
  5882.         if(!(s.data('key') in osmium_capacitors)) return;
  5883.         c = osmium_capacitors[s.data('key')];
  5884.     }
  5885.  
  5886.     if(s.length !== 1) return;
  5887.     s = s.find('svg');
  5888.  
  5889.     if(c.capacity > 0) {
  5890.         var delta = -1000 * c.delta;
  5891.         if(delta >= 0) delta = '+' + delta.toFixed(1);
  5892.         else delta = delta.toFixed(1);
  5893.  
  5894.         s.data({
  5895.             capacity: c.capacity,
  5896.             current: c.stable ? (c.capacity * c.stable_fraction) : 0
  5897.         });
  5898.         s.parent().prop(
  5899.             'title', delta + ' GJ/s, '
  5900.                 + (c.stable ? ((100 * c.stable_fraction).toFixed(1) + '%') : c.depletion_time)
  5901.         );
  5902.     } else {
  5903.         s.data({ capacity: 1000, current: 1000 });
  5904.         s.parent().prop('title', ''); /* XXX: .removeProp() gives "undefined" as tooltip */
  5905.     }
  5906.  
  5907.     s.trigger('redraw');
  5908. };
  5909.  
  5910. osmium_regen_offsets = function() {
  5911.     var divs = $("section#projected form#projected-list").children('div.pr-loadout');
  5912.     divs.each(function() {
  5913.         jsPlumb.recalculateOffsets($(this));
  5914.     });
  5915. }
  5916.  
  5917. osmium_commit_undo_deferred_timeoutid = undefined;
  5918. osmium_commit_undo_deferred = function(delay) {
  5919.     if(!osmium_user_initiated) return;
  5920.  
  5921.     if(delay === undefined) delay = 100;
  5922.  
  5923.     if(osmium_commit_undo_deferred_timeoutid !== undefined) {
  5924.         clearTimeout(osmium_commit_undo_deferred_timeoutid);
  5925.     } else {
  5926.         osmium_clfspinner_push();
  5927.     }
  5928.  
  5929.     osmium_commit_undo_deferred_timeoutid = setTimeout(function() {
  5930.         osmium_commit_undo_deferred_timeoutid = undefined;
  5931.         osmium_commit_clf();
  5932.         osmium_undo_push();
  5933.         osmium_clfspinner_pop();
  5934.     }, delay);
  5935. };
  5936. /* Osmium
  5937.  * Copyright (C) 2013, 2014, 2015 Romain "Artefact2" Dalmaso <artefact2@gmail.com>
  5938.  *
  5939.  * This program is free software: you can redistribute it and/or modify
  5940.  * it under the terms of the GNU Affero General Public License as published by
  5941.  * the Free Software Foundation, either version 3 of the License, or
  5942.  * (at your option) any later version.
  5943.  *
  5944.  * This program is distributed in the hope that it will be useful,
  5945.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  5946.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  5947.  * GNU Affero General Public License for more details.
  5948.  *
  5949.  * You should have received a copy of the GNU Affero General Public License
  5950.  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  5951.  */
  5952.  
  5953. /*<<< require snippet loadout_common >>>*/
  5954. /*<<< require snippet view_loadout-presets >>>*/
  5955. /*<<< require snippet new_loadout-ship >>>*/
  5956. /*<<< require snippet new_loadout-modules >>>*/
  5957. /*<<< require snippet new_loadout-drones >>>*/
  5958. /*<<< require snippet new_loadout-implants >>>*/
  5959. /*<<< require snippet new_loadout-beacons >>>*/
  5960. /*<<< require snippet new_loadout-remote >>>*/
  5961.  
  5962. $(function() {
  5963.     $("section#remote").on('made_visible', function() {
  5964.         jsPlumb.setSuspendDrawing(false);
  5965.     });
  5966.  
  5967.     osmium_tabify($("div#vlmain > ul.tabs"), 0);
  5968.  
  5969.     jsPlumb.setSuspendDrawing(true);
  5970.  
  5971.     var odata = $("div#osmium-data");
  5972.  
  5973.     osmium_load_common_data();
  5974.     osmium_clf_slots = odata.data('clfslots');
  5975.     osmium_capacitors = odata.data('capacitors');
  5976.     osmium_ia = odata.data('ia');
  5977.  
  5978.     osmium_load_static_client_data(osmium_cdatastaticver, function(cdata) {
  5979.         osmium_gen();
  5980.  
  5981.         /* XXX. On T3 loadouts, gen_ship() will overwrite clf_slots
  5982.          * using the bare T3 slotcounts (0/0/0).
  5983.          *
  5984.          * Usually this is later corrected by committing the CLF and
  5985.          * reading the slot counts back from process_clf. But in the
  5986.          * initial phase, we don't commit the CLF. So undo the mess
  5987.          * gen_ship() did, then regenerate the modules again.
  5988.          */
  5989.         osmium_clf_slots = odata.data('clfslots');
  5990.         osmium_gen_modules();
  5991.        
  5992.         osmium_init();
  5993.  
  5994.         if($("a[href='#remote']").parent().hasClass('active')) {
  5995.             $("section#remote").trigger('made_visible');
  5996.         }
  5997.  
  5998.         osmium_highlight_missing_prereqs(odata.data('missingprereqs'));
  5999.  
  6000.         osmium_user_initiated_push(true);
  6001.  
  6002.         osmium_undo_push();
  6003.         osmium_init_votes();
  6004.         osmium_init_comment_replies();
  6005.         osmium_init_export();
  6006.     });
  6007.  
  6008.     $('body').on('click', '.confirm', function() {
  6009.         return confirm("You are about to do a destructive action.\n\nIt cannot be undone.\n\nContinue?");
  6010.     });
  6011.  
  6012.     $("h1#vltitle > ul.tags > li.retag").click(function() {
  6013.         var t = $(this);
  6014.         var ul = t.parent();
  6015.         var h1 = ul.parent();
  6016.  
  6017.         var form = $(document.createElement('form'));
  6018.         form.addClass('retag');
  6019.         form.prop('method', 'post');
  6020.         form.prop('action', osmium_relative + '/internal/retag/' + $("section#ship").data('loadoutid'));
  6021.  
  6022.         form.append(
  6023.             $(document.createElement('input'))
  6024.                 .prop('type', 'hidden')
  6025.                 .prop('name', 'o___csrf')
  6026.                 .val(osmium_token)
  6027.         );
  6028.  
  6029.         var inp = $(document.createElement('input'));
  6030.         inp.prop('type', 'text');
  6031.         inp.prop('placeholder', 'Space-separated list of tags…');
  6032.         inp.prop('name', 'tags');
  6033.  
  6034.         var tags = [];
  6035.         ul.children('li').not('.retag').each(function() {
  6036.             tags.push($(this).text());
  6037.         });
  6038.  
  6039.         inp.val(tags.join(' '));
  6040.         form.append(inp);
  6041.  
  6042.         form.append([
  6043.             ' ',
  6044.             $(document.createElement('input'))
  6045.                 .prop('type', 'submit')
  6046.                 .val('Update tags')
  6047.         ]);
  6048.  
  6049.         form.append([
  6050.             ' ',
  6051.             $(document.createElement('a'))
  6052.                 .addClass('cancel')
  6053.                 .text('Cancel')
  6054.                 .click(function() {
  6055.                     form.remove();
  6056.                     ul.show();
  6057.                 })
  6058.         ]);
  6059.  
  6060.         form.submit(function(e) {
  6061.             e.preventDefault();
  6062.             var hidden = form.find('input[type="submit"], a.cancel');
  6063.             var spinner = $(document.createElement('span')).addClass('spinner');
  6064.             var postdata = form.serialize();
  6065.  
  6066.             hidden.hide();
  6067.             form.append(spinner);
  6068.             inp.prop('disabled', true);
  6069.  
  6070.             $.ajax({
  6071.                 type: 'POST',
  6072.                 url: form.prop('action'),
  6073.                 data: postdata,
  6074.                 dataType: 'json',
  6075.                 complete: function() {
  6076.                     inp.prop('disabled', false);
  6077.                     spinner.remove();
  6078.                     hidden.show();
  6079.                 },
  6080.                 success: function(payload) {
  6081.                     form.find('p.error_box').remove();
  6082.  
  6083.                     if("error" in payload) {
  6084.                         form.append(
  6085.                             $(document.createElement('p'))
  6086.                                 .addClass('error_box')
  6087.                                 .text(payload.error)
  6088.                         );
  6089.                     }
  6090.  
  6091.                     if("dest" in payload) {
  6092.                         document.location.replace(osmium_relative + '/' + payload.dest);
  6093.                     }
  6094.                 }
  6095.             });
  6096.         });
  6097.  
  6098.         form.hide();
  6099.         h1.append(form);
  6100.         ul.hide();
  6101.         form.show();
  6102.         inp.focus();
  6103.     });
  6104.  
  6105.     if(window.history && Array.isArray(history.state) && history.state.length === 2) {
  6106.         osmium_clf = $.extend(true, {}, history.state[1]);
  6107.         osmium_commit_clf();
  6108.         osmium_gen();
  6109.     }
  6110. });
  6111.  
  6112. osmium_loadout_readonly = true;
  6113. osmium_clftype = 'view';
  6114.  
  6115. osmium_init = function() {
  6116.     osmium_init_ship();
  6117.     osmium_init_presets();
  6118.     $("section#loadout > section#modules > div.slots > ul > li[data-typeid] span.charge").remove();
  6119.     osmium_init_modules();
  6120.     osmium_init_fattribs();
  6121.     osmium_init_drones();
  6122.     osmium_init_implants();
  6123.     osmium_init_beacons();
  6124.     osmium_init_remote();
  6125. };
  6126.  
  6127. osmium_gen = function() {
  6128.     osmium_gen_ship();
  6129.     osmium_gen_modules();
  6130.     osmium_gen_fattribs();
  6131.     osmium_gen_drones();
  6132.     osmium_gen_implants();
  6133.     osmium_gen_beacons();
  6134.     osmium_gen_remote();
  6135. };
  6136.  
  6137. osmium_init_votes = function() {
  6138.     $("section#ship > div.votes > a, section#comments > div.comment > div.votes > a").click(function() {
  6139.         var t = $(this);
  6140.         var score = t.parent().children('strong');
  6141.         var delta = 0;
  6142.         var upvoted = t.parent().children('a.upvote').hasClass('voted');
  6143.         var downvoted = t.parent().children('a.downvote').hasClass('voted');
  6144.         var action;
  6145.  
  6146.         if(t.hasClass('voted')) {
  6147.             t.removeClass('voted');
  6148.             delta += t.hasClass('upvote') ? -1 : 1;
  6149.             action = 'rmvote';
  6150.         } else {
  6151.             t.parent().children('a.voted').each(function() {
  6152.                 delta += $(this).removeClass('voted').hasClass('upvote') ? -1 : 1;
  6153.             });
  6154.  
  6155.             t.addClass('voted');
  6156.             if(t.hasClass('upvote')) {
  6157.                 delta += 1;
  6158.                 action = 'castupvote';
  6159.             } else {
  6160.                 delta += -1;
  6161.                 action = 'castdownvote';
  6162.             }
  6163.         }
  6164.  
  6165.         score.text(parseInt(score.text(), 10) + delta);
  6166.  
  6167.         var targettype = t.parent().data('targettype');
  6168.         var opts = {
  6169.             'o___csrf': osmium_token,
  6170.             loadoutid: $("section#ship").data('loadoutid')
  6171.         };
  6172.  
  6173.         if(targettype == 'comment') {
  6174.             opts.commentid = t.parent().parent().data('commentid');
  6175.         }
  6176.  
  6177.         $.ajax({
  6178.             type: 'POST',
  6179.             url: osmium_relative + '/internal/vote/' + targettype + '/' + action,
  6180.             data: opts,
  6181.             dataType: 'json',
  6182.             success: function(data) {
  6183.                 if(!data['success']) {
  6184.                     score.text(parseInt(score.text(), 10) - delta);
  6185.  
  6186.                     if(upvoted) {
  6187.                         t.parent().children('a.upvote').addClass('voted');
  6188.                     } else {
  6189.                         t.parent().children('a.upvote').removeClass('voted');
  6190.                     }
  6191.  
  6192.                     if(downvoted) {
  6193.                         t.parent().children('a.downvote').addClass('voted');
  6194.                     } else {
  6195.                         t.parent().children('a.downvote').removeClass('voted');
  6196.                     }
  6197.  
  6198.                     var error = $(document.createElement('div'));
  6199.                     error.addClass('verror');
  6200.                     error.text(data['error']);
  6201.                     error.append('<br /><small>(click to close)</small>');
  6202.                     error.hide();
  6203.                     error.click(function() {
  6204.                         $(this).fadeOut(250);
  6205.                     });
  6206.                     t.parent().append(error);
  6207.                     error.fadeIn(250);
  6208.                 }
  6209.             }
  6210.         });
  6211.     });
  6212. };
  6213.  
  6214. osmium_init_comment_replies = function() {
  6215.     $("section#comments > div.comment > header > div.meta a.add_comment").click(function() {
  6216.         $(this).closest('div.comment').find('ul.replies > li.new').fadeIn(250).find('textarea').focus();
  6217.     });
  6218.     $("section#comments > div.comment > ul.replies > li.new > form > a.cancel").click(function() {
  6219.         $(this).parent().parent().hide().find('textarea').val('');
  6220.     });
  6221. };
  6222.  
  6223. osmium_init_export = function() {
  6224.     $("section#export a[type]").click(function(e) {
  6225.         e.preventDefault();
  6226.  
  6227.         var t = $(this);
  6228.  
  6229.         osmium_clfspinner_push();
  6230.         $.ajax({
  6231.             url: t.attr('href'),
  6232.             dataType: 'text',
  6233.             success: function(payload) {
  6234.                 osmium_modal_rotextarea(t.text(), payload);
  6235.             },
  6236.             error: function() {
  6237.                 window.location.assign(t.attr('href'));
  6238.             },
  6239.             complete: function() {
  6240.                 osmium_clfspinner_pop();
  6241.             }
  6242.         });
  6243.     });
  6244.  
  6245.     $("section#export, h1#vltitle").on('click', 'a[data-ccpdna]', function() {
  6246.         var t = $(this);
  6247.         t.blur();
  6248.  
  6249.         if(typeof CCPEVE === 'object' && 'showFitting' in CCPEVE && typeof CCPEVE.showFitting === 'function') {
  6250.             CCPEVE.showFitting(t.data('ccpdna'));
  6251.         }
  6252.     });
  6253. };
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement