您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
An experimental catch filter that aims to help you have much better control and will completely change how you capture Pokémon.
// ==UserScript== // @name [Pokeclicker] Catch Filter Fantasia // @namespace Pokeclicker Scripts // @author Ephenia (Credit: Pastaficionado, umamaistempo) // @description An experimental catch filter that aims to help you have much better control and will completely change how you capture Pokémon. // @copyright https://github.com/Ephenia // @license GPL-3.0 License // @version 1.9.3 // @homepageURL https://github.com/Ephenia/Pokeclicker-Scripts/ // @supportURL https://github.com/Ephenia/Pokeclicker-Scripts/issues // @match https://www.pokeclicker.com/ // @icon https://www.google.com/s2/favicons?domain=pokeclicker.com // @grant unsafeWindow // @run-at document-idle // ==/UserScript== const ballNames = ['None', 'Pokeball', 'Greatball', 'Ultraball', 'Masterball', 'Fastball', 'Quickball', 'Timerball', 'Duskball', 'Luxuryball', 'Diveball', 'Lureball', 'Nestball', 'Repeatball', 'Beastball']; var filterState; var filterTypes; var filterBallPref; var catchFilter; var filterColor; function initCatchFilter() { const pokeballDisplay = document.getElementById('pokeballSelector'); const profileModal = document.getElementById('profileModal'); filterState ? filterColor = true : filterColor = false; // Setting custon CSS styles addGlobalStyle('#catch-filter-btn { position: absolute; left: 0px; top: 0px; width: auto; height: 41px; }'); addGlobalStyle('#catch-filter-cont { display: flex; flex-direction: column; justify-content: center; }'); addGlobalStyle('#filter-results { cursor: pointer; }'); addGlobalStyle('#filter-results > div { display: flex; align-items: center; justify-content: center; min-height: 34px; }'); addGlobalStyle('#filter-results > div:hover { background-color: gold; }'); addGlobalStyle('.filter-pokeball-n { position: absolute; right: 25%; }'); addGlobalStyle('.filter-pokeball-s { position: absolute; right: 20%; }'); addGlobalStyle('.filter-shiny { position: absolute; right: 17%; }'); addGlobalStyle('#filter-btn-cont { display: flex; justify-content: center; flex-wrap: wrap; gap: 10px; }'); addGlobalStyle('#filter-btn-cont > button { display: flex; justify-content: center; min-width: 107px; }'); addGlobalStyle('#filter-btn-tools { display: flex; flex-direction: row; justify-content: space-between; align-items: flex-end; column-gap: 10px; }'); // Creating the button to append to the Pokeballs container let frag = new DocumentFragment(); const btn = document.createElement('button'); btn.innerText = 'Filter' btn.setAttribute('id', 'catch-filter-btn') btn.setAttribute('class', 'btn btn-sm btn-primary'); btn.addEventListener('click', () => { $('#filterModal').modal('show') }) frag.appendChild(btn); pokeballDisplay.appendChild(frag); // Creating a modal for the catch filter const filterMod = document.createElement('div'); filterMod.setAttribute("class", "modal noselect fade show"); filterMod.setAttribute("id", "filterModal"); filterMod.setAttribute("tabindex", "-1"); filterMod.setAttribute("aria-labelledby", "filterModal"); filterMod.setAttribute("aria-labelledby", "filterModal"); filterMod.setAttribute("aria-modal", "true"); filterMod.setAttribute("role", "dialog"); filterMod.innerHTML = `<div class="modal-dialog modal-dialog-scrollable modal-dialog-centered modal-lg" role="document"> <div class="modal-content"> <div class="modal-header" style="justify-content: space-around;"> <h5 class="modal-title">Catch Filter</h5> <button type="button" class="close" data-dismiss="modal" aria-label="Close"> <span aria-hidden="true">×</span> </button> </div> <div class="modal-body"> </div> </div>` profileModal.before(filterMod); const modalBody = document.querySelector('[id=filterModal] div div [class=modal-body]'); modalBody.innerHTML = `<button id="catch-filter" class="btn btn-${filterColor ? 'success' : 'danger'}" style="margin-left:20px;">Catch Filter ${filterState ? '[ON]' : '[OFF]'}</button> <hr> <div id="filter-btn-cont"></div> <hr> <div id="filter-btn-tools"> <button id="filter-load" class="btn btn-block btn-primary">Load Filtered</button> <button id="filter-route" class="btn btn-block btn-primary">Load Route</button> <button id="filter-dungeon" class="btn btn-block btn-primary">Load Dungeon</button> <button id="filter-all" class="btn btn-block btn-primary">Filter All</button> <button id="unfilter-all" class="btn btn-block btn-primary">Unfilter All</button> <button id="reset-ball-filter" class="btn btn-block btn-primary">Reset Balls</button> </div> <hr> <div id="catch-filter-cont"> <input id="filter-search" type="text" placeholder="Search for a Pokémon..."> <hr> <div id="filter-results"></div> </div>` // Re-assigning the previous document fragment frag = new DocumentFragment(); filterTypes.forEach((Type, Index) => { const typeName = PokemonType[Index]; const btn = document.createElement('button'); btn.innerText = `${typeName} ${Type ? '[ON]' : '[OFF]'}`; btn.setAttribute('class', `btn btn-${Type ? 'success' : 'danger'}`); btn.setAttribute('data-src', Index); btn.addEventListener('click', (event) => { toggleTypeFilter(event); }); frag.appendChild(btn); }) document.getElementById('filter-btn-cont').appendChild(frag); document.getElementById('filter-load').addEventListener('click', () => { loadFilteredList(); }); document.getElementById('filter-route').addEventListener('click', (event) => { filterPokemonRoute(event); }); document.getElementById('filter-dungeon').addEventListener('click', (event) => { filterPokemonDungeon(event); }); document.getElementById('filter-all').addEventListener('click', () => { filterAllPoke(); }); document.getElementById('unfilter-all').addEventListener('click', () => { unfilterAllPoke(); }); document.getElementById('reset-ball-filter').addEventListener('click', () => { resetBallFilterAll(); }); document.getElementById('catch-filter').addEventListener('click', (event) => { toggleCatchFilter(event); }); document.getElementById('filter-search').addEventListener('input', (event) => { filterPokeSearch(event); }); overloadPokeballMethod(); loadFilteredList(); } function toggleTypeFilter(event) { const elem = event.target; const index = +elem.getAttribute('data-src'); const typeName = PokemonType[index]; filterTypes[index] ? filterTypes[index] = false : filterTypes[index] = true; elem.setAttribute('class', `btn btn-${filterTypes[index] ? 'success' : 'danger'}`); elem.innerText = `${typeName} ${filterTypes[index] ? '[ON]' : '[OFF]'}`; localStorage.setItem('filterTypes', JSON.stringify(filterTypes)); } function loadFilteredList() { if (catchFilter.length != 0) { document.getElementById('filter-results').innerHTML = ''; const frag = new DocumentFragment(); for (const id of catchFilter) { const findPoke = pokemonList.find(p => p.id == id); const pokeIndex = pokemonList.indexOf(findPoke); const ballPrefN = filterBallPref[pokeIndex].normal; const ballPrefS = filterBallPref[pokeIndex].shiny; const div = document.createElement('div'); div.innerHTML = `${findPoke.name} <img src="assets/images/pokeball/${ballNames[ballPrefN]}.svg" class="filter-pokeball-n pokeball-small pokeball-selected" ball-pref="${pokeIndex}" pref-type="normal"> <img src="assets/images/pokeball/${ballNames[ballPrefS]}.svg" class="filter-pokeball-s pokeball-small pokeball-selected" ball-pref="${pokeIndex}" pref-type="shiny"> <div class="filter-shiny">✨</div>`; div.setAttribute('data-src', findPoke.id); if (catchFilter.includes(findPoke.id)) { div.setAttribute('style', 'background-color: yellowgreen;'); } div.addEventListener('click', (event) => { toggleFilteredPoke(event);loadFilteredList(); }); frag.appendChild(div); } document.getElementById('filter-results').appendChild(frag); setRightClick(); } else { document.getElementById('filter-results').innerHTML = '<b style="color: red">Your filtered list is empty.</b>'; } } function filterPokemonRoute(event) { const elem = event.target; document.getElementById('filter-results').innerHTML = ''; let routePoke; try {routePoke = Routes.getRoute(player.region, player.route).pokemon const frag = new DocumentFragment(); //Using a side array to ensure adding pokemons only once in results (multiple occurences can occur with special) let routePokesList = []; for (const area in routePoke) { const routeArea = routePoke[area]; if (routeArea.length > 0) { //Special Route Pokemons (Weather, Quests, ...) if (area ==="special") { for (const speRoute of routeArea) { const speRoutePokes = speRoute.pokemon; for (const poke of speRoutePokes) { const findPoke = pokemonList.find(p => p.name == poke); if (!routePokesList.includes(findPoke.id)) { routePokesList.push(findPoke.id); const pokeIndex = pokemonList.indexOf(findPoke); const ballPrefN = filterBallPref[pokeIndex].normal; const ballPrefS = filterBallPref[pokeIndex].shiny; const div = document.createElement('div'); div.innerHTML = `${findPoke.name} <img src="assets/images/pokeball/${ballNames[ballPrefN]}.svg" class="filter-pokeball-n pokeball-small pokeball-selected" ball-pref="${pokeIndex}" pref-type="normal"> <img src="assets/images/pokeball/${ballNames[ballPrefS]}.svg" class="filter-pokeball-s pokeball-small pokeball-selected" ball-pref="${pokeIndex}" pref-type="shiny"> <div class="filter-shiny">✨</div>`; div.setAttribute('data-src', findPoke.id); if (catchFilter.includes(findPoke.id)) { div.setAttribute('style', 'background-color: yellowgreen;'); } div.addEventListener('click', (event) => { toggleFilteredPoke(event); }); frag.appendChild(div); } } } } else { const thisArea = routePoke[area]; for (const poke of routeArea) { const findPoke = pokemonList.find(p => p.name == poke); if (!routePokesList.includes(findPoke.id)) { routePokesList.push(findPoke.id); const pokeIndex = pokemonList.indexOf(findPoke); const ballPrefN = filterBallPref[pokeIndex].normal; const ballPrefS = filterBallPref[pokeIndex].shiny; const div = document.createElement('div'); div.innerHTML = `${findPoke.name} <img src="assets/images/pokeball/${ballNames[ballPrefN]}.svg" class="filter-pokeball-n pokeball-small pokeball-selected" ball-pref="${pokeIndex}" pref-type="normal"> <img src="assets/images/pokeball/${ballNames[ballPrefS]}.svg" class="filter-pokeball-s pokeball-small pokeball-selected" ball-pref="${pokeIndex}" pref-type="shiny"> <div class="filter-shiny">✨</div>`; div.setAttribute('data-src', findPoke.id); if (catchFilter.includes(findPoke.id)) { div.setAttribute('style', 'background-color: yellowgreen;'); } div.addEventListener('click', (event) => { toggleFilteredPoke(event); }); frag.appendChild(div); } } } } } if (routePokesList.length > 0) { document.getElementById('filter-results').appendChild(frag); setRightClick(); } } catch (err) { document.getElementById('filter-results').innerHTML = '<b style="color: red">You are not on a route.</b>'; }; } function filterPokemonDungeon(event) { const elem = event.target; document.getElementById('filter-results').innerHTML = ''; let validDungeon; try {validDungeon = DungeonRunner.dungeon; const dungeonPoke = validDungeon.enemyList; const dungeonBoss = validDungeon.bossList; const frag = new DocumentFragment(); for (const enemy of dungeonPoke) { let pokeStr; if (typeof enemy == 'string') { pokeStr = enemy; } else if (typeof enemy.pokemon == 'string') { pokeStr = enemy.pokemon; } if (typeof pokeStr != 'undefined') { const findPoke = pokemonList.find(p => p.name == pokeStr); const pokeIndex = pokemonList.indexOf(findPoke); const ballPrefN = filterBallPref[pokeIndex].normal; const ballPrefS = filterBallPref[pokeIndex].shiny; const div = document.createElement('div'); div.innerHTML = `${findPoke.name} <img src="assets/images/pokeball/${ballNames[ballPrefN]}.svg" class="filter-pokeball-n pokeball-small pokeball-selected" ball-pref="${pokeIndex}" pref-type="normal"> <img src="assets/images/pokeball/${ballNames[ballPrefS]}.svg" class="filter-pokeball-s pokeball-small pokeball-selected" ball-pref="${pokeIndex}" pref-type="shiny"> <div class="filter-shiny">✨</div>`; div.setAttribute('data-src', findPoke.id); if (catchFilter.includes(findPoke.id)) { div.setAttribute('style', 'background-color: yellowgreen;'); } div.addEventListener('click', (event) => { toggleFilteredPoke(event); }); frag.appendChild(div); } } for (const enemy of dungeonBoss) { let pokeStr; const construct = enemy.constructor.name; if (construct == 'DungeonBossPokemon') { pokeStr = enemy.name; } if (typeof pokeStr != 'undefined') { const findPoke = pokemonList.find(p => p.name == pokeStr); const pokeIndex = pokemonList.indexOf(findPoke); const ballPrefN = filterBallPref[pokeIndex].normal; const ballPrefS = filterBallPref[pokeIndex].shiny; const div = document.createElement('div'); div.innerHTML = `${findPoke.name} <img src="assets/images/pokeball/${ballNames[ballPrefN]}.svg" class="filter-pokeball-n pokeball-small pokeball-selected" ball-pref="${pokeIndex}" pref-type="normal"> <img src="assets/images/pokeball/${ballNames[ballPrefS]}.svg" class="filter-pokeball-s pokeball-small pokeball-selected" ball-pref="${pokeIndex}" pref-type="shiny"> <div class="filter-shiny">✨</div>`; div.setAttribute('data-src', findPoke.id); if (catchFilter.includes(findPoke.id)) { div.setAttribute('style', 'background-color: yellowgreen;'); } div.addEventListener('click', (event) => { toggleFilteredPoke(event); }); frag.appendChild(div); } } document.getElementById('filter-results').appendChild(frag); setRightClick(); } catch (err) { document.getElementById('filter-results').innerHTML = '<b style="color: red">You are not in a dungeon or dungeon info cannot be found.</b>'; }; } function filterAllPoke() { catchFilter = []; for (const poke of pokemonList) { catchFilter.push(poke.id); } localStorage.setItem('catchFilter', JSON.stringify(catchFilter)); filterPokeSearch(true); } function unfilterAllPoke() { catchFilter = []; localStorage.setItem('catchFilter', JSON.stringify(catchFilter)); filterPokeSearch(true); } function resetBallFilterAll() { filterBallPref = new Array(pokemonList.length).fill({normal: 0, shiny: 0}, 0, pokemonList.length); localStorage.setItem('filterBallPref', JSON.stringify(filterBallPref)); filterPokeSearch(true); } function toggleCatchFilter(event) { const elem = event.target; filterState ? filterColor = false : filterColor = true; filterState = filterColor; elem.setAttribute('class', `btn btn-${filterColor ? 'success' : 'danger'}`); elem.innerText = `Catch Filter ${filterState ? "[ON]" : "[OFF]"}`; localStorage.setItem('filterState', filterState); } function filterPokeSearch(event) { document.getElementById('filter-results').innerHTML = ''; let pokeStr; try { pokeStr = event.target.value.toLowerCase(); } catch (err) { pokeStr = document.getElementById('filter-search').value; }; const filterList = pokemonList.filter(p => p.name.toLowerCase().includes(pokeStr)); const frag = new DocumentFragment(); for (const poke of filterList) { const findPoke = pokemonList.find(p => p.name == poke.name); const pokeIndex = pokemonList.indexOf(findPoke); const ballPrefN = filterBallPref[pokeIndex].normal; const ballPrefS = filterBallPref[pokeIndex].shiny; const div = document.createElement('div'); div.innerHTML = `${findPoke.name} <img src="assets/images/pokeball/${ballNames[ballPrefN]}.svg" class="filter-pokeball-n pokeball-small pokeball-selected" ball-pref="${pokeIndex}" pref-type="normal"> <img src="assets/images/pokeball/${ballNames[ballPrefS]}.svg" class="filter-pokeball-s pokeball-small pokeball-selected" ball-pref="${pokeIndex}" pref-type="shiny"> <div class="filter-shiny">✨</div>`; div.setAttribute('data-src', poke.id); if (catchFilter.includes(poke.id)) { div.setAttribute('style', 'background-color: yellowgreen;'); } div.addEventListener('click', (event) => { toggleFilteredPoke(event); }); frag.appendChild(div); } document.getElementById('filter-results').appendChild(frag); setRightClick(); } function toggleFilteredPoke(event) { const elem = event.target; const id = +elem.getAttribute('data-src'); if (elem.hasAttribute('ball-pref')) { const ballPref = +elem.getAttribute('ball-pref'); const prefType = elem.getAttribute('pref-type'); filterBallPref[ballPref][prefType] < ballNames.length - 1 ? filterBallPref[ballPref][prefType]++ : filterBallPref[ballPref][prefType] = 0; elem.src = `assets/images/pokeball/${ballNames[filterBallPref[ballPref][prefType]]}.svg` localStorage.setItem('filterBallPref', JSON.stringify(filterBallPref)); return; } const idExists = catchFilter.indexOf(id); if (idExists == -1) { catchFilter.push(id); elem.setAttribute('style', 'background-color: yellowgreen;'); } else { catchFilter.splice(idExists, 1); elem.removeAttribute('style'); } localStorage.setItem('catchFilter', JSON.stringify(catchFilter)); } function setRightClick() { const ballElemsN = document.getElementsByClassName('filter-pokeball-n'); const ballElemsS = document.getElementsByClassName('filter-pokeball-s'); for (let i = 0; i < ballElemsN.length; i++) { ballElemsN[i].addEventListener('contextmenu', (event) => { event.preventDefault();resetBallFilter(event); }); } for (let ii = 0; ii < ballElemsS.length; ii++) { ballElemsS[ii].addEventListener('contextmenu', (event) => { event.preventDefault();resetBallFilter(event); }); } } function resetBallFilter(event) { const elem = event.target; const ballPref = +elem.getAttribute('ball-pref'); const prefType = elem.getAttribute('pref-type'); filterBallPref[ballPref][prefType] = 0; elem.src = `assets/images/pokeball/${ballNames[filterBallPref[ballPref][prefType]]}.svg` localStorage.setItem('filterBallPref', JSON.stringify(filterBallPref)); } function overloadPokeballMethod() { const hasBall = function(ballId) { return App.game.pokeballs.pokeballs[ballId].quantity() > 0; } const newMethod = function(id, isShiny, isShadow, encounterType) { const pokemon = PokemonHelper.getPokemonById(id); const type1 = pokemon.type1; const type2 = pokemon.type2; // FIXME: we could just use a map with the changes we want const pokeIndex = pokemonList.findIndex(p => p.id == pokemon.id); // FIXME: the values of pokeballs for the choices on the modal are // offset by -1 let ballPrefN = filterBallPref[pokeIndex].normal - 1; let ballPrefS = filterBallPref[pokeIndex].shiny - 1; const overrideBallN = ballPrefN !== GameConstants.Pokeball.None; const overrideBallS = ballPrefS !== GameConstants.Pokeball.None; const isAllowed = catchFilter.includes(id) || filterTypes[type1] || filterTypes[type2] if (filterState && isAllowed && isShiny && overrideBallS && hasBall(ballPrefS)) { return ballPrefS; } else if (filterState && isAllowed && !isShiny && overrideBallN && hasBall(ballPrefN)) { return ballPrefN; } else if (filterState && !isAllowed) { return GameConstants.Pokeball.None; } else { return App.game.pokeballs.oldCalculatePokeballToUse(id, isShiny, isShadow, encounterType); } } // HACK: doing this to keep the function inside the pokeballs object // otherwise it will not have correct access to `this`. Doing this this // way to just overload the function instead of rewriting it. App.game.pokeballs.oldCalculatePokeballToUse = App.game.pokeballs.calculatePokeballToUse; App.game.pokeballs.calculatePokeballToUse = newMethod; } if (!localStorage.getItem('filterState')) { localStorage.setItem('filterState', false); } if (!localStorage.getItem('filterTypes')) { const typeArray = new Array(18).fill(false, 0, 18); localStorage.setItem('filterTypes', JSON.stringify(typeArray)); } if (!localStorage.getItem('filterBallPref')) { const prefArray = new Array(pokemonList.length).fill({normal: 0, shiny: 0}, 0, pokemonList.length); localStorage.setItem('filterBallPref', JSON.stringify(prefArray)); } if (!localStorage.getItem('catchFilter')) { localStorage.setItem('catchFilter', JSON.stringify([])); } filterState = JSON.parse(localStorage.getItem('filterState')); filterTypes = JSON.parse(localStorage.getItem('filterTypes')); catchFilter = JSON.parse(localStorage.getItem('catchFilter')); filterBallPref = JSON.parse(localStorage.getItem('filterBallPref')); if (filterBallPref.length != pokemonList.length) { const diff = pokemonList.length - filterBallPref.length; const newPoke = new Array(diff).fill({normal: 0, shiny: 0}, 0, diff); filterBallPref = filterBallPref.concat(newPoke); localStorage.setItem('filterBallPref', JSON.stringify(filterBallPref)); } const fixIt = filterBallPref.filter(e => typeof e == 'number'); if (fixIt.length != 0) { for (const index in filterBallPref) { if (typeof filterBallPref[index] == 'number') { filterBallPref[index] = {normal: 0, shiny: 0}; } } localStorage.setItem('filterBallPref', JSON.stringify(filterBallPref)); } function addGlobalStyle(css) { var head, style; head = document.getElementsByTagName('head')[0]; if (!head) { return; } style = document.createElement('style'); style.type = 'text/css'; style.innerHTML = css; head.appendChild(style); } function loadEpheniaScript(scriptName, initFunction, priorityFunction) { function reportScriptError(scriptName, error) { console.error(`Error while initializing '${scriptName}' userscript:\n${error}`); Notifier.notify({ type: NotificationConstants.NotificationOption.warning, title: scriptName, message: `The '${scriptName}' userscript crashed while loading. Check for updates or disable the script, then restart the game.\n\nReport script issues to the script developer, not to the Pokéclicker team.`, timeout: GameConstants.DAY, }); } const windowObject = !App.isUsingClient ? unsafeWindow : window; // Inject handlers if they don't exist yet if (windowObject.epheniaScriptInitializers === undefined) { windowObject.epheniaScriptInitializers = {}; const oldInit = Preload.hideSplashScreen; var hasInitialized = false; // Initializes scripts once enough of the game has loaded Preload.hideSplashScreen = function (...args) { var result = oldInit.apply(this, args); if (App.game && !hasInitialized) { // Initialize all attached userscripts Object.entries(windowObject.epheniaScriptInitializers).forEach(([scriptName, initFunction]) => { try { initFunction(); } catch (e) { reportScriptError(scriptName, e); } }); hasInitialized = true; } return result; } } // Prevent issues with duplicate script names if (windowObject.epheniaScriptInitializers[scriptName] !== undefined) { console.warn(`Duplicate '${scriptName}' userscripts found!`); Notifier.notify({ type: NotificationConstants.NotificationOption.warning, title: scriptName, message: `Duplicate '${scriptName}' userscripts detected. This could cause unpredictable behavior and is not recommended.`, timeout: GameConstants.DAY, }); let number = 2; while (windowObject.epheniaScriptInitializers[`${scriptName} ${number}`] !== undefined) { number++; } scriptName = `${scriptName} ${number}`; } // Add initializer for this particular script windowObject.epheniaScriptInitializers[scriptName] = initFunction; // Run any functions that need to execute before the game starts if (priorityFunction) { $(document).ready(() => { try { priorityFunction(); } catch (e) { reportScriptError(scriptName, e); // Remove main initialization function windowObject.epheniaScriptInitializers[scriptName] = () => null; } }); } } if (!App.isUsingClient || localStorage.getItem('catchfilterfantasia') === 'true') { loadEpheniaScript('catchfilterfantasia', initCatchFilter); }