Warbase Filters

Filter things out of the war base

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Warbase Filters
// @namespace    somenamespace
// @version      0.5.1
// @description  Filter things out of the war base
// @author       tos
// @include        *.torn.com/factions.php?step=your*
// @grant        GM_addStyle
// ==/UserScript==

animation_enabled = true
animation_duration = 5 //minutes

extended_desc_hide = true

difficulty_colors = {
  0: '#e0f2f2',//blue
  1: '#e0f2e9',
  2: '#e0f2e0',//green
  3: '#e6f2e0',
  4: '#ebf2e0',
  5: '#f2f2e0',//yellow
  6: '#f2ebe0',
  7: '#f2e6e0',
  8: '#f2e0e0',//red
  9: '#f2d0d0',
  10: '#f2c0c0',
  11: 'rgb(255,0,0)',
}

GM_addStyle(`
  .wb_extended.f-war-list .descriptions {
    display: none;
  }

  .wb_extended.f-war-list .act {
    padding-bottom: 0 !important;
    border-radius: 5px !important;
  }
  
  #wb_filter_wrap .arrow-wrap {display: block;}
  #wb_filter_wrap i {margin: 8px 12px 0px 0px;}
  #wb_filter_wrap .active i {margin: 11px 12px 0px 0px;}
  
  #warbase_filters {
    display: flex;}
  
  #warbase_filters .wb_content_left {
    display: inline-flex;
    flex-direction: column;
    padding: 5px;
    width: 40%;
    vertical-align: top;}
    
  #warbase_filters .wb_content_middle {
    display: inline-flex;
    flex-direction: column;
    justify-content: center;
    padding: 5px;
    width: 30%;}
    
  #warbase_filters .wb_content_right {
    display: inline-flex;
    flex-direction: column;
    padding: 5px;
    width: 30%;}
  #warbase_filters .wb_content_right span{
    justify-content: flex-end;}
  #warbase_filters .wb_content_right input{
    margin-right: 0px !important;
    margin-left: 3px;}
  
  #warbase_filters .wbTotals_col_left{
    display: inline-flex;
    flex-direction: column;
    font-size: 110%;
    font-weight: bold;
    width: auto;}
  #warbase_filters .wbTotals_col_right{
    display: inline-flex;
    flex-direction: column;
    font-size: 110%;
    text-align: right;
    font-weight: normal;
    width: auto;}
  
  #warbase_filters .wbTotals_title{
    padding: 1px 0px 1px 10px;}
  
  #warbase_filters .wbTotals {
    padding: 1px 0px;}
  
  #warbase_filters .filter-title {
    display: inline-flex;
    background-color: #BABABA;
    border-radius: 5px 0px 0px 5px;
    align-items: center;
    font-size: 150%;
    padding: 5px;}
  
  #warbase_filters .filter-content {
    display: inline-flex;
    flex-direction: column;
    background-color: #DBDBDB;
    border-radius: 0px 5px 5px 0px;
    padding: 3px 0px;}
  
  #warbase_filters .filter-row {
    display: flex;
    flex-wrap: wrap;}
  
  #warbase_filters span{
    display: flex;
    flex-wrap: wrap;
    min-height: 3px;
    padding: 1px 10px;}
  
  #warbase_filters input[type="checkbox"] {
    margin-right: 3px;}
  
  #warbase_filters input[type="number"] {
    background: transparent;
    border-bottom: 1px solid black;
    text-align: center;
    width: 50px;}
  
  .f-chain {border-radius: 14px}
  
  @keyframes linkFade {
    0% {color: #969;}
    95% {color: #769;}
    100% {color: #069;}}
  
  .animation_colorfade {
    animation-name: linkFade;
    animation-duration: ${animation_duration * 60}s;}
  
  @keyframes chainIconFade {
    from {background-color: #b2b2b2;}
    to {background-color: #f2f2f2;}}
  
  .animation_colorblind {
    animation-name: chainIconFade;
    animation-duration: ${animation_duration * 60}s;}
  
  #warbase_results {
    display: none;}
  
  #warbase_results .wbResults_placeholder {
    font-weight: bold;
    padding: 10px;}
  
  #wars_extended {
    margin-bottom:10px;}
  
  #wars_extended .descriptions-new {
    display: block;
    margin: 0;
    float: left;
    background-color: transparent;
    border-radius: 0;
    box-shadow: none;
    height: auto;
    width: 100%;}
  
  .wb_difficulty_DIV {
    float: right;
    vertical-align: middle;
  }
  
  .wb_difficulty_INPUT {
    background: white;
    border-radius: 3px;
    box-shadow: 0px 0px 2px #f2f2f2;
    text-align: center;
    float: right;
    height: 100%;
    width: 70%;
    margin: 12% 5%;
    padding: 3px 0px;
  }
  
  .wb_hide {
    overflow: hidden;
    height: 0;}
`)

