Spacom.Addons.Fleets.Sort

Add a sorting and filters for fleets tabs

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Spacom.Addons.Fleets.Sort
// @version      0.1.4
// @namespace    http://dimio.org/
// @description  Add a sorting and filters for fleets tabs
// @author       dimio ([email protected])
// @license      MIT
// @homepage     https://github.com/dimio/userscripts-spacom.ru-addons
// @supportURL   https://github.com/dimio/userscripts-spacom.ru-addons/issues
// @supportURL   https://spacom.ru/forum/discussion/47/polzovatelskie-skripty
// @encoding     utf-8
// @match        http*://spacom.ru/?act=game/map*
// @include      http*://spacom.ru/?act=game/map*
// @run-at       document-end
// ==/UserScript==
console.log(GM_info.script.name, 'booted v.', GM_info.script.version);
const homePage = GM_info.scriptMetaStr.split('\n')[6].split(' ')[6];

const ERR_MSG = {
  NO_LIB: `Для работы ${GM_info.script.name} необходимо установить и включить последние версии следующих дополнений:
<ul>
<li>Spacom.Addons</li>
<li>Spacom.Addons.Fleets.Common</li>
</ul>
<a href="${homePage}">${homePage}</a>`,
  NO_FILTER_PARAMS: `Не найдены параметры для фильтрации.
Для сброса фильтров закройте и откройте заново текущую вкладку флотов.`,
  NO_FILTER_SELECTED: `Условия фильтрации не указаны.`,
};

