// ==UserScript==
// @name [Pokechill] 自定义木桩测试-龟龟
// @namespace https://play-pokechill.github.io/
// @version 2.1
// @description 木桩技能池基于所选属性动态过滤,包含所有可学技能。重置按钮同时清空已选技能,修复卡片图片显示。新增种族值星级编辑(0-6星)。移动端适配优化。
// @author 人民当家做主
// @icon https://play-pokechill.github.io/img/icons/icon.png
// @match https://play-pokechill.github.io/*
// @match https://g1tyx.github.io/play-pokechill/*
// @grant none
// ==/UserScript==
(function() {
'use strict';
const dummyAreaId = 'dummyCustom';
const dummyPkmnId = 'dummy_custom';
// 自定义木桩图片(如果图片失效,请替换为有效链接)
const DUMMY_SPRITE_URL = 'https://picui.ogmua.cn/s1/2026/03/10/69afd44fed40b.webp';
function waitForGame() {
if (typeof areas === 'undefined' || typeof pkmn === 'undefined' || typeof move === 'undefined' || typeof ability === 'undefined') {
setTimeout(waitForGame, 200);
return;
}
console.log('[DummyCustom] 游戏核心已加载,开始注入...');
initDummyPokemon();
injectDummyTarget();
createConfigPanel();
setupImageErrorHandler();
addMobileStyles(); // 添加移动端适配样式
}
waitForGame();
// 全局图片错误处理:将木桩相关的图片请求替换为自定义图片
function setupImageErrorHandler() {
document.addEventListener('error', function(e) {
const img = e.target;
if (img.tagName !== 'IMG') return;
if (img.src.includes(`/sprite/${dummyPkmnId}.png`) ||
img.src.includes(`/sprite/${dummyPkmnId}.gif`) ||
img.src.includes(`/trainers/.png`)) {
if (img.src.includes('/trainers/.png')) {
img.style.display = 'none';
e.preventDefault();
return;
}
img.src = DUMMY_SPRITE_URL;
img.onerror = null;
console.log('[DummyCustom] 替换图片为自定义木桩图片');
}
}, true);
}
function initDummyPokemon() {
if (!pkmn[dummyPkmnId]) {
if (!ability.none) {
ability.none = {
id: 'none',
rename: '无',
rarity: 1,
type: ['all'],
info: function() { return '没有任何效果。'; }
};
}
pkmn[dummyPkmnId] = {
id: dummyPkmnId,
rename: '龟龟',
type: ['normal'],
bst: { hp: 4, atk: 4, def: 4, satk: 4, sdef: 4, spe: 4 },
level: 100,
exp: 0,
caught: 0,
shiny: false,
ability: 'none',
hiddenAbility: undefined,
hiddenAbilityUnlocked: false,
movepool: [],
moves: { slot1: undefined, slot2: undefined, slot3: undefined, slot4: undefined },
ivs: { hp: 0, atk: 0, def: 0, satk: 0, sdef: 0, spe: 0 },
pokerus: false,
ribbons: [],
newMoves: [],
newPokemon: undefined,
newEvolution: undefined,
tag: undefined,
lockHp: true
};
}
}
// 根据当前属性刷新可学技能池(基于 moveset 过滤)
function refreshDummyMovepool() {
const dummy = pkmn[dummyPkmnId];
if (!dummy) return;
const types = dummy.type;
const newMovepool = [];
for (const moveId in move) {
const m = move[moveId];
if (m.moveset) {
if (m.moveset.includes('all') || types.some(t => m.moveset.includes(t))) {
newMovepool.push(moveId);
}
}
}
dummy.movepool = newMovepool;
console.log(`[DummyCustom] 刷新技能池,共 ${newMovepool.length} 个技能(基于属性 ${types.join('/')})`);
}
function ensureDummyArea() {
if (!areas[dummyAreaId]) {
areas[dummyAreaId] = {
name: `木桩测试 (可配置)`,
background: 'gym',
trainer: true,
type: 'vs',
level: 100,
team: {
slot1: pkmn[dummyPkmnId],
slot1Moves: [undefined, undefined, undefined, undefined]
},
dummy: true,
defeated: false,
unlockRequirement: () => true,
reward: []
};
}
}
// 生成0-6星选项的HTML
function generateStarOptions(selected) {
let options = '';
for (let i = 0; i <= 6; i++) {
options += ``;
}
return options;
}
// 添加移动端适配样式
function addMobileStyles() {
const style = document.createElement('style');
style.textContent = `
@media (max-width: 768px) {
#dummy-config-panel {
width: 95vw !important;
min-width: unset !important;
padding: 1rem !important;
font-size: 14px !important;
}
#dummy-config-panel select,
#dummy-config-panel input[type="number"] {
font-size: 16px !important; /* 防止iOS缩放 */
padding: 0.5rem !important;
}
#dummy-config-panel button {
padding: 0.8rem 1rem !important;
font-size: 16px !important;
}
#dummy-config-panel .vs-card {
max-width: 100% !important;
}
/* 种族值两列布局在移动端保持两列,但可适当调整间距 */
#dummy-config-panel [style*="grid-template-columns: repeat(2, 1fr)"] {
gap: 0.8rem !important;
}
/* 锁血和技能按钮行改为垂直排列(如果空间不足) */
#dummy-config-panel > div > div[style*="justify-content:space-between"] {
flex-direction: column;
align-items: stretch !important;
gap: 0.8rem;
}
#dummy-config-panel > div > div[style*="justify-content:space-between"] button {
width: 100%;
}
}
`;
document.head.appendChild(style);
}
function createConfigPanel() {
if (document.getElementById('dummy-config-panel')) return;
const panel = document.createElement('div');
panel.id = 'dummy-config-panel';
panel.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: var(--light2, #ECDEB7);
border: 2px solid var(--light1, #94886B);
border-radius: 0.5rem;
padding: 1.5rem;
z-index: 10000;
display: none;
flex-direction: column;
gap: 1rem;
min-width: 350px;
max-width: 90vw;
max-height: 90vh;
overflow-y: auto;
color: var(--dark1, #36342F);
font-family: 'Courier New', monospace;
box-shadow: 0 0 20px rgba(0,0,0,0.5);
`;
// 预读取当前木桩的种族值用于初始化下拉框(实际在打开面板时设置)
const dummy = pkmn[dummyPkmnId];
const bst = dummy ? dummy.bst : { hp:6, atk:4, def:2, satk:4, sdef:2, spe:4 };
panel.innerHTML = `
配置木桩
`;
document.body.appendChild(panel);
// 辅助函数:从界面读取种族值并更新木桩对象
function updateBstFromUI() {
const dummy = pkmn[dummyPkmnId];
dummy.bst.hp = parseInt(document.getElementById('dummy-bst-hp').value, 10);
dummy.bst.atk = parseInt(document.getElementById('dummy-bst-atk').value, 10);
dummy.bst.def = parseInt(document.getElementById('dummy-bst-def').value, 10);
dummy.bst.satk = parseInt(document.getElementById('dummy-bst-satk').value, 10);
dummy.bst.sdef = parseInt(document.getElementById('dummy-bst-sdef').value, 10);
dummy.bst.spe = parseInt(document.getElementById('dummy-bst-spe').value, 10);
}
// 辅助函数:将木桩对象的种族值设置到UI
function setBstToUI() {
const dummy = pkmn[dummyPkmnId];
document.getElementById('dummy-bst-hp').value = dummy.bst.hp;
document.getElementById('dummy-bst-atk').value = dummy.bst.atk;
document.getElementById('dummy-bst-def').value = dummy.bst.def;
document.getElementById('dummy-bst-satk').value = dummy.bst.satk;
document.getElementById('dummy-bst-sdef').value = dummy.bst.sdef;
document.getElementById('dummy-bst-spe').value = dummy.bst.spe;
}
const updateType = () => {
const type1 = document.getElementById('dummy-type1').value;
const type2 = document.getElementById('dummy-type2').value;
pkmn[dummyPkmnId].type = type2 ? [type1, type2] : [type1];
};
document.getElementById('dummy-type1').addEventListener('change', updateType);
document.getElementById('dummy-type2').addEventListener('change', updateType);
// 配置技能按钮:保存当前所有配置(包括种族值),刷新技能池,打开编辑器
document.getElementById('dummy-config-skills').addEventListener('click', () => {
updateType();
const level = parseInt(document.getElementById('dummy-level').value, 10);
pkmn[dummyPkmnId].level = Math.min(100, Math.max(1, level));
pkmn[dummyPkmnId].lockHp = document.getElementById('dummy-lockhp').checked;
updateBstFromUI(); // 保存种族值
refreshDummyMovepool();
panel.style.display = 'none';
if (typeof tooltipData === 'function') {
tooltipData('pkmnEditor', dummyPkmnId);
} else {
alert('无法打开编辑器');
}
});
// 重置按钮:恢复默认值(属性、等级、锁血、种族值、清空技能)
document.getElementById('dummy-config-reset').addEventListener('click', () => {
document.getElementById('dummy-type1').value = 'normal';
document.getElementById('dummy-type2').value = '';
document.getElementById('dummy-level').value = 100;
document.getElementById('dummy-lockhp').checked = true;
const dummy = pkmn[dummyPkmnId];
dummy.type = ['normal'];
dummy.level = 100;
dummy.lockHp = true;
// 重置种族值为初始值
dummy.bst = { hp:6, atk:4, def:2, satk:4, sdef:2, spe:4 };
// 清空已选技能
dummy.moves = { slot1: undefined, slot2: undefined, slot3: undefined, slot4: undefined };
// 更新UI中的种族值下拉框
setBstToUI();
refreshDummyMovepool();
console.log('[DummyCustom] 木桩已重置为默认值,技能已清空');
});
// 确定按钮:保存所有配置并进入对战预览
document.getElementById('dummy-config-ok').addEventListener('click', () => {
const type1 = document.getElementById('dummy-type1').value;
const type2 = document.getElementById('dummy-type2').value;
const level = parseInt(document.getElementById('dummy-level').value, 10);
const lockHp = document.getElementById('dummy-lockhp').checked;
const dummy = pkmn[dummyPkmnId];
dummy.type = type2 ? [type1, type2] : [type1];
dummy.level = Math.min(100, Math.max(1, level));
dummy.lockHp = lockHp;
dummy.playerHp = undefined;
updateBstFromUI(); // 保存种族值
panel.style.display = 'none';
saved.currentAreaBuffer = dummyAreaId;
document.getElementById('preview-team-exit').style.display = 'flex';
document.getElementById('team-menu').style.zIndex = '50';
document.getElementById('team-menu').style.display = 'flex';
document.getElementById('menu-button-parent').style.display = 'none';
updatePreviewTeam();
afkSeconds = 0;
document.getElementById('explore-menu').style.display = 'none';
});
document.getElementById('dummy-config-cancel').addEventListener('click', () => {
panel.style.display = 'none';
});
}
function injectDummyTarget() {
ensureDummyArea();
const originalUpdateVS = updateVS;
updateVS = function() {
originalUpdateVS();
const vsListing = document.getElementById('vs-listing');
if (!vsListing) return;
const existingCards = vsListing.querySelectorAll(`[data-trainer="${dummyAreaId}"]`);
existingCards.forEach(card => card.remove());
const dummyCard = document.createElement('div');
dummyCard.className = 'vs-card';
dummyCard.dataset.trainer = dummyAreaId;
dummyCard.innerHTML = `
自定义木桩
测试木桩
`;
dummyCard.addEventListener('click', () => {
const panel = document.getElementById('dummy-config-panel');
if (panel) {
const dummy = pkmn[dummyPkmnId];
document.getElementById('dummy-type1').value = dummy.type[0] || 'normal';
document.getElementById('dummy-type2').value = dummy.type[1] || '';
document.getElementById('dummy-level').value = dummy.level;
document.getElementById('dummy-lockhp').checked = dummy.lockHp;
// 同步种族值到UI
document.getElementById('dummy-bst-hp').value = dummy.bst.hp;
document.getElementById('dummy-bst-atk').value = dummy.bst.atk;
document.getElementById('dummy-bst-def').value = dummy.bst.def;
document.getElementById('dummy-bst-satk').value = dummy.bst.satk;
document.getElementById('dummy-bst-sdef').value = dummy.bst.sdef;
document.getElementById('dummy-bst-spe').value = dummy.bst.spe;
panel.style.display = 'flex';
}
});
vsListing.appendChild(dummyCard);
};
}
const originalSetWildPkmn = setWildPkmn;
setWildPkmn = function() {
if (saved.currentArea === dummyAreaId) {
const dummy = pkmn[dummyPkmnId];
if (!dummy) return;
barProgressWild = 0;
exploreCombatWildTurn = 1;
['team-indicator', 'spiraling-indicator', 'factory-indicator', 'training-indicator', 'raid-timer-indicator']
.forEach(id => document.getElementById(id) && (document.getElementById(id).style.display = 'none'));
saved.currentPkmn = dummyPkmnId;
wildLevel = dummy.level;
const hpStars = dummy.bst.hp;
wildPkmnHp = (100 + (hpStars * 30) * (1 + dummy.level * 0.2)) * 4;
wildPkmnHpMax = wildPkmnHp;
document.getElementById('explore-wild-name').innerHTML = (dummy.rename || '木桩') + ` lvl ${dummy.level}`;
const sprite = document.getElementById('explore-wild-sprite');
sprite.src = DUMMY_SPRITE_URL;
sprite.dataset.dummy = "true";
sprite.onerror = function() {
console.warn('木桩图片加载失败');
};
if (pkmn.psyduck?.float) sprite.classList.add('floating-pkmn');
else sprite.classList.remove('floating-pkmn');
document.getElementById('explore-wild-sprite-data').dataset.pkmn = dummyPkmnId;
const moves = [dummy.moves.slot1, dummy.moves.slot2, dummy.moves.slot3, dummy.moves.slot4];
const container = document.getElementById('explore-header-moves-wild');
container.innerHTML = '';
for (let i = 0; i < 4; i++) {
const moveId = moves[i];
if (!moveId) {
const emptyDiv = document.createElement('div');
emptyDiv.className = 'pkmn-movebox';
emptyDiv.style.pointerEvents = 'none';
emptyDiv.style.opacity = '0.3';
container.appendChild(emptyDiv);
} else {
const moveDiv = document.createElement('div');
moveDiv.className = 'pkmn-movebox';
moveDiv.style.borderColor = returnTypeColor(move[moveId].type);
moveDiv.id = `pkmn-movebox-wild-${i+1}`;
moveDiv.innerHTML = `
${format(moveId)}
`;
moveDiv.dataset.move = moveId;
container.appendChild(moveDiv);
}
}
updateWildPkmn();
voidAnimation('explore-wild-sprite', 'wildPokemonSpawn 0.5s 1');
return;
}
const sprite = document.getElementById('explore-wild-sprite');
if (sprite) delete sprite.dataset.dummy;
originalSetWildPkmn();
};
const originalExploreCombatPlayer = exploreCombatPlayer;
exploreCombatPlayer = function() {
originalExploreCombatPlayer();
if (saved.currentArea === dummyAreaId && pkmn[dummyPkmnId] && pkmn[dummyPkmnId].lockHp) {
if (wildPkmnHp <= 0) {
wildPkmnHp = wildPkmnHpMax;
updateWildPkmn();
}
}
};
const originalExploreCombatWild = exploreCombatWild;
exploreCombatWild = function() {
originalExploreCombatWild();
if (saved.currentArea === dummyAreaId && pkmn[dummyPkmnId] && pkmn[dummyPkmnId].lockHp) {
wildPkmnHp = wildPkmnHpMax;
updateWildPkmn();
}
};
// 安全包装原函数,忽略跨域错误
if (typeof trimTransparent === 'function') {
const originalTrimTransparent = trimTransparent;
trimTransparent = function(img) {
try {
return originalTrimTransparent(img);
} catch (e) {
if (e.name === 'SecurityError' && e.message.includes('cross-origin')) {
console.warn('[DummyCustom] 忽略跨域裁剪错误');
return { width: img.naturalWidth, height: img.naturalHeight };
}
throw e;
}
};
}
})();