// ==UserScript==
// @name [Pokechill] 队伍编辑
// @namespace https://play-pokechill.github.io/
// @version 1.1
// @description 队伍编辑,特性包含所有可记忆的(含道具学习),技能仅排除招牌,中文显示,支持搜索
// @author 人民当家做主
// @license MIT
// @icon https://play-pokechill.github.io/img/icons/icon.png
// @match https://play-pokechill.github.io/*
// @match https://g1tyx.github.io/play-pokechill/*
// @require https://cdn.jsdelivr.net/npm/fuse.js@7.1.0
// @grant none
// ==/UserScript==
(function() {
'use strict';
// ---------- 获取中文翻译 ----------
function getChineseName(id) {
if (!id) return '';
const engName = typeof format === 'function' ? format(id) : id;
if (window.EN_CN_DICT && window.EN_CN_DICT[engName]) {
return window.EN_CN_DICT[engName];
}
return engName;
}
// ---------- 等待游戏核心加载 ----------
function waitForGame() {
if (typeof saved === 'undefined' || typeof pkmn === 'undefined' || typeof move === 'undefined' || typeof ability === 'undefined' || typeof item === 'undefined') {
setTimeout(waitForGame, 300);
return;
}
if (!window.EN_CN_DICT) {
setTimeout(waitForGame, 300);
return;
}
initTeamEditor();
}
// ---------- 初始化 ----------
function initTeamEditor() {
addMenuButton();
createEditorPanel();
bindEvents();
}
// ---------- 添加菜单按钮 ----------
function addMenuButton() {
const menuItems = document.getElementById('menu-items');
if (!menuItems) return;
if (document.getElementById('team-editor-menu-btn')) return;
const btn = document.createElement('div');
btn.id = 'team-editor-menu-btn';
btn.className = 'menu-item';
btn.innerHTML = `
队伍编辑
`;
btn.addEventListener('click', (e) => {
e.stopPropagation();
openTeamEditor();
});
menuItems.appendChild(btn);
}
// ---------- 创建编辑器面板 ----------
function createEditorPanel() {
if (document.getElementById('team-editor-panel')) return;
const panel = document.createElement('div');
panel.id = 'team-editor-panel';
panel.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.85);
z-index: 10000;
display: none;
justify-content: center;
align-items: center;
backdrop-filter: blur(5px);
`;
panel.innerHTML = `
✎ 队伍编辑 (当前队伍: 队伍1)
`;
document.body.appendChild(panel);
}
// ---------- 打开编辑器 ----------
function openTeamEditor() {
const panel = document.getElementById('team-editor-panel');
if (!panel) return;
if (typeof closeTooltip === 'function') closeTooltip();
populateTeamSlots();
panel.style.display = 'flex';
}
// ---------- 关闭编辑器 ----------
function closeTeamEditor() {
const panel = document.getElementById('team-editor-panel');
if (panel) panel.style.display = 'none';
}
// ---------- 填充6个槽位 ----------
function populateTeamSlots() {
const container = document.getElementById('team-editor-slots');
if (!container) return;
const teamName = saved.currentPreviewTeam || 'preview1';
const team = saved.previewTeams[teamName];
if (!team) {
container.innerHTML = '无法读取队伍数据
';
return;
}
document.getElementById('editor-current-team-name').innerText = teamName.replace('preview', '队伍');
let html = '';
for (let i = 1; i <= 6; i++) {
const slot = team[`slot${i}`];
const pokeId = slot?.pkmn;
const pokeData = pokeId ? pkmn[pokeId] : null;
html += `
${pokeData ? `

` : '❌'}
${pokeData ? getChineseName(pokeId) : '空'} ${pokeId || ''}
${pokeData ? `
等级
` : ''}
${pokeData ? `
特性
${[1,2,3,4].map(m => {
const moveId = pokeData.moves?.[`slot${m}`];
return `
${moveId ? getChineseName(moveId) : '--'}
`;
}).join('')}
` : '
空位,无法编辑
'}
`;
}
container.innerHTML = html;
// 绑定事件
container.querySelectorAll('.editor-ability-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
e.stopPropagation();
const slot = btn.dataset.slot;
showAbilitySearch(slot);
});
});
container.querySelectorAll('.editor-move-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
e.stopPropagation();
const slot = btn.dataset.slot;
const moveSlot = btn.dataset.moveSlot;
showMoveSearch(slot, moveSlot);
});
});
container.querySelectorAll('.editor-level').forEach(input => {
input.addEventListener('change', function() {
const slot = this.dataset.slot;
const val = parseInt(this.value, 10);
updatePokemonLevel(slot, val);
});
});
container.querySelectorAll('.editor-ability-input').forEach(inp => {
inp.addEventListener('click', (e) => {
e.stopPropagation();
const slot = inp.dataset.slot;
showAbilitySearch(slot);
});
});
}
// ---------- 更新等级 ----------
function updatePokemonLevel(slot, newLevel) {
const teamName = saved.currentPreviewTeam || 'preview1';
const team = saved.previewTeams[teamName];
const slotKey = `slot${slot}`;
const pokeId = team[slotKey]?.pkmn;
if (!pokeId) return;
const poke = pkmn[pokeId];
if (!poke) return;
poke.level = Math.min(100, Math.max(1, newLevel));
}
// ---------- 构建可记忆特性集合 ----------
function getMemoryAbilities() {
const memorySet = new Set();
for (let itemId in item) {
const it = item[itemId];
if (it.type === 'memory' && it.ability) {
memorySet.add(it.ability);
}
}
return memorySet;
}
// ---------- 显示特性搜索(包含所有可记忆特性) ----------
function showAbilitySearch(slot) {
const teamName = saved.currentPreviewTeam || 'preview1';
const team = saved.previewTeams[teamName];
const slotKey = `slot${slot}`;
const pokeId = team[slotKey]?.pkmn;
if (!pokeId) return;
const poke = pkmn[pokeId];
if (!poke) return;
const memoryAbilities = getMemoryAbilities();
const abilityList = [];
for (let abId in ability) {
const ab = ability[abId];
// 条件:普通特性(type存在) 或者 是可记忆特性(即使type不存在)
if (ab.type || memoryAbilities.has(abId)) {
abilityList.push({
id: abId,
name: getChineseName(abId),
data: ab
});
}
}
const fuse = new Fuse(abilityList, {
keys: ['name', 'id'],
threshold: 0.3,
includeScore: true
});
showSearchPopup('选择特性', abilityList, fuse, (selectedId) => {
poke.ability = selectedId;
if (poke.hiddenAbility && poke.hiddenAbility.id === selectedId) {
poke.hiddenAbilityUnlocked = true;
}
const input = document.querySelector(`.editor-ability-input[data-slot="${slot}"]`);
if (input) input.value = getChineseName(selectedId);
});
}
// ---------- 显示技能搜索(仅排除招牌技能) ----------
function showMoveSearch(slot, moveSlot) {
const teamName = saved.currentPreviewTeam || 'preview1';
const team = saved.previewTeams[teamName];
const slotKey = `slot${slot}`;
const pokeId = team[slotKey]?.pkmn;
if (!pokeId) return;
const poke = pkmn[pokeId];
if (!poke) return;
const moveList = [];
for (let moveId in move) {
const mv = move[moveId];
// 仅排除招牌技能(moveset 为 undefined 的技能)
if (!mv.moveset) continue;
moveList.push({
id: moveId,
name: getChineseName(moveId),
data: mv
});
}
const fuse = new Fuse(moveList, {
keys: ['name', 'id'],
threshold: 0.3,
includeScore: true
});
showSearchPopup(`选择技能 (槽位${moveSlot})`, moveList, fuse, (selectedId) => {
if (!poke.movepool.includes(selectedId)) {
poke.movepool.push(selectedId);
}
poke.moves[`slot${moveSlot}`] = selectedId;
// 更新显示
const container = document.querySelector(`.team-editor-slot[data-slot-index="${slot}"] .editor-moves`);
if (container) {
const moveDivs = container.querySelectorAll('.pkmn-movebox');
if (moveDivs[moveSlot-1]) {
moveDivs[moveSlot-1].querySelector('span').textContent = getChineseName(selectedId);
const img = moveDivs[moveSlot-1].querySelector('img');
img.src = `img/icons/${move[selectedId].type}.svg`;
img.style.backgroundColor = `var(--type-${move[selectedId].type})`;
}
}
});
}
// ---------- 通用搜索弹窗 ----------
function showSearchPopup(title, items, fuseInstance, onSelect) {
const existing = document.getElementById('team-editor-search-popup');
if (existing) existing.remove();
const popup = document.createElement('div');
popup.id = 'team-editor-search-popup';
popup.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 400px;
max-width: 90%;
max-height: 80%;
background: var(--dark1);
border: 2px solid var(--light1);
border-radius: 1rem;
padding: 1rem;
z-index: 20000;
display: flex;
flex-direction: column;
gap: 0.8rem;
color: white;
box-shadow: 0 0 30px black;
`;
popup.innerHTML = `
${title}
`;
document.body.appendChild(popup);
const input = document.getElementById('search-popup-input');
const resultsDiv = document.getElementById('search-popup-results');
const closeBtn = document.getElementById('search-popup-close');
function renderResults(list) {
resultsDiv.innerHTML = '';
if (!list.length) {
resultsDiv.innerHTML = '无结果
';
return;
}
list.slice(0, 50).forEach(item => {
const div = document.createElement('div');
div.style.cssText = `
padding: 0.5rem;
background: var(--dark2);
border-radius: 0.3rem;
cursor: pointer;
display: flex;
justify-content: space-between;
`;
div.innerHTML = `${item.name}${item.id}`;
div.addEventListener('click', () => {
onSelect(item.id);
popup.remove();
});
resultsDiv.appendChild(div);
});
}
renderResults(items);
input.addEventListener('input', () => {
const query = input.value.trim();
if (!query) {
renderResults(items);
return;
}
const results = fuseInstance.search(query).map(r => r.item);
renderResults(results);
});
closeBtn.addEventListener('click', () => popup.remove());
const onKeyDown = (e) => {
if (e.key === 'Escape') popup.remove();
};
window.addEventListener('keydown', onKeyDown, { once: true });
}
// ---------- 绑定事件 ----------
function bindEvents() {
document.addEventListener('click', (e) => {
const closeBtn = e.target.closest('#team-editor-close');
if (closeBtn) closeTeamEditor();
const refreshBtn = e.target.closest('#team-editor-refresh');
if (refreshBtn) populateTeamSlots();
const saveBtn = e.target.closest('#team-editor-save');
if (saveBtn) {
if (typeof updatePreviewTeam === 'function') updatePreviewTeam();
closeTeamEditor();
}
});
}
waitForGame();
})();