(function (window) {
  'use strict';

  window.unsafeWindow = window.unsafeWindow || window;
  const w = unsafeWindow;
  const Addons = w.Addons;

  if (w.self !== w.top) {
    return;
  }
  if (!Addons) {
    // if (!Addons || !Addons.Fleets.Common) {
    w.showSmallMessage(ERR_MSG.NO_LIB);
    return;
  }

  function Filter(by, isExclude = false) {
    this.by = by;
    this.isExclude = isExclude;
  }

  Addons.Fleets.Sort = {
    /**
     * contains current fleets by fleet tab
     * note: use Proxy or Object.observe for watch to map.fleets changes
     * and cache filtered fleets for each tab?
     */
    fleets: [],

    filters: {},
    sort: {},
    garrisonIco: (Addons.Fleets.Common) ? Addons.Fleets.Common.garrisonIco : "5.png",

    toggleSubMenu(owner, fleetType, redraw) {
      const subMenu = `fleets_${owner}_${fleetType}`;
      // Current Fleets tab was closed, purge filters & drop filtered fleets for current tab
      if (w.sub_menu === subMenu && !redraw) {
        this.clearFilters(subMenu);
        return false;
      }
      return subMenu;
    },
    createDefaultFilters(owner, fleetType) {
      if (!Addons.Common.isVariableDefined(this.filters[w.sub_menu])) {
        this.filters[w.sub_menu] = [
          new Filter({'type': [fleetType], 'owner': [owner]}),
          new Filter({'weight': ["0"]}, true)
        ];
      }
      if (!Addons.Common.isVariableDefined(this.sort[w.sub_menu])) {
        this.sort[w.sub_menu] = {};
      }
    },
    clearFilters(subMenu) {
      delete this.filters[subMenu];
      delete this.sort[subMenu];
    },
    showFleets(opt) {
      const owner = opt.owner;
      const fleetType = opt.fleetType || 'fleet';
      const sortBy = opt.sortBy || 'weight';
      let redraw = opt.redraw || false; //true for sort & filter buttons

      w.map.clearInfo();

      if ((w.sub_menu = this.toggleSubMenu(owner, fleetType, redraw)) === false) {
        return false;
      }

      this.createDefaultFilters(owner, fleetType);
      this.fleets = w.map.fleets.slice();
      this.fleets.forEach(fleet => {
        fleet['type'] = this.getFleetType(fleet);
        // hack for sorting by ship type (== ship icon) for garrisons
        fleet.ico = this.setDummyGarrisonIco(fleet);
      });

      // apply filters for current fleets tab
      this.filters[w.sub_menu].forEach(filter => {
        this.fleets = this.filterBy(this.fleets, filter);
      });

      // apply sorting for current fleets tab
      this.sortFleets(owner, sortBy, redraw);
      // show fleets for current fleets tab
      this.drawFleetsTab(this.fleets);
      // add sorting & filtering buttons to current fleets tab
      this.addButtons(owner, fleetType);
      // add mark/unmark buttons
      if (Addons.Fleets.MarkOnMap) {
        Addons.Fleets.MarkOnMap.init();
      }
      // add summary button
      if (Addons.Fleets.Summary) {
        Addons.Fleets.Summary.init();
      }

      return true;
    },
    showFilteredFleets(owner, fleetType, filterBy) {
      const filterParams = this.getFilterParams(filterBy);
      if (Addons.Common.isObjNotEmpty(filterParams)) {
        this.getFilter(filterParams, filterBy)
        .then(
          filter => {
            this.filters[w.sub_menu].push(filter);
            this.showFleets({
              'owner': owner,
              'fleetType': fleetType,
              'redraw': true,
            })
          },
          error => w.showSmallMessage(ERR_MSG.NO_FILTER_SELECTED)
        );
      }
      else {
        w.showSmallMessage(ERR_MSG.NO_FILTER_PARAMS);
      }
    },
    getFilterParams(filterBy) {
      let filterParams = {};
      this.fleets.forEach(fleet => {
        filterParams[this.getFilterParam(filterBy, fleet)] = fleet[filterBy];
      });
      filterParams = Object.fromEntries(
        Object.entries(filterParams).sort((a, b) => {
          return Addons.Sort.alphabetically(a[0], b[0])
        })
      );
      return filterParams;
    },
    getFilterParam(filterBy, fleet) {
      if (filterBy === 'star_id') {
        const star = w.map.stars[fleet[filterBy]];
        return star.name + '&nbsp;' + star.x + ':' + star.y;
      }
      return fleet[filterBy];
    },
    getFilter(params, filterBy) {
      const isExcludeId = 'filtering-list-exclude';
      const filter = new Filter({[filterBy]: []});

      let message = `Отфильтровать по:</br>
        <select id='fl_filter' size='
        ${Object.keys(params).length < 8 ? Object.keys(params).length + 1 : 8}'
        multiple='multiple'>`;
      for (const i in params) {
        if (params.hasOwnProperty(i)) {
          message += `<option value="${params[i]}">${i}</option>`;
        }
      }
      message += '</select></br>';
      message += `<input type="checkbox" id="${isExcludeId}"/>`;
      message += `<label for="${isExcludeId}">Исключить выбранное</label>`;

      w.showSmallMessage(message);

      $(`#${isExcludeId}`).change(function () {
        filter.isExclude = $(this).is(':checked');
      });
      $('#fl_filter').change(function () {
        filter.by[filterBy] = $(this).val();
      });

      return new Promise(function (resolve, reject) {
        $('#data_modal > button').removeAttr("onclick")
        .click(function () {
          $('#fl_filter').change();
          $.modal.close();
          Addons.Common.isVariableDefined(filter.by[filterBy])
            ? resolve(filter) : reject();
        });
      });
    },
    sortFleets(owner, sortBy, redraw) {
      if (Addons.Common.isVariableDefined(this.sort[w.sub_menu]["last"])
        && (!redraw || sortBy === 'weight')) {
        this.sortBy(this.fleets, this.sort[w.sub_menu]["last"], owner);
      }
      else {
        this.sortBy(this.fleets, sortBy, owner);
        if (sortBy === this.sort[w.sub_menu]["last"]) {
          this.sort[w.sub_menu]["isReverse"] = !this.sort[w.sub_menu]["isReverse"];
        }
        this.sort[w.sub_menu]["last"] = (sortBy === 'weight') ? undefined : sortBy;
      }
      if (this.sort[w.sub_menu]["isReverse"]) {
        this.fleets.reverse();
      }
    },
    getFleetType(fleet) {
      /** non-own garrisons have a `fleet.garrison: "0"`
       * own garrisons have a `fleet.garrison: "1"`
       * all garrisons name is `fleet.fleet_name: "Гарнизон"`
       * and ico is `fleet.ico: null`
       */
      return (+fleet.garrison === 0) ? 'fleet' : 'garrison';
    },
    setDummyGarrisonIco(fleet) {
      // hack for sorting by ship type (== ship icon)
      return (fleet.ico !== null) ? fleet.ico : this.garrisonIco;
    },
    filterBy(fleets, filter) {
      if (Addons.Common.isObjNotEmpty(fleets)) {
        const keys = Object.keys(filter.by);
        const values = Object.values(filter.by);

        return fleets.filter(fleet => {
          // it's a dark magic :)
          return keys.every((key, index) => {
            return filter.isExclude ?
              values[index].every((value) => {
                return fleet[key] !== value
              })
              :
              values[index].some((value) => {
                return fleet[key] === value
              })
          })
          // && +fleet.weight !== 0;
        });
      }
      return [];
    },
    sortBy(fleets, sortBy, owner) {
      switch (sortBy) {
        case 'weight':
          fleets.sort(w.fleetOrder);
          break;
        case 'fleet_speed':
          fleets.sort(this.sorter.speed);
          fleets.map((fleet) => {
            fleet.fleet_speed = parseFloat(fleet.fleet_speed).toFixed(2);
            return fleet;
          });
          break;
        case 'turn': // by fleet state
          if (owner !== 'own') {
            fleets.sort(this.sorter.state.other);
          }
          else {
            fleets.sort(this.sorter.own).reverse();
          }
          break;
        case 'ico': // by fleet type
          fleets.sort(this.sorter.type);
          break;
        case 'fleet_name': // by owner & fleet names
          if (owner === 'own') {
            fleets.sort(this.sorter.fleetName);
            break;
          }
          fleets.sort(this.sorter.fleetName);
          fleets.sort(this.sorter.playerName);
          break;
        case 'stat':
          fleets.sort(this.sorter.combatPower.hp);
          // fleets.sort(this.sorter.combatPower.health);
          fleets.reverse();
          break;
        case 'player_id':
          if (owner !== 'other') {
            break;
          }
          fleets.sort(this.sorter.playerId);
          break;
        default:
          fleets.sort(w.fleetOrder);
          break;
      }
      // sort in-place
      // return fleets;
    },
    drawFleetsTab(fleets) {
      if (fleets && Addons.Common.isObjNotEmpty(fleets)) {
        $('#items_list').append(w.tmpl('fleets_title', fleets));
        for (const fleet of fleets) {
          if (Addons.Common.isVariableDefined(fleet)) {
            w.map.showBlockFleet(fleet, fleet.owner);
          }
        }
        $('#items_list>>>[title],#items_list>>>>[title]').qtip({
          position: {
            my: 'bottom center', // at the bottom right of...
            at: 'top center', // Position my top left...
          },
          style: {
            classes: 'qtip-dark tips',
          },
        });
      }
      else {
        $('#items_list').html(
          '<div class="player_fleet_title">Нет подходящих флотов</div>');
      }
    },
    getNaviDivs() {
      const divs = {};
      divs.fleet_speed = $('#items_list .fleet_speed')[0];
      divs.fleet_name = $('#items_list .fleet_name')[0];
      divs.turn = $('#items_list .fleet_state')[0];
      divs.ico = $('#items_list .fleet_ico_list')[0];
      divs.stat = $('#items_list .fleet_stat')[0];
      return divs;
    },
    addButtons(owner, fleetType) {
      const timerID = setInterval(() => {
        const divs = this.getNaviDivs();
        if (Addons.Common.isObjNotEmpty(divs)) {
          clearInterval(timerID);
          this.addSortButtons(divs, owner, fleetType);
          this.addFilterButtons(divs, owner, fleetType);
        }
      }, 0);
    },
    addSortButtons(divs, owner, fleetType) {
      // div match a sortBy
      for (let div in divs) {
        if (divs.hasOwnProperty(div) && Addons.Common.isVariableDefined(divs[div])) {
          if (fleetType === "garrison") {
            if (div !== "ico" && div !== "stat") {
              continue;
            }
          }
          Addons.DOM.makeClickable({
            elem: divs[div],
            icon: 'fa-sort',
            css_name: `sort-by-${div}`,
            title: 'Отсортировать',
            cb: `Addons.Fleets.Sort.showFleets({owner:'${owner}',fleetType:'${fleetType}',sortBy:'${div}',redraw:true})`
          });
        }
      }
    },
    addFilterButtons(divs, owner, fleetType) {
      // div match a filterBy
      for (let div in divs) {
        if (divs.hasOwnProperty(div) && Addons.Common.isVariableDefined(divs[div])) {
          if (div === 'fleet_speed' || div === 'stat') {
            continue;
          }
          if (fleetType === 'garrison' &&
            (div === 'turn' || div === 'fleet_name')) {
            continue;
          }
          if ((owner === 'other' || owner === 'peace') &&
            div === 'fleet_name') {
            // add the additional button before current
            Addons.DOM.appendClickableIcon({
              elem: divs[div],
              icon: 'fa-id-badge',
              css_name: `filter-by-${div}`,
              title: 'Отфильтровать по владельцу',
              cb: `Addons.Fleets.Sort.showFilteredFleets('${owner}','${fleetType}','player_name')`
            });
          }
          if (div === 'turn') {
            // add the additional button before current
            Addons.DOM.appendClickableIcon({
              elem: divs[div],
              icon: 'fa-crosshairs',
              css_name: `filter-by-star_id`,
              title: 'Отфильтровать по системе',
              cb: `Addons.Fleets.Sort.showFilteredFleets('${owner}','${fleetType}','star_id')`
            });
          }

          Addons.DOM.appendClickableIcon({
            elem: divs[div],
            icon: 'fa-filter',
            css_name: `filter-by-${div}`,
            title: 'Отфильтровать',
            cb: `Addons.Fleets.Sort.showFilteredFleets('${owner}','${fleetType}','${div}')`
          });
        }
      }
    },
    init() {
      $('#navi > div:nth-child(2)').attr('onclick',
        'Addons.Fleets.Sort.showFleets({owner:\'own\'});return false;');
      $('#navi > div:nth-child(3)').attr('onclick',
        'Addons.Fleets.Sort.showFleets({owner:\'other\'});return false;');
      Addons.DOM.createNaviBarButton('Гарнизон', 1,
        'Addons.Fleets.Sort.showFleets({owner:\'own\',fleetType: \'garrison\'});return false;');
      Addons.DOM.createNaviBarButton('Союзные', 3,
        'Addons.Fleets.Sort.showFleets({owner:\'peace\'});return false;');
      Addons.DOM.createNaviBarButton('Пираты', 5,
        'Addons.Fleets.Sort.showFleets({owner:\'pirate\'});return false;');
    },
    sorter: {
      state: {
        own(a, b) {
          if (a.allow_explore > b.allow_explore) {
            return 1;
          }
          else if (a.allow_explore < b.allow_explore) {
            return -1;
          }
          if (a.allow_fly > b.allow_fly) {
            return 1;
          }
          else if (a.allow_fly < b.allow_fly) {
            return -1;
          }
          if (a.allow_transfer > b.allow_transfer) {
            return 1;
          }
          else if (a.allow_transfer < b.allow_transfer) {
            return -1;
          }
          if (a.allow_garrison > b.allow_garrison) {
            return 1;
          }
          else if (a.allow_garrison < b.allow_garrison) {
            return -1;
          }
          if (a.allow_station > b.allow_station) {
            return -1; // изучает аномалию - опускаем ниже
          }
          else if (a.allow_station < b.allow_station) {
            return 1; // готов изучить или не станция - поднять
          }
          if (a.start_turn > b.start_turn) {
            return -1; // будет дольше в полёте - опустить
          }
          else if (a.start_turn < b.start_turn) {
            return 1;
          }
          return 0;
        },
        other(a, b) {
          return Addons.Sort.numerically(a.turn, b.turn);
        },
      },
      combatPower: {
        health(a, b) {
          return Addons.Sort.numerically(a.health, b.health);
        },
        hp(a, b) {
          return Addons.Sort.numerically(
            a.hp + a.laser_hp,
            b.hp + b.laser_hp
          );
        },
      },
      speed(a, b) {
        // order desc
        return -Addons.Sort.numerically(a.fleet_speed, b.fleet_speed);
      },
      type(a, b) {
        return Addons.Sort.alphabetically(a.ico, b.ico);
      },
      fleetName(a, b) {
        return Addons.Sort.alphabetically(a.fleet_name, b.fleet_name);
      },
      playerName(a, b) {
        return Addons.Sort.alphabetically(a.player_name, b.player_name);
      },
      playerId(a, b) {
        // order asc
        return Addons.Sort.numerically(a.player_id, b.player_id);
      },
    },
  };

  Addons.Fleets.Sort.init(this);

})(window);