您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
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); }