Greasy Fork 支持简体中文。

Warbase Filters

Filter things out of the war base

  1. // ==UserScript==
  2. // @name Warbase Filters
  3. // @namespace somenamespace
  4. // @version 0.5.1
  5. // @description Filter things out of the war base
  6. // @author tos
  7. // @include *.torn.com/factions.php?step=your*
  8. // @grant GM_addStyle
  9. // ==/UserScript==
  10.  
  11. animation_enabled = true
  12. animation_duration = 5 //minutes
  13.  
  14. extended_desc_hide = true
  15.  
  16. difficulty_colors = {
  17. 0: '#e0f2f2',//blue
  18. 1: '#e0f2e9',
  19. 2: '#e0f2e0',//green
  20. 3: '#e6f2e0',
  21. 4: '#ebf2e0',
  22. 5: '#f2f2e0',//yellow
  23. 6: '#f2ebe0',
  24. 7: '#f2e6e0',
  25. 8: '#f2e0e0',//red
  26. 9: '#f2d0d0',
  27. 10: '#f2c0c0',
  28. 11: 'rgb(255,0,0)',
  29. }
  30.  
  31. GM_addStyle(`
  32. .wb_extended.f-war-list .descriptions {
  33. display: none;
  34. }
  35.  
  36. .wb_extended.f-war-list .act {
  37. padding-bottom: 0 !important;
  38. border-radius: 5px !important;
  39. }
  40. #wb_filter_wrap .arrow-wrap {display: block;}
  41. #wb_filter_wrap i {margin: 8px 12px 0px 0px;}
  42. #wb_filter_wrap .active i {margin: 11px 12px 0px 0px;}
  43. #warbase_filters {
  44. display: flex;}
  45. #warbase_filters .wb_content_left {
  46. display: inline-flex;
  47. flex-direction: column;
  48. padding: 5px;
  49. width: 40%;
  50. vertical-align: top;}
  51. #warbase_filters .wb_content_middle {
  52. display: inline-flex;
  53. flex-direction: column;
  54. justify-content: center;
  55. padding: 5px;
  56. width: 30%;}
  57. #warbase_filters .wb_content_right {
  58. display: inline-flex;
  59. flex-direction: column;
  60. padding: 5px;
  61. width: 30%;}
  62. #warbase_filters .wb_content_right span{
  63. justify-content: flex-end;}
  64. #warbase_filters .wb_content_right input{
  65. margin-right: 0px !important;
  66. margin-left: 3px;}
  67. #warbase_filters .wbTotals_col_left{
  68. display: inline-flex;
  69. flex-direction: column;
  70. font-size: 110%;
  71. font-weight: bold;
  72. width: auto;}
  73. #warbase_filters .wbTotals_col_right{
  74. display: inline-flex;
  75. flex-direction: column;
  76. font-size: 110%;
  77. text-align: right;
  78. font-weight: normal;
  79. width: auto;}
  80. #warbase_filters .wbTotals_title{
  81. padding: 1px 0px 1px 10px;}
  82. #warbase_filters .wbTotals {
  83. padding: 1px 0px;}
  84. #warbase_filters .filter-title {
  85. display: inline-flex;
  86. background-color: #BABABA;
  87. border-radius: 5px 0px 0px 5px;
  88. align-items: center;
  89. font-size: 150%;
  90. padding: 5px;}
  91. #warbase_filters .filter-content {
  92. display: inline-flex;
  93. flex-direction: column;
  94. background-color: #DBDBDB;
  95. border-radius: 0px 5px 5px 0px;
  96. padding: 3px 0px;}
  97. #warbase_filters .filter-row {
  98. display: flex;
  99. flex-wrap: wrap;}
  100. #warbase_filters span{
  101. display: flex;
  102. flex-wrap: wrap;
  103. min-height: 3px;
  104. padding: 1px 10px;}
  105. #warbase_filters input[type="checkbox"] {
  106. margin-right: 3px;}
  107. #warbase_filters input[type="number"] {
  108. background: transparent;
  109. border-bottom: 1px solid black;
  110. text-align: center;
  111. width: 50px;}
  112. .f-chain {border-radius: 14px}
  113. @keyframes linkFade {
  114. 0% {color: #969;}
  115. 95% {color: #769;}
  116. 100% {color: #069;}}
  117. .animation_colorfade {
  118. animation-name: linkFade;
  119. animation-duration: ${animation_duration * 60}s;}
  120. @keyframes chainIconFade {
  121. from {background-color: #b2b2b2;}
  122. to {background-color: #f2f2f2;}}
  123. .animation_colorblind {
  124. animation-name: chainIconFade;
  125. animation-duration: ${animation_duration * 60}s;}
  126. #warbase_results {
  127. display: none;}
  128. #warbase_results .wbResults_placeholder {
  129. font-weight: bold;
  130. padding: 10px;}
  131. #wars_extended {
  132. margin-bottom:10px;}
  133. #wars_extended .descriptions-new {
  134. display: block;
  135. margin: 0;
  136. float: left;
  137. background-color: transparent;
  138. border-radius: 0;
  139. box-shadow: none;
  140. height: auto;
  141. width: 100%;}
  142. .wb_difficulty_DIV {
  143. float: right;
  144. vertical-align: middle;
  145. }
  146. .wb_difficulty_INPUT {
  147. background: white;
  148. border-radius: 3px;
  149. box-shadow: 0px 0px 2px #f2f2f2;
  150. text-align: center;
  151. float: right;
  152. height: 100%;
  153. width: 70%;
  154. margin: 12% 5%;
  155. padding: 3px 0px;
  156. }
  157. .wb_hide {
  158. overflow: hidden;
  159. height: 0;}
  160. `)
  161.  
  162. const default_options = {
  163. fed: false,
  164. traveling: false,
  165. online: true,
  166. idle: true,
  167. offline: true,
  168. hosp: true,
  169. hosp_time: 0,
  170. level: false,
  171. level_min: 0,
  172. level_max: 100,
  173. extended: false,
  174. territories_inverted: false,
  175. colorblind: false,
  176. filters_collapse: false,
  177. }
  178.  
  179. let filters = Object.assign(default_options, JSON.parse(localStorage.getItem('torn_wb_filters'))) //torn_warbase_filters
  180. const storeFilters = () => localStorage.setItem('torn_wb_filters', JSON.stringify(filters))
  181.  
  182. let enemy_difficulty = JSON.parse(localStorage.getItem('torn_enemy_difficulty')) || {} //torn_enemy_difficulties
  183. const difficulty_max = Object.keys(difficulty_colors).length - 1
  184.  
  185. let faction_nodes = {}
  186. let faction_totals = {}
  187.  
  188. const count_enemies = (obj) => {
  189. let enemy_totals = {total:0, ok:0, hidden:0}
  190. for (const factionID of Object.keys(obj)) {
  191. enemy_totals.total += faction_totals[factionID].total
  192. enemy_totals.ok += faction_totals[factionID].ok
  193. enemy_totals.hidden += faction_totals[factionID].hidden
  194. }
  195. return enemy_totals
  196. }
  197.  
  198. const run_filters = (node) => {
  199. const factionID = node.querySelector('.t-blue').href.split('&')[1].replace('=', '')
  200. let target_TOTALS = {total: 0, ok: 0, hidden: 0}
  201. faction_totals[factionID] = {}
  202. for (const enemy_LI of node.querySelector('.member-list').children) {
  203. target_TOTALS.total += 1
  204. const status = enemy_LI.querySelector('.status').firstElementChild.textContent
  205. const online_status_icon = enemy_LI.querySelector('#icon1') || enemy_LI.querySelector('#icon2') || enemy_LI.querySelector('#icon62')
  206. const online_status = online_status_icon.title.replace('<b>', '').replace('</b>', '')
  207. //const bountied = enemy_LI.querySelector('#icon13') || false
  208. //if(bountied) enemy_LI.style.backgroundColor ='#F0D9D2';
  209. let hosp_time = 0
  210. if (enemy_LI.querySelector('#icon15')) {
  211. const time_string = enemy_LI.querySelector('#icon15').title.split('\'>')[1].split('</')[0]
  212. hosp_time = parseInt(time_string.split(':')[0]) * 3600 + parseInt(time_string.split(':')[1]) * 60 + parseInt(time_string.split(':')[2])
  213. }
  214. let jail_time = 0
  215. if (enemy_LI.querySelector('#icon16')) {
  216. const time_string = enemy_LI.querySelector('#icon16').title.split('\'>')[1].split('</')[0]
  217. jail_time = parseInt(time_string.split(':')[0]) * 3600 + parseInt(time_string.split(':')[1]) * 60 + parseInt(time_string.split(':')[2])
  218. }
  219. const level = parseInt(enemy_LI.querySelector('.lvl .t-hide').nextSibling.textContent)
  220. const userID = enemy_LI.querySelector('.name').href.split('XID=')[1]
  221. const li_icon_wrap = enemy_LI.querySelector('.member-icons')
  222. if (!enemy_LI.querySelector('.wb_difficulty_DIV')) {
  223. const difficulty_DIV = document.createElement('DIV')
  224. difficulty_DIV.className = 'wb_difficulty_DIV'
  225. difficulty_DIV.innerHTML = `<input class="wb_difficulty_INPUT" type="number" min=0 max=${difficulty_max} data-userID="${userID}"></input>`
  226. li_icon_wrap.append(difficulty_DIV)
  227. const difficulty_INPUT = enemy_LI.querySelector('.wb_difficulty_INPUT')
  228. difficulty_INPUT.addEventListener('change', (event) => {
  229. if (difficulty_INPUT.value < 0) difficulty_INPUT.value = 0
  230. if (difficulty_INPUT.value > difficulty_max) difficulty_INPUT.value = difficulty_max
  231. const difficulty = difficulty_INPUT.value
  232. if (difficulty === '') {
  233. if (enemy_difficulty['ID_'+ userID]) delete enemy_difficulty['ID_'+ userID]
  234. for (const this_user of document.querySelectorAll(`.wb_difficulty_INPUT[data-userID="${userID}"`)) {
  235. this_user.parentElement.parentElement.parentElement.style.backgroundColor = 'initial'
  236. this_user.value = difficulty
  237. }
  238. }
  239. else {
  240. enemy_difficulty['ID_'+ userID] = difficulty
  241. for (const this_user of document.querySelectorAll(`.wb_difficulty_INPUT[data-userID="${userID}"`)) {
  242. this_user.parentElement.parentElement.parentElement.style.backgroundColor = difficulty_colors[enemy_difficulty['ID_'+ userID]]
  243. this_user.value = difficulty
  244. }
  245. }
  246. localStorage.setItem('torn_enemy_difficulty', JSON.stringify(enemy_difficulty))
  247. })
  248. }
  249. if (enemy_difficulty['ID_'+ userID]) {
  250. enemy_LI.querySelector('.wb_difficulty_INPUT').value = enemy_difficulty['ID_'+ userID]
  251. enemy_LI.style.backgroundColor = difficulty_colors[enemy_difficulty['ID_'+ userID]]
  252. }
  253. if (status === 'Okay') target_TOTALS.ok +=1
  254. const hide =
  255. (!filters.fed && status === 'Federal') ||
  256. (!filters.traveling && status === 'Traveling') ||
  257. (!filters.online && online_status === 'Online') ||
  258. (!filters.idle && online_status === 'Idle') ||
  259. (!filters.offline && online_status === 'Offline') ||
  260. (filters.hosp && (filters.hosp_time * 60 < hosp_time || filters.hosp_time * 60 < jail_time)) ||
  261. (filters.level && (filters.level_min > level || filters.level_max < level))
  262. enemy_LI.style.display = hide ? 'none' : 'list-item'
  263. if (enemy_LI.style.display === 'none') target_TOTALS.hidden += 1
  264. }
  265. faction_totals[factionID].total = target_TOTALS.total
  266. faction_totals[factionID].ok = target_TOTALS.ok
  267. faction_totals[factionID].hidden = target_TOTALS.total - target_TOTALS.hidden
  268. const warbase_totals = count_enemies(faction_totals)
  269. for (const totals_span of document.querySelectorAll('.wbTotals')) {
  270. const totals_controls = totals_span.className.split('wb_')[1]
  271. if (totals_controls === 'counted') totals_span.textContent = Object.keys(faction_totals).length +' / '+ totals_span.textContent.split('/')[1]
  272. else totals_span.textContent = warbase_totals[totals_controls] +' / '+ warbase_totals.total
  273. }
  274. }
  275.  
  276.  
  277. const observer = new MutationObserver((mutations) => {
  278. for (const mutation of mutations) {
  279. for (const node of mutation.addedNodes) {
  280. if (node.className && node.className === 'faction-respect-wars-wp' && !document.querySelector('#wb_filter_wrap')) {
  281. faction_nodes = {}
  282. faction_totals = {}
  283. const faction_main_wrap = document.querySelector('#faction-main')
  284. const respect_wars_wrap = document.querySelector('#faction-main .faction-respect-wars-wp')
  285. const wars_UL = respect_wars_wrap.querySelector('.f-war-list')
  286. const territory_wrap = document.querySelector('#faction-wars-wp')
  287. let fac_count_total = 0
  288. for (const faction_tab of respect_wars_wrap.querySelector('.f-war-list').children) {
  289. if (faction_tab.className !== 'inactive' && faction_tab.className !== 'clear') fac_count_total += 1
  290. }
  291. //Filter DIV-------------------------------------------------------------------------------------------------------------------------------------
  292. const filter_DIV = document.createElement('DIV')
  293. filter_DIV.id = 'wb_filter_wrap'
  294. filter_DIV.innerHTML =
  295. `<div class="title-black m-top10 ${filters.filters_collapse ? 'border-round': 'top-round active' }">
  296. <div class="arrow-wrap">
  297. <i class="accordion-header-arrow right"></i>
  298. </div>
  299. War Base Filters
  300. </div>
  301. <div class="cont-gray map-wrap bottom-round " id="warbase_filters">
  302. <div class="wb_content_left">
  303. <div class="filter-row">
  304. <div class="filter-title">Show</div>
  305. <div class="filter-content">
  306. <div class="filter-row">
  307. <span><input type="checkbox" class="wbFilter wb_fed">Federal</span>
  308. <span><input type="checkbox" class="wbFilter wb_traveling">Traveling</span>
  309. </div>
  310. <div class="filter-row">
  311. <span><input type="checkbox" class="wbFilter wb_online">Online</span>
  312. <span><input type="checkbox" class="wbFilter wb_idle">Idle</span>
  313. <span><input type="checkbox" class="wbFilter wb_offline">Offline</span>
  314. </div>
  315. <span></span>
  316. <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>
  317. <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>
  318. </div>
  319. </div>
  320. </div>
  321. <div class="wb_content_middle">
  322. <div class="filter_row">
  323. <div class="wbTotals_col_left">
  324. <span class="filter-row wbTotals_title">Factions Loaded:&nbsp;</span>
  325. <span class="filter-row wbTotals_title">Enemies Filtered:&nbsp;</span>
  326. <span class="filter-row wbTotals_title">Enemies Okay:&nbsp;</span>
  327. </div>
  328. <div class="wbTotals_col_right">
  329. <span class="filter-row wbTotals wb_counted">0 / ${fac_count_total}</span>
  330. <span class="filter-row wbTotals wb_hidden">...</span>
  331. <span class="filter-row wbTotals wb_ok">...</span>
  332. </div>
  333. </div>
  334. </div>
  335. <div class="wb_content_right">
  336. <span class="filter-row">Extended Warbase<input type="checkbox" class="wbFilter wb_extended"></span>
  337. <span class="filter-row">Territories on Top<input type="checkbox" class="wbFilter wb_territories_inverted"></span>
  338. <span class="filter-row">Color Blind Mode<input type="checkbox" class="wbFilter wb_colorblind"></span>
  339. </div>
  340. </div>`
  341. faction_main_wrap.insertBefore(filter_DIV, respect_wars_wrap)
  342. //Show/Hide button for respect wars------------------------------------------------------------------------------------------------------------------
  343. const banner = respect_wars_wrap.querySelector('.f-msg')
  344. wars_UL.style.display = 'block'
  345. banner.onclick = () => wars_UL.classList.toggle('wb_hide')
  346.  
  347. //War Base Extended DIV------------------------------------------------------------------------------------------------------------------------------
  348. const warlist_DIV = document.createElement('DIV')
  349. warlist_DIV.id = 'warbase_results'
  350. warlist_DIV.innerHTML =
  351. `<div class="title-black m-top10 top-round">War Base Extended</div>
  352. <div class="cont-gray map-wrap bottom-round">
  353. <div class="wbResults_placeholder">Updates on faction tab clicks...</div>
  354. <ul id="wars_extended" class="f-war-list war-old">
  355. <li class="clear"></li>
  356. </ul>
  357. </div>`
  358. faction_main_wrap.insertBefore(warlist_DIV, territory_wrap)
  359.  
  360. //Event Listeners for Filter DIV----------------------------------------------------------------------------------------------------------------------
  361. const wb_filter_title = document.querySelector('#wb_filter_wrap .title-black')
  362. const wb_filter_content = document.querySelector('#warbase_filters')
  363. filters.filters_collapse ? wb_filter_content.style.display = 'none': wb_filter_content.style.display = 'flex'
  364. wb_filter_title.addEventListener('click', (event) => {
  365. if (filters.filters_collapse) {
  366. wb_filter_title.classList.add('top-round')
  367. wb_filter_title.classList.add('active')
  368. wb_filter_title.classList.remove('border-round')
  369. wb_filter_content.style.display = 'flex'
  370. filters.filters_collapse = false
  371. }
  372. else {
  373. wb_filter_title.classList.remove('top-round')
  374. wb_filter_title.classList.remove('active')
  375. wb_filter_title.classList.add('border-round')
  376. wb_filter_content.style.display = 'none'
  377. filters.filters_collapse = true
  378. }
  379. storeFilters()
  380. })
  381. const filter_inputs = document.querySelectorAll('.wbFilter')
  382. for (const wbFilter of filter_inputs) {
  383. const filter_controls = wbFilter.className.split('wb_')[1]
  384. switch (wbFilter.type) {
  385. case 'checkbox':
  386. wbFilter.checked = filters[filter_controls]
  387. wbFilter.addEventListener('change', (event) => {
  388. filters[filter_controls] = event.target.checked
  389. storeFilters()
  390. switch (filter_controls) {
  391. case 'extended':
  392. if (event.target.checked) {
  393. document.querySelector('#warbase_results').style.display = 'block'
  394. wars_UL.classList.add('wb_extended')
  395. }
  396. else {
  397. document.querySelector('#warbase_results').style.display = 'none'
  398. wars_UL.classList.remove('wb_extended')
  399. }
  400. break
  401. case 'territories_inverted':
  402. if (event.target.checked) faction_main_wrap.insertBefore(territory_wrap, respect_wars_wrap)
  403. else {
  404. faction_main_wrap.insertBefore(respect_wars_wrap, territory_wrap)
  405. faction_main_wrap.insertBefore(document.querySelector('#warbase_results'), territory_wrap)
  406. }
  407. break
  408. case 'colorblind':
  409. break
  410. default:
  411. if (document.querySelector('#faction-main .faction-respect-wars-wp .descriptions')) {
  412. run_filters(document.querySelector('#faction-main .faction-respect-wars-wp .descriptions'))
  413. }
  414. if (Object.keys(faction_nodes).length > 0) {
  415. for (const facID of Object.keys(faction_nodes)) {
  416. run_filters(faction_nodes[facID])
  417. }
  418. }
  419. break
  420. }
  421. })
  422. break
  423. case 'number':
  424. wbFilter.value = filters[filter_controls]
  425. wbFilter.addEventListener('change', (event) => {
  426. filters[filter_controls] = event.target.value
  427. storeFilters()
  428. switch (filter_controls) {
  429. default:
  430. if (document.querySelector('#faction-main .faction-respect-wars-wp .descriptions')) {
  431. run_filters(document.querySelector('#faction-main .faction-respect-wars-wp .descriptions'))
  432. }
  433. if (Object.keys(faction_nodes).length > 0) {
  434. for (const facID of Object.keys(faction_nodes)) {
  435. run_filters(faction_nodes[facID])
  436. }
  437. }
  438. break
  439. }
  440. })
  441. break
  442. default:
  443. break
  444. }
  445. }
  446. //Set Extended and Territories inverted--------------------------------------------------------------------------------------------------------------------
  447. if (filters.extended) {
  448. warlist_DIV.style.display = 'block'
  449. wars_UL.classList.add('wb_extended')
  450. }
  451. if (filters.territories_inverted) faction_main_wrap.insertBefore(territory_wrap, respect_wars_wrap)
  452. }
  453. //Observing for tabs opening--------------------------------------------------------------------------------------------------------------------------------
  454. if (node.className && node.className === 'descriptions') {
  455. if (node.querySelector('.member-list')) {
  456. const factionID = node.querySelector('.t-blue').href.split('&')[1].replace('=', '')
  457. if (animation_enabled) {
  458. const faction_link = node.parentElement.querySelector('.act .name .t-blue')
  459. if (faction_link.className.includes('animation_colorfade')) {
  460. faction_link.classList.remove('animation_colorfade')
  461. void faction_link.offsetWidth
  462. }
  463. faction_link.classList.add('animation_colorfade')
  464. faction_link.addEventListener("animationend", (anim) => anim.target.classList.remove('animation_colorfade'))
  465. if (filters['colorblind']) {
  466. const chain_icon = node.parentElement.querySelector('.act .f-chain')
  467. if (chain_icon.className.includes('animation_colorblind')) {
  468. chain_icon.classList.remove('animation_colorblind')
  469. void chain_icon.offsetWidth
  470. }
  471. chain_icon.classList.add('animation_colorblind')
  472. chain_icon.addEventListener("animationend", (anim) => anim.target.classList.remove('animation_colorblind'))
  473. }
  474. }
  475. //clone node for extended war base
  476. const wars_extended = document.querySelector('#wars_extended')
  477. faction_nodes[factionID] = node.cloneNode(true)
  478. faction_nodes[factionID].id = factionID
  479. faction_nodes[factionID].className = 'descriptions-new'
  480. run_filters(faction_nodes[factionID])
  481. if (!document.querySelector('#'+factionID)) {
  482. wars_extended.parentElement.querySelector('.wbResults_placeholder').style.display = 'none'
  483. wars_extended.insertBefore(faction_nodes[factionID], wars_extended.lastElementChild)
  484. }
  485. else {
  486. wars_extended.replaceChild(faction_nodes[factionID], document.querySelector('#'+factionID))
  487. }
  488. run_filters(node)
  489. }
  490. }
  491. }
  492. }
  493. });
  494.  
  495. const wrapper = document.querySelector('#faction-main')
  496. observer.observe(wrapper, { subtree: true, childList: true })