const default_options = {
  fed: false,
  traveling: false,
  online: true,
  idle: true,
  offline: true,
  hosp: true,
  hosp_time: 0,
  level: false,
  level_min: 0,
  level_max: 100,
  extended: false,
  territories_inverted: false,
  colorblind: false,
  filters_collapse: false,
}

let filters = Object.assign(default_options, JSON.parse(localStorage.getItem('torn_wb_filters'))) //torn_warbase_filters
const storeFilters = () => localStorage.setItem('torn_wb_filters', JSON.stringify(filters))

let enemy_difficulty = JSON.parse(localStorage.getItem('torn_enemy_difficulty')) || {} //torn_enemy_difficulties
const difficulty_max = Object.keys(difficulty_colors).length - 1

let faction_nodes = {}
let faction_totals = {}

const count_enemies = (obj) => {
  let enemy_totals = {total:0, ok:0, hidden:0}
  for (const factionID of Object.keys(obj)) {
    enemy_totals.total += faction_totals[factionID].total
    enemy_totals.ok += faction_totals[factionID].ok
    enemy_totals.hidden += faction_totals[factionID].hidden
  }
  return enemy_totals
}

const run_filters = (node) => {
  const factionID = node.querySelector('.t-blue').href.split('&')[1].replace('=', '')
  let target_TOTALS = {total: 0, ok: 0, hidden: 0}
  faction_totals[factionID] = {}
  for (const enemy_LI of node.querySelector('.member-list').children) {
    target_TOTALS.total += 1
    const status = enemy_LI.querySelector('.status').firstElementChild.textContent
    const online_status_icon = enemy_LI.querySelector('#icon1') || enemy_LI.querySelector('#icon2') || enemy_LI.querySelector('#icon62')
    const online_status = online_status_icon.title.replace('<b>', '').replace('</b>', '')
    //const bountied = enemy_LI.querySelector('#icon13') || false
    //if(bountied) enemy_LI.style.backgroundColor ='#F0D9D2';
    let hosp_time = 0
    if (enemy_LI.querySelector('#icon15')) {
      const time_string = enemy_LI.querySelector('#icon15').title.split('\'>')[1].split('</')[0]
      hosp_time = parseInt(time_string.split(':')[0]) * 3600 + parseInt(time_string.split(':')[1]) * 60 + parseInt(time_string.split(':')[2])
    }
    let jail_time = 0
    if (enemy_LI.querySelector('#icon16')) {
      const time_string = enemy_LI.querySelector('#icon16').title.split('\'>')[1].split('</')[0]
      jail_time = parseInt(time_string.split(':')[0]) * 3600 + parseInt(time_string.split(':')[1]) * 60 + parseInt(time_string.split(':')[2])
    }
    
    const level = parseInt(enemy_LI.querySelector('.lvl .t-hide').nextSibling.textContent)
    const userID = enemy_LI.querySelector('.name').href.split('XID=')[1]
    const li_icon_wrap = enemy_LI.querySelector('.member-icons')
    
    if (!enemy_LI.querySelector('.wb_difficulty_DIV')) {
      const difficulty_DIV = document.createElement('DIV')
      difficulty_DIV.className = 'wb_difficulty_DIV'
      difficulty_DIV.innerHTML = `<input class="wb_difficulty_INPUT" type="number" min=0 max=${difficulty_max} data-userID="${userID}"></input>`
      li_icon_wrap.append(difficulty_DIV)
      const difficulty_INPUT = enemy_LI.querySelector('.wb_difficulty_INPUT')
      difficulty_INPUT.addEventListener('change', (event) => {
        if (difficulty_INPUT.value < 0) difficulty_INPUT.value = 0
        if (difficulty_INPUT.value > difficulty_max) difficulty_INPUT.value = difficulty_max
        const difficulty = difficulty_INPUT.value
        if (difficulty === '') {
          if (enemy_difficulty['ID_'+ userID]) delete enemy_difficulty['ID_'+ userID]
          for (const this_user of document.querySelectorAll(`.wb_difficulty_INPUT[data-userID="${userID}"`)) {
            this_user.parentElement.parentElement.parentElement.style.backgroundColor = 'initial'
            this_user.value = difficulty
          }
        }
        else {
          enemy_difficulty['ID_'+ userID] = difficulty
          for (const this_user of document.querySelectorAll(`.wb_difficulty_INPUT[data-userID="${userID}"`)) {
            this_user.parentElement.parentElement.parentElement.style.backgroundColor = difficulty_colors[enemy_difficulty['ID_'+ userID]]
            this_user.value = difficulty
          }
        }
        localStorage.setItem('torn_enemy_difficulty', JSON.stringify(enemy_difficulty))
      })
    }
    if (enemy_difficulty['ID_'+ userID]) {
      enemy_LI.querySelector('.wb_difficulty_INPUT').value = enemy_difficulty['ID_'+ userID]
      enemy_LI.style.backgroundColor = difficulty_colors[enemy_difficulty['ID_'+ userID]]
    }
    
    
    if (status === 'Okay') target_TOTALS.ok +=1
    const hide =
        (!filters.fed       && status === 'Federal') ||
        (!filters.traveling && status === 'Traveling') ||
        (!filters.online    && online_status === 'Online') ||
        (!filters.idle      && online_status === 'Idle') ||
        (!filters.offline   && online_status === 'Offline') ||
        (filters.hosp      && (filters.hosp_time * 60 < hosp_time || filters.hosp_time * 60 < jail_time)) ||
        (filters.level     && (filters.level_min > level || filters.level_max < level))
	
    enemy_LI.style.display = hide ? 'none' : 'list-item'
    
    if (enemy_LI.style.display === 'none') target_TOTALS.hidden += 1
  }
  
  faction_totals[factionID].total = target_TOTALS.total
  faction_totals[factionID].ok = target_TOTALS.ok
  faction_totals[factionID].hidden = target_TOTALS.total - target_TOTALS.hidden
  
  const warbase_totals = count_enemies(faction_totals)
  for (const totals_span of document.querySelectorAll('.wbTotals')) {
    const totals_controls = totals_span.className.split('wb_')[1]
    if (totals_controls === 'counted') totals_span.textContent = Object.keys(faction_totals).length +' / '+ totals_span.textContent.split('/')[1]
    else totals_span.textContent = warbase_totals[totals_controls] +' / '+ warbase_totals.total
  }
}


