diff --git a/enhancedautohatchery.user.js b/enhancedautohatchery.user.js index 81f4872..2e8c7cc 100644 --- a/enhancedautohatchery.user.js +++ b/enhancedautohatchery.user.js @@ -2,10 +2,10 @@ // @name [Pokeclicker] Enhanced Auto Hatchery // @namespace Pokeclicker Scripts // @author Ephenia (Original/Credit: Drak + Ivan Lay, Optimatum) -// @description Automatically hatches eggs at 100% completion. Adds an On/Off button for auto hatching as well as an option for automatically hatching store bought eggs and dug up fossils. +// @description Auto-hatches eggs at 100% and adds controls: Auto Hatch, PKRS Mode, Auto Egg, Auto Fossil, Shiny Fossils, Filter Only, and a Priority ID queue (e.g., 149,123,552,479.01) supporting alternate forms. // @copyright https://github.com/Ephenia // @license GPL-3.0 License -// @version 3.1.4 +// @version 3.3.0-priority // @homepageURL https://github.com/Ephenia/Pokeclicker-Scripts/ // @supportURL https://github.com/Ephenia/Pokeclicker-Scripts/issues @@ -23,6 +23,12 @@ var eggState; var fossilState; var shinyFossilState; var pkrsState; +// Filter Only: restricts loading to current hatchery filter +var filterOnlyState; + +// NEW: user-defined priority Pokémon IDs (array of numbers) +var priorityIdList = []; + var pkrsHatcherySearchTime = 0; var numMonsWithPkrsCached; var autoHatcheryCachedList = []; @@ -36,24 +42,42 @@ function initAutoHatch() { Auto Hatch [${hatchState ? 'ON' : 'OFF'}] ` - breedingModal.querySelector('.modal-header').querySelectorAll('button')[1].outerHTML += ` `; + Shiny Fossils [${shinyFossilState ? 'ON' : 'OFF'}] + + + +
+ + +
+ + `; + + document.getElementById('auto-hatch-start').addEventListener('click', toggleAutoHatch); + document.getElementById('auto-egg').addEventListener('click', toggleEgg); + document.getElementById('auto-fossil').addEventListener('click', toggleFossil); + document.getElementById('shiny-fossils').addEventListener('click', toggleShinyFossil); + document.getElementById('pkrs-mode').addEventListener('click', togglePKRS); + document.getElementById('filter-only').addEventListener('click', toggleFilterOnly); - document.getElementById('auto-hatch-start').addEventListener('click', event => { toggleAutoHatch(event); }); - document.getElementById('auto-egg').addEventListener('click', event => { toggleEgg(event); }); - document.getElementById('auto-fossil').addEventListener('click', event => { toggleFossil(event); }); - document.getElementById('shiny-fossils').addEventListener('click', event => { toggleShinyFossil(event); }); - document.getElementById('pkrs-mode').addEventListener('click', event => { togglePKRS(event); }); + // Load/render priority list UI + renderPriorityUiFromStorage(); + document.getElementById('priority-save').addEventListener('click', savePriorityFromInput); addGlobalStyle('.eggSlot.disabled { pointer-events: unset !important; }'); @@ -61,7 +85,11 @@ function initAutoHatch() { autoHatcheryCachedList = BreedingController.hatcherySortedFilteredList(); // Immediately refresh the cached list when the filtered list or sort settings change - const listUpdateObservables = [BreedingController.hatcheryFilteredList, Settings.getSetting('hatcherySort').observableValue, Settings.getSetting('hatcherySortDirection').observableValue]; + const listUpdateObservables = [ + BreedingController.hatcheryFilteredList, + Settings.getSetting('hatcherySort').observableValue, + Settings.getSetting('hatcherySortDirection').observableValue + ]; listUpdateObservables.forEach(observable => observable.subscribe(() => { autoHatcheryCachedList = BreedingController.hatcherySortedFilteredList(); })); @@ -114,6 +142,84 @@ function togglePKRS(event) { localStorage.setItem('pokerusModeState', pkrsState); } +function toggleFilterOnly(event) { + const element = event.target; + filterOnlyState = !filterOnlyState; + element.classList.replace(...(filterOnlyState ? ['btn-danger', 'btn-success'] : ['btn-success', 'btn-danger'])); + element.textContent = `Filter Only [${filterOnlyState ? 'ON' : 'OFF'}]`; + localStorage.setItem('filterOnly', filterOnlyState); +} + +// ---------- Priority IDs: storage, parsing, UI ---------- + +function renderPriorityUiFromStorage() { + // Load from localStorage + const raw = localStorage.getItem('priorityMonIds') || '[]'; + try { + const arr = JSON.parse(raw); + if (Array.isArray(arr)) priorityIdList = arr.map(n => Number(n)).filter(n => Number.isFinite(n) && n > 0); + } catch { + priorityIdList = []; + } + const input = document.getElementById('priority-ids'); + const status = document.getElementById('priority-status'); + input.value = priorityIdList.join(', '); + status.textContent = priorityIdList.length ? `Priority: [${priorityIdList.join(', ')}]` : 'Priority: (none)'; +} + +function savePriorityFromInput() { + const input = document.getElementById('priority-ids'); + const status = document.getElementById('priority-status'); + // Accept comma/space separated, tolerate extra chars - now supports decimal IDs + const parts = input.value.split(/[^0-9.]+/).filter(Boolean); + const parsed = parts.map(n => Number(n)).filter(n => Number.isFinite(n) && n > 0 && !isNaN(n)); + // De-duplicate while preserving order + const seen = new Set(); + priorityIdList = parsed.filter(n => (seen.has(n) ? false : (seen.add(n), true))); + + localStorage.setItem('priorityMonIds', JSON.stringify(priorityIdList)); + status.textContent = priorityIdList.length ? `Priority: [${priorityIdList.join(', ')}]` : 'Priority: (none)'; + Notifier.notify({ + type: NotificationConstants.NotificationOption.success, + title: '[Auto Hatchery]', + message: `Saved priority IDs: ${priorityIdList.length ? priorityIdList.join(', ') : '(none)'}`, + timeout: GameConstants.SECOND * 3, + }); +} + +// Try adding the next available priority mon (highest priority first) +function autoHatchPriority() { + if (!priorityIdList.length) return false; + + // Build quick lookup from party by Pokédex ID -> first hatchable instance + // We'll scan per priority to keep order strict. + for (let id of priorityIdList) { + // Find a hatchable party member whose dex ID matches + // Support both integer IDs (479) and decimal IDs (479.01) for alternate forms + const mon = App.game.party.caughtPokemon.find(p => { + if (!p || p.breeding || !p.isHatchable()) return false; + // pokemonMap[p.name].id is available elsewhere in script (type was used earlier) + const data = pokemonMap[p.name]; + if (!data) return false; + + // Exact match for decimal IDs (e.g., 479.01) + if (data.id === id) return true; + + // For integer IDs, match both base forms and alternate forms + // This allows 479 to match both 479 (base) and 479.01, 479.02, etc. (alternates) + if (Number.isInteger(id) && Math.floor(data.id) === id) return true; + + return false; + }); + if (mon) { + return App.game.breeding.addPokemonToHatchery(mon); + } + } + return false; +} + +// -------------------------------------------------------- + function bindAutoHatcher() { const progressEggsOld = Breeding.prototype.progressEggs; Breeding.prototype.progressEggs = function progressEggs(...args) { @@ -141,15 +247,18 @@ function autoHatcher() { } while (App.game.breeding.hasFreeEggSlot()) { - // Attempts enabled autoHatch methods in order until one succeeds - // (subsequent autoHatch methods aren't called due to short-circuiting) - let success = pkrsState && autoHatchPkrs(); + // Priority list ALWAYS comes first (strict order) + let success = autoHatchPriority(); + + // Then PKRS / Eggs / Fossils + success ||= pkrsState && autoHatchPkrs(); success ||= eggState && autoHatchEgg(); success ||= fossilState && autoHatchFossil(); + + // Finally, standard list (respects Filter Only toggle) success ||= autoHatchMon(); - if (!success) { - break; - } + + if (!success) break; } } @@ -158,11 +267,9 @@ function autoHatchPkrs() { if (!App.game.keyItems.hasKeyItem(KeyItemType.Pokerus_virus)) { return false; } - // No need to search if we already know there aren't party members to infect if (numMonsWithPkrsCached == App.game.party.caughtPokemon.length) { return false; } - // If we couldn't find a uninfected/contagious pair, wait a while before trying again if (Date.now() - pkrsHatcherySearchTime < delayAfterFailure) { return false; } @@ -171,8 +278,6 @@ function autoHatchPkrs() { let contagious = {}; let foundPair = false; let infectedCount = 0; - // Find first uninfected/contagious pair sharing a type - // Ideally the uninfected mon is dual-type to accelerate future spreading for (let mon of App.game.party.caughtPokemon) { infectedCount += mon.pokerus > GameConstants.Pokerus.Uninfected; if (mon.breeding || mon.level < 100) { @@ -194,21 +299,17 @@ function autoHatchPkrs() { checkMatch = true; } } - // Stop searching upon finding a infectable dual-type if (checkMatch) { for (let type of types) { if (type in uninfectedDual && type in contagious) { foundPair = {'uninfected': uninfectedDual[type], 'contagious': contagious[type]}; } } - if (foundPair) { - break; - } + if (foundPair) break; } } if (!foundPair) { numMonsWithPkrsCached = infectedCount; - // No infectable dual-type pokemon found, try a monotype for (let type of GameHelper.enumNumbers(PokemonType)) { if (type in uninfectedMono && type in contagious) { foundPair = {'uninfected': uninfectedMono[type], 'contagious': contagious[type]}; @@ -236,12 +337,11 @@ function autoHatchEgg() { } function autoHatchFossil() { - // Fossils in inventory with amount > 0 let fossilList = UndergroundItems.list.filter(it => it.valueType === UndergroundItemValueType.Fossil && player.itemList[it.itemName]() > 0); if (fossilList.length == 0) { return false; } - let priorityList = fossilList.filter(f => { + let priorityList = fossilList.filter(f => { const caughtStatus = PartyController.getCaughtStatusByName(GameConstants.FossilToPokemon[f.name]); return caughtStatus == CaughtStatus.NotCaught || (shinyFossilState && caughtStatus == CaughtStatus.Caught); }); @@ -249,22 +349,23 @@ function autoHatchFossil() { fossilList = priorityList; } let fossilToUse = fossilList[Math.floor(Math.random() * fossilList.length)]; - // Workaround as sellMineItem returns null let before = player.amountOfItem(fossilToUse.itemName) UndergroundController.sellMineItem(fossilToUse); let after = player.amountOfItem(fossilToUse.itemName); return before > after; } +// Respects Filter Only toggle function autoHatchMon() { let toHatch = autoHatcheryCachedList.find(p => p.isHatchable()); - if (!toHatch) { - // Nothing matches the hatchery filters - toHatch = App.game.party.caughtPokemon.find(p => p.isHatchable()); + if (filterOnlyState) { + if (!toHatch) return false; + return App.game.breeding.addPokemonToHatchery(toHatch); } if (!toHatch) { - return false; + toHatch = App.game.party.caughtPokemon.find(p => p.isHatchable()); } + if (!toHatch) return false; return App.game.breeding.addPokemonToHatchery(toHatch); } @@ -273,6 +374,14 @@ eggState = loadSetting('autoEgg', false); fossilState = loadSetting('autoFossil', false); shinyFossilState = loadSetting('shinyFossil', false); pkrsState = loadSetting('pokerusModeState', false); +filterOnlyState = loadSetting('filterOnly', false); + +// Load priority list once at boot (UI will re-render it) +try { + const raw = localStorage.getItem('priorityMonIds') || '[]'; + const arr = JSON.parse(raw); + priorityIdList = Array.isArray(arr) ? arr.map(n => Number(n)).filter(n => Number.isFinite(n) && n > 0) : []; +} catch { priorityIdList = []; } function loadSetting(key, defaultVal) { var val; @@ -309,17 +418,14 @@ function loadEpheniaScript(scriptName, initFunction, priorityFunction) { }); } 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(); @@ -333,14 +439,13 @@ function loadEpheniaScript(scriptName, initFunction, priorityFunction) { } } - // 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, + timeout: GameConstants.DAY, }); let number = 2; while (windowObject.epheniaScriptInitializers[`${scriptName} ${number}`] !== undefined) { @@ -348,16 +453,13 @@ function loadEpheniaScript(scriptName, initFunction, priorityFunction) { } 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; } });