// ==UserScript== // @name Pokechill精灵获取图鉴(含隐藏) // @namespace http://tampermonkey.net/ // @version 1.5 // @description 显示所有宝可梦(包括隐藏)的获取方式,隐藏宝可梦固定标注,镰刀盔B标记为暂不可获得。实时过滤所有极巨化宝可梦和烈空坐。点击行可查看详情。新增移动端优化及筛选(未获得/未闪光)。移动端不可拖动。 // @author 人民当家做主 (Modified) // @match https://play-pokechill.github.io/* // @match https://play-pokechill.g8hh.com.cn/* // @match https://g1tyx.github.io/play-pokechill/* // @grant none // ==/UserScript== (function() { 'use strict'; // ---------- 配置 ---------- // 手动排除的特殊形态(非极巨化,不包含 gmax) const EXCLUDE_IDS = new Set([ 'arceus', 'burmySandy', 'burmyTrash', 'galarianMrmime', 'kyuremWhite', 'megaAbsol', 'megaAltaria', 'megaAmpharos', 'megaAudino', 'megaBanette', 'megaGardevoir', 'megaHoundoom', 'megaKangaskhan', 'megaLopunny', 'megaMedicham', 'megaRayquaza', 'megaSableye', 'megaSalamence', 'megaSharpedo', 'megaSlowbro', 'wormadamSandy', 'wormadamTrash' ]); // 辅助翻译 function t(english) { if (!english) return english; const formatted = typeof window.format === 'function' ? window.format(english) : english; return (window.EN_CN_DICT && window.EN_CN_DICT[formatted]) || formatted; } function getChineseName(key) { return t(key); } function isAreaCatchable(area) { if (area.uncatchable === true) return false; if (area.type === 'dungeon') return false; if (area.trainer === true) return false; return true; } // ---------- 获取详细获取方式(隐藏宝可梦特殊处理)---------- function getAcquisitionMethod(id) { const p = pkmn[id]; if (!p) return '未知'; // 特殊处理:镰刀盔B if (id === 'kabutopsB') { return '暂不可获得'; } // 隐藏宝可梦:直接返回固定信息 if (p.hidden === true) { return '隐藏宝可梦'; } const sources = []; // 1. 进化获得 const preEvoList = []; for (const key in pkmn) { const candidate = pkmn[key]; if (candidate.evolve) { const evoObj = candidate.evolve(); for (const slot in evoObj) { if (evoObj[slot].pkmn === p) { if (!candidate.hidden) { let condition = ''; if (evoObj[slot].level) { condition = `在等级 ${evoObj[slot].level} `; } if (evoObj[slot].item) { const itemId = evoObj[slot].item.id; const itemName = getChineseName(itemId); condition += `使用 ${itemName} `; } condition += '进化获得'; preEvoList.push(`由 ${getChineseName(candidate.id)} ${condition}`); } } } } } if (preEvoList.length > 0) sources.push(...preEvoList); // 2. 商店购买 if (typeof shop !== 'undefined') { for (const key in shop) { if (shop[key].pkmn === id) { sources.push(t('Poke-Mart') + '购买'); break; } } } // 3. 遍历所有区域 if (typeof areas !== 'undefined') { for (const areaId in areas) { const area = areas[areaId]; let areaName = area.name || areaId; const areaNameCN = getChineseName(areaName); let prefix = ''; if (area.type === 'wild') prefix = t('Wild Area') + '--'; else if (area.type === 'dungeon') prefix = t('Dungeons') + '--'; else if (area.type === 'event') prefix = t('Events') + '--'; else if (area.type === 'frontier') prefix = t('Battle Frontier') + '--'; else if (area.type === 'dimension') prefix = t('Mega Dimension') + '--'; else if (area.type === 'season') prefix = '季节活动--'; else if (area.type === 'vs' && area.name) prefix = t('VS') + ' ' + t('Trainer') + ' '; let rotationSuffix = ''; if (area.rotation !== undefined) { rotationSuffix = ` (${t('Rotation')}${area.rotation})`; } if (area.spawns && isAreaCatchable(area)) { for (const rarity in area.spawns) { const list = area.spawns[rarity]; if (Array.isArray(list) && list.some(p => p.id === id)) { const rarityCN = { common: t('common'), uncommon: t('uncommon'), rare: t('rare') }[rarity] || rarity; sources.push(`${prefix}${areaNameCN}${rotationSuffix} (${rarityCN})`); } } } if (area.reward) { const rewardList = area.reward; if (Array.isArray(rewardList) && rewardList.some(p => p.id === id)) { sources.push(`${prefix}${area.name ? areaNameCN + ' ' + t('Reward') : areaNameCN + ' ' + t('Reward')}`); } } if (area.itemReward) { for (const key in area.itemReward) { const reward = area.itemReward[key]; if (reward.item === id) { sources.push(`${prefix}${area.name ? areaNameCN + ' ' + t('Reward') : areaNameCN + ' ' + t('Reward')}`); } } } } } // 4. 开拓区专属 if (typeof exclusiveFrontierPkmn !== 'undefined' && exclusiveFrontierPkmn.includes(p)) { sources.push(t('Battle Frontier') + '随机奖励'); } // 5. 野生公园 if (typeof areas !== 'undefined' && areas.wildlifePark) { const park = areas.wildlifePark; if (park.spawns) { for (const rarity in park.spawns) { const list = park.spawns[rarity]; if (Array.isArray(list) && list.some(p => p.id === id)) { const rarityCN = { common: t('common'), uncommon: t('uncommon'), rare: t('rare') }[rarity] || rarity; sources.push(`野生公园 (${rarityCN})`); } } } } if (sources.length > 0) { const unique = [...new Set(sources)]; return unique.join(';'); } const tag = p.tagObtainedIn; const map = { 'wild': t('Wild Area'), 'event': t('Events'), 'park': '野生公园', 'frontier': t('Battle Frontier'), 'mart': t('Poke-Mart'), 'unobtainable': '不可获取', 'arceus': '阿尔宙斯解锁' }; return map[tag] || '其他'; } // ---------- 检测闪光数据是否存在 ---------- function hasShinyData() { if (typeof pkmn !== 'object') return false; for (let id in pkmn) { if (pkmn[id] && typeof pkmn[id].shinyCaught !== 'undefined') { return true; } } return false; } // ---------- 获取所有可显示宝可梦(实时过滤极巨化和烈空坐)---------- function fetchAllPokemon() { const list = []; for (const id in pkmn) { const p = pkmn[id]; // 实时过滤:排除所有包含 "gmax" 的极巨化宝可梦(不区分大小写) if (id.toLowerCase().includes('gmax')) continue; // 排除烈空坐 if (id === 'rayquaza') continue; // 排除手动配置的其他特殊形态 if (EXCLUDE_IDS.has(id)) continue; // 排除不可获取的宝可梦 if (p.tagObtainedIn === 'unobtainable') continue; list.push({ id, name: getChineseName(id), method: getAcquisitionMethod(id), caught: p.caught || 0, shinyCaught: p.shinyCaught || 0 // 若无此字段,默认为0 }); } list.sort((a, b) => a.id.localeCompare(b.id)); return list; } // ---------- 根据筛选条件过滤 ---------- function filterByOptions(data, showUncaught, showUnshiny) { return data.filter(item => { if (showUncaught && item.caught > 0) return false; if (showUnshiny && item.shinyCaught > 0) return false; return true; }); } // ---------- 文本搜索 ---------- function filterByText(data, keyword) { if (!keyword) return data; const lower = keyword.toLowerCase(); return data.filter(item => item.id.toLowerCase().includes(lower) || item.name.toLowerCase().includes(lower) || item.method.toLowerCase().includes(lower) ); } // ---------- 面板状态 ---------- let allPokemonData = []; let filteredData = []; let filterText = ''; let showUncaught = true; let showUnshiny = false; let panel = null; let tbodyRef = null; let statsCountRef = null; let panelCollapsed = false; // 更新表格显示 function updateDisplay() { if (!tbodyRef || !statsCountRef) return; let afterOptions = filterByOptions(allPokemonData, showUncaught, showUnshiny); let afterText = filterByText(afterOptions, filterText); filteredData = afterText; statsCountRef.textContent = filteredData.length; const rowsHtml = filteredData.map(item => ` ${item.id} ${item.name} ${item.method} `).join('') || '🎉 没有匹配的宝可梦'; tbodyRef.innerHTML = rowsHtml; tbodyRef.querySelectorAll('tr[data-pkmn]').forEach(row => { row.addEventListener('click', (e) => { const id = row.dataset.pkmn; if (typeof tooltipData === 'function') { if (typeof closeTooltip === 'function') { closeTooltip(); setTimeout(() => tooltipData('pkmn', id), 200); } else { tooltipData('pkmn', id); } } else { alert(`宝可梦: ${id}\n获取方式: ${getAcquisitionMethod(id)}`); } }); }); } // 刷新数据 function refreshData() { allPokemonData = fetchAllPokemon(); updateDisplay(); } // 折叠/展开 function toggleCollapse() { panelCollapsed = !panelCollapsed; const content = panel.querySelector('.panel-content'); const footer = panel.querySelector('.footer'); const toggleBtn = panel.querySelector('.collapse-btn'); if (panelCollapsed) { content.style.display = 'none'; footer.style.display = 'none'; toggleBtn.innerHTML = '▶'; panel.style.height = 'auto'; } else { content.style.display = 'block'; footer.style.display = 'block'; toggleBtn.innerHTML = '▼'; panel.style.height = 'auto'; } } // ---------- 样式 ---------- const style = document.createElement('style'); style.textContent = ` #pkmn-missing-panel { position: fixed; bottom: 15px; right: 15px; width: 500px; max-width: calc(100vw - 30px); background: rgba(28, 28, 35, 0.96); backdrop-filter: blur(8px); border: 1px solid rgba(120, 120, 150, 0.3); border-radius: 20px; box-shadow: 0 12px 28px rgba(0, 0, 0, 0.6); color: #f0f0f0; font-family: 'Segoe UI', Roboto, system-ui, sans-serif; font-size: 13px; z-index: 9999; resize: both; overflow: hidden; user-select: none; transition: opacity 0.2s, box-shadow 0.2s; letter-spacing: 0.3px; } @media (max-width: 600px) { #pkmn-missing-panel { left: 10px !important; right: 10px !important; bottom: 10px !important; width: auto !important; max-width: none; font-size: 14px; } .id-col { width: 70px; } .name-col { width: 90px; } .method-col { max-width: 180px; } .panel-header { font-size: 16px; padding: 12px 16px; cursor: default; } .panel-header button { min-width: 44px; min-height: 44px; font-size: 18px; } .panel-content { padding: 12px; max-height: 60vh; } .stats { padding: 10px 12px; font-size: 15px; } .stats .count { font-size: 28px; } .search-box { padding: 12px 16px; font-size: 16px; } .filter-bar { flex-wrap: wrap; gap: 12px; padding: 8px 0 12px; } .filter-item { font-size: 15px; } .filter-item input[type="checkbox"] { width: 20px; height: 20px; margin-right: 6px; transform: scale(1.2); } th, td { padding: 8px 6px; } .footer { padding: 12px; font-size: 13px; } } #pkmn-missing-panel.dragging { opacity: 0.85; box-shadow: 0 18px 32px rgba(0, 0, 0, 0.7); transition: none; } .panel-header { padding: 12px 18px; background: linear-gradient(145deg, #2f2f3c, #23232e); border-radius: 20px 20px 0 0; cursor: move; display: flex; justify-content: space-between; align-items: center; font-weight: 600; border-bottom: 1px solid #45455a; color: #f0e6d0; font-size: 16px; } .panel-header-left { display: flex; align-items: center; gap: 12px; } .panel-header-left span { display: flex; align-items: center; gap: 8px; } .panel-header-left span::before { content: "📋"; font-size: 20px; filter: drop-shadow(0 2px 3px #00000055); } .collapse-btn { background: #3a3a4c; border: none; color: #ddd; font-size: 18px; cursor: pointer; padding: 6px 12px; border-radius: 30px; transition: background 0.2s; line-height: 1; box-shadow: 0 2px 4px #00000033; min-width: 44px; text-align: center; } .collapse-btn:hover { background: #5a5a78; color: white; } .panel-header button:not(.collapse-btn) { background: #3a3a4c; border: none; color: #ddd; font-size: 18px; cursor: pointer; padding: 6px 12px; border-radius: 30px; transition: background 0.2s; line-height: 1; margin-left: 8px; box-shadow: 0 2px 4px #00000033; min-width: 44px; } .panel-header button:not(.collapse-btn):hover { background: #5a5a78; color: white; } .panel-content { padding: 18px; max-height: 520px; overflow-y: auto; scrollbar-width: thin; scrollbar-color: #6a6a8a #2d2d3a; transition: max-height 0.2s ease; } .panel-content::-webkit-scrollbar { width: 8px; } .panel-content::-webkit-scrollbar-track { background: #2d2d3a; border-radius: 8px; } .panel-content::-webkit-scrollbar-thumb { background: #6a6a8a; border-radius: 8px; border: 2px solid #2d2d3a; } .stats { background: #1d1d28; border-radius: 40px; padding: 12px 18px; margin-bottom: 16px; text-align: center; font-size: 16px; border: 1px solid #3e3e52; box-shadow: inset 0 2px 5px #00000033; display: flex; justify-content: center; align-items: center; gap: 8px; } .stats .count { color: #ffd966; font-weight: 700; font-size: 28px; line-height: 1; text-shadow: 0 0 10px #ffb34755; } .filter-bar { display: flex; justify-content: space-around; align-items: center; background: #262633; border-radius: 40px; padding: 10px 16px; margin-bottom: 16px; border: 1px solid #4b4b64; } .filter-item { display: flex; align-items: center; gap: 6px; color: #ddd; font-size: 14px; cursor: pointer; } .filter-item input[type="checkbox"] { width: 18px; height: 18px; cursor: pointer; accent-color: #6a8cff; } .search-box { width: 100%; padding: 12px 18px; margin-bottom: 16px; background: #21212e; border: 1px solid #4b4b64; border-radius: 40px; color: #f0f0f0; font-size: 15px; outline: none; box-sizing: border-box; transition: border-color 0.2s, box-shadow 0.2s; box-shadow: 0 2px 6px #00000033; } .search-box::placeholder { color: #8888a0; font-style: italic; } .search-box:focus { border-color: #9a9aff; box-shadow: 0 0 12px #5a5aff66; } .refresh-btn { background: #3f4d7a; border: none; color: white; padding: 6px 14px; border-radius: 30px; cursor: pointer; font-size: 14px; font-weight: 500; transition: background 0.2s, transform 0.1s; box-shadow: 0 3px 6px #00000044; margin-left: 8px; } .refresh-btn:hover { background: #5f73b0; transform: scale(1.02); } table { width: 100%; border-collapse: collapse; background: #1a1a24; border-radius: 20px; overflow: hidden; box-shadow: 0 4px 12px #00000055; table-layout: fixed; } th { background: #2c2c3c; padding: 12px 8px; text-align: left; font-weight: 600; color: #c0c0e0; font-size: 13px; border-bottom: 1px solid #4a4a60; } td { padding: 10px 8px; border-top: 1px solid #333342; cursor: pointer; transition: background 0.15s; word-break: break-word; } tr:hover td { background: #2f2f42; } .id-col { color: #b0d0ff; font-family: 'JetBrains Mono', 'Fira Code', monospace; font-size: 12px; width: 85px; } .name-col { color: #f0f0f0; font-weight: 500; width: 110px; } .method-col { color: #c0e6b0; white-space: normal; } .footer { padding: 12px; text-align: center; font-size: 12px; color: #7a7a90; border-top: 1px solid #2e2e3e; background: #1b1b26; } `; document.head.appendChild(style); // ---------- 创建面板 ---------- if (document.getElementById('pkmn-missing-panel')) { document.getElementById('pkmn-missing-panel').remove(); } panel = document.createElement('div'); panel.id = 'pkmn-missing-panel'; const shinySupported = hasShinyData(); const shinyCheckboxHtml = shinySupported ? ` ` : ''; panel.innerHTML = `
Pokechill获取图鉴(含隐藏)V1.7
0
${shinyCheckboxHtml}
编号名称获取方式
`; document.body.appendChild(panel); // 获取 DOM 引用 tbodyRef = panel.querySelector('tbody'); statsCountRef = panel.querySelector('.stats .count'); const searchInput = panel.querySelector('.search-box'); const refreshBtn = panel.querySelector('.refresh-btn'); const closeBtn = panel.querySelector('.close-btn'); const collapseBtn = panel.querySelector('.collapse-btn'); const header = panel.querySelector('.panel-header'); const uncaughtCheck = panel.querySelector('#uncaughtFilter'); const shinyCheck = panel.querySelector('#shinyFilter'); // 初始化数据 refreshData(); // 事件绑定 if (uncaughtCheck) { uncaughtCheck.addEventListener('change', (e) => { showUncaught = e.target.checked; updateDisplay(); }); } if (shinyCheck) { shinyCheck.addEventListener('change', (e) => { showUnshiny = e.target.checked; updateDisplay(); }); } let debounceTimer; searchInput.addEventListener('input', (e) => { clearTimeout(debounceTimer); debounceTimer = setTimeout(() => { filterText = e.target.value; updateDisplay(); }, 200); }); refreshBtn.addEventListener('click', () => { refreshData(); }); closeBtn.addEventListener('click', () => { panel.remove(); }); collapseBtn.addEventListener('click', (e) => { e.stopPropagation(); toggleCollapse(); }); // ---------- 拖拽功能(移动端禁用)---------- let isDragging = false; let offsetX, offsetY; function startDrag(clientX, clientY) { if (window.innerWidth <= 600) return; isDragging = true; panel.classList.add('dragging'); const rect = panel.getBoundingClientRect(); offsetX = clientX - rect.left; offsetY = clientY - rect.top; } header.addEventListener('mousedown', (e) => { if (e.target.tagName === 'BUTTON' || e.target.tagName === 'INPUT') return; startDrag(e.clientX, e.clientY); }); header.addEventListener('touchstart', (e) => { if (e.target.tagName === 'BUTTON' || e.target.tagName === 'INPUT') return; const touch = e.touches[0]; startDrag(touch.clientX, touch.clientY); e.preventDefault(); }, { passive: false }); function doDrag(clientX, clientY) { if (!isDragging) return; let left = clientX - offsetX; let top = clientY - offsetY; left = Math.max(0, Math.min(window.innerWidth - panel.offsetWidth, left)); top = Math.max(0, Math.min(window.innerHeight - panel.offsetHeight, top)); panel.style.left = left + 'px'; panel.style.top = top + 'px'; panel.style.right = 'auto'; panel.style.bottom = 'auto'; } document.addEventListener('mousemove', (e) => { if (!isDragging) return; e.preventDefault(); doDrag(e.clientX, e.clientY); }); document.addEventListener('touchmove', (e) => { if (!isDragging) return; e.preventDefault(); const touch = e.touches[0]; doDrag(touch.clientX, touch.clientY); }, { passive: false }); document.addEventListener('mouseup', () => { isDragging = false; panel.classList.remove('dragging'); }); document.addEventListener('touchend', () => { isDragging = false; panel.classList.remove('dragging'); }); })();