const observer = new MutationObserver((mutations) => {
  for (const mutation of mutations) {
    for (const node of mutation.addedNodes) {
      if (node.className && node.className === 'faction-respect-wars-wp' && !document.querySelector('#wb_filter_wrap')) {
        faction_nodes = {}
        faction_totals = {}
        const faction_main_wrap = document.querySelector('#faction-main')
        const respect_wars_wrap = document.querySelector('#faction-main .faction-respect-wars-wp')
        const wars_UL = respect_wars_wrap.querySelector('.f-war-list')
        const territory_wrap = document.querySelector('#faction-wars-wp')
        let fac_count_total = 0
        for (const faction_tab of respect_wars_wrap.querySelector('.f-war-list').children) {
          if (faction_tab.className !== 'inactive' && faction_tab.className !== 'clear') fac_count_total += 1
        }
//Filter DIV-------------------------------------------------------------------------------------------------------------------------------------
        const filter_DIV = document.createElement('DIV')
        filter_DIV.id = 'wb_filter_wrap'
        filter_DIV.innerHTML =
         `<div class="title-black m-top10 ${filters.filters_collapse ? 'border-round': 'top-round active' }">
            <div class="arrow-wrap">
              <i class="accordion-header-arrow right"></i>
            </div>
            War Base Filters
          </div>
          <div class="cont-gray map-wrap bottom-round " id="warbase_filters">
            <div class="wb_content_left">
              <div class="filter-row">
                <div class="filter-title">Show</div>
                <div class="filter-content">
                  <div class="filter-row">
                    <span><input type="checkbox" class="wbFilter wb_fed">Federal</span>
                    <span><input type="checkbox" class="wbFilter wb_traveling">Traveling</span>
                  </div>
                  <div class="filter-row">
                    <span><input type="checkbox" class="wbFilter wb_online">Online</span>
                    <span><input type="checkbox" class="wbFilter wb_idle">Idle</span>
                    <span><input type="checkbox" class="wbFilter wb_offline">Offline</span>
                  </div>
                  <span></span>
                  <span class="filter-row"><input type="checkbox" class="wbFilter wb_hosp">Hosp/Jail time &lt;&nbsp;<input type="number" class="wbFilter wb_hosp_time" min="0"> minutes</span>
                  <span class="filter-row"><input type="checkbox" class="wbFilter wb_level">Level<input type="number" min="0" max="100" class="wbFilter wb_level_min">to<input type="number" min="0" max="100" class="wbFilter wb_level_max"></span>
                </div>
              </div>
            </div>
            <div class="wb_content_middle">
              <div class="filter_row">
                <div class="wbTotals_col_left">
                  <span class="filter-row wbTotals_title">Factions Loaded:&nbsp;</span>
                  <span class="filter-row wbTotals_title">Enemies Filtered:&nbsp;</span>
                  <span class="filter-row wbTotals_title">Enemies Okay:&nbsp;</span>
                </div>
                <div class="wbTotals_col_right">
                  <span class="filter-row wbTotals wb_counted">0 / ${fac_count_total}</span>
                  <span class="filter-row wbTotals wb_hidden">...</span>
                  <span class="filter-row wbTotals wb_ok">...</span>
                </div>
              </div>
            </div>
            <div class="wb_content_right">
              <span class="filter-row">Extended Warbase<input type="checkbox" class="wbFilter wb_extended"></span>
              <span class="filter-row">Territories on Top<input type="checkbox" class="wbFilter wb_territories_inverted"></span>
              <span class="filter-row">Color Blind Mode<input type="checkbox" class="wbFilter wb_colorblind"></span>
            </div>
          </div>`
        faction_main_wrap.insertBefore(filter_DIV, respect_wars_wrap)
        
//Show/Hide button for respect wars------------------------------------------------------------------------------------------------------------------
        const banner = respect_wars_wrap.querySelector('.f-msg')
        wars_UL.style.display = 'block'
        banner.onclick = () => wars_UL.classList.toggle('wb_hide')

//War Base Extended DIV------------------------------------------------------------------------------------------------------------------------------
        const warlist_DIV = document.createElement('DIV')
        warlist_DIV.id = 'warbase_results'
        warlist_DIV.innerHTML =
         `<div class="title-black m-top10 top-round">War Base Extended</div>
          <div class="cont-gray map-wrap bottom-round">
            <div class="wbResults_placeholder">Updates on faction tab clicks...</div>
            <ul id="wars_extended" class="f-war-list war-old">
              <li class="clear"></li>
            </ul>
          </div>`
        faction_main_wrap.insertBefore(warlist_DIV, territory_wrap)

//Event Listeners for Filter DIV----------------------------------------------------------------------------------------------------------------------
        const wb_filter_title = document.querySelector('#wb_filter_wrap .title-black')
        const wb_filter_content = document.querySelector('#warbase_filters')
        filters.filters_collapse ? wb_filter_content.style.display = 'none': wb_filter_content.style.display = 'flex'
        wb_filter_title.addEventListener('click', (event) => {
          if (filters.filters_collapse) {
            wb_filter_title.classList.add('top-round')
            wb_filter_title.classList.add('active')
            wb_filter_title.classList.remove('border-round')
            wb_filter_content.style.display = 'flex'
            filters.filters_collapse = false
          }
          else {
            wb_filter_title.classList.remove('top-round')
            wb_filter_title.classList.remove('active')
            wb_filter_title.classList.add('border-round')
            wb_filter_content.style.display = 'none'
            filters.filters_collapse = true
          }
          storeFilters()
        })
        
        const filter_inputs = document.querySelectorAll('.wbFilter')
        for (const wbFilter of filter_inputs) {
          const filter_controls = wbFilter.className.split('wb_')[1]
          switch (wbFilter.type) {
            case 'checkbox':
              wbFilter.checked = filters[filter_controls]
              wbFilter.addEventListener('change', (event) => {
                filters[filter_controls] = event.target.checked
                storeFilters()
                switch (filter_controls) {
                  case 'extended':
                    if (event.target.checked) {
                      document.querySelector('#warbase_results').style.display = 'block'
                      wars_UL.classList.add('wb_extended')
                    }
                    else {
                      document.querySelector('#warbase_results').style.display = 'none'
                      wars_UL.classList.remove('wb_extended')
                    }
                    break
                  case 'territories_inverted':
                    if (event.target.checked) faction_main_wrap.insertBefore(territory_wrap, respect_wars_wrap)
                    else {
                      faction_main_wrap.insertBefore(respect_wars_wrap, territory_wrap)
                      faction_main_wrap.insertBefore(document.querySelector('#warbase_results'), territory_wrap)
                    }
                    break
                  case 'colorblind':
                    break
                  default:
                    if (document.querySelector('#faction-main .faction-respect-wars-wp .descriptions')) {
                      run_filters(document.querySelector('#faction-main .faction-respect-wars-wp .descriptions'))
                    }
                    if (Object.keys(faction_nodes).length > 0) {
                      for (const facID of Object.keys(faction_nodes)) {
                        run_filters(faction_nodes[facID])
                      }
                    }
                    break
                }
              })
              break
            case 'number':
              wbFilter.value = filters[filter_controls]
              wbFilter.addEventListener('change', (event) => {
                filters[filter_controls] = event.target.value
                storeFilters()
                switch (filter_controls) {
                  default:
                    if (document.querySelector('#faction-main .faction-respect-wars-wp .descriptions')) {
                      run_filters(document.querySelector('#faction-main .faction-respect-wars-wp .descriptions'))
                    }
                    if (Object.keys(faction_nodes).length > 0) {
                      for (const facID of Object.keys(faction_nodes)) {
                        run_filters(faction_nodes[facID])
                      }
                    }
                    break
                }
              })
              break
            default:
              break
          }
        }
//Set Extended and Territories inverted--------------------------------------------------------------------------------------------------------------------
        if (filters.extended) {
          warlist_DIV.style.display = 'block'
          wars_UL.classList.add('wb_extended')
        }
        if (filters.territories_inverted) faction_main_wrap.insertBefore(territory_wrap, respect_wars_wrap)
      }
//Observing for tabs opening--------------------------------------------------------------------------------------------------------------------------------
      if (node.className && node.className === 'descriptions') {
        if (node.querySelector('.member-list')) {
          const factionID = node.querySelector('.t-blue').href.split('&')[1].replace('=', '')
          
          if (animation_enabled) {
            const faction_link = node.parentElement.querySelector('.act .name .t-blue')
            if (faction_link.className.includes('animation_colorfade')) {
            	faction_link.classList.remove('animation_colorfade')
            	void faction_link.offsetWidth
            }
            faction_link.classList.add('animation_colorfade')
            faction_link.addEventListener("animationend", (anim) => anim.target.classList.remove('animation_colorfade'))
            if (filters['colorblind']) {
              const chain_icon = node.parentElement.querySelector('.act .f-chain')
              if (chain_icon.className.includes('animation_colorblind')) {
            	chain_icon.classList.remove('animation_colorblind')
            	void chain_icon.offsetWidth
              }
              chain_icon.classList.add('animation_colorblind')
              chain_icon.addEventListener("animationend", (anim) => anim.target.classList.remove('animation_colorblind'))
            }
          }
          
          //clone node for extended war base
          const wars_extended = document.querySelector('#wars_extended')
          faction_nodes[factionID] = node.cloneNode(true)
          faction_nodes[factionID].id = factionID
          faction_nodes[factionID].className = 'descriptions-new'
          run_filters(faction_nodes[factionID])
          if (!document.querySelector('#'+factionID)) {
            wars_extended.parentElement.querySelector('.wbResults_placeholder').style.display = 'none'
            wars_extended.insertBefore(faction_nodes[factionID], wars_extended.lastElementChild)
          }
          else {
            wars_extended.replaceChild(faction_nodes[factionID], document.querySelector('#'+factionID))
          }
          run_filters(node)
        }
      }
    }
  }
});

const wrapper = document.querySelector('#faction-main')
observer.observe(wrapper, { subtree: true, childList: true })