Floating mod menu for PokeLike
// ==UserScript==
// @name PokeLike Mod Menu
// @namespace pokelike-mod-menu
// @version 3.0.1
// @description Floating mod menu for PokeLike
// @match https://pokelike.xyz/*
// @grant none
// @run-at document-idle
// @license MIT
// ==/UserScript==
(() => {
'use strict';
const MENU_ID = 'plmm-menu';
const STYLE_ID = 'plmm-style';
const STAT_FIELDS = [
['hp', 'HP'],
['atk', 'Attack'],
['def', 'Defense'],
['special', 'Special Attack'],
['spdef', 'Special Defense'],
['speed', 'Speed']
];
const RUN_SETTING_FIELDS = [
['usedBallCatch', 'Used Ball Catch'],
['usedPokecenter', 'Used Pokecenter'],
['usedTM', 'Used TM'],
['pickedUpItem', 'Picked Up Item'],
['anyFainted', 'Any Fainted']
];
let selectedUid = '';
let lastSyncSnapshot = '';
const lockedRunSettings = {};
function getState() {
try {
return Function('return typeof state !== "undefined" ? state : null;')();
} catch {
return null;
}
}
function getItemPool() {
try {
const pools = Function(`
const regularItems = typeof ITEM_POOL !== "undefined" && Array.isArray(ITEM_POOL) ? ITEM_POOL : [];
const usableItems = typeof USABLE_ITEM_POOL !== "undefined" && Array.isArray(USABLE_ITEM_POOL) ? USABLE_ITEM_POOL : [];
return [regularItems, usableItems];
`)();
const seen = new Set();
const merged = [];
for (const item of pools.flat()) {
const key = item?.id || item?.name || JSON.stringify(item);
if (seen.has(key)) continue;
seen.add(key);
merged.push(item);
}
return merged;
} catch {
return [];
}
}
function getPokedexEntries() {
try {
const pokedex = Function('return typeof __POKEDEX__ !== "undefined" ? __POKEDEX__ : null;')();
if (!pokedex || typeof pokedex !== 'object') return [];
return Object.entries(pokedex)
.map(([speciesId, info]) => ({
speciesId: String(speciesId),
name: info?.name || `Pokemon #${speciesId}`
}))
.sort((a, b) => Number(a.speciesId) - Number(b.speciesId));
} catch {
return [];
}
}
function getTeam() {
const s = getState();
return s && Array.isArray(s.team) ? s.team : [];
}
function getSelectedMon() {
const team = getTeam();
if (!team.length) return null;
const select = document.querySelector('#plmm-team-select');
const uid = select?.value ?? selectedUid;
if (!uid) return null;
return team.find(p => String(p._uid) === String(uid)) ?? null;
}
function clampNumber(value, fallback, min, max) {
const n = Number(value);
if (!Number.isFinite(n)) return fallback;
return Math.min(max, Math.max(min, n));
}
function displayName(mon) {
return mon.name || `Pokemon #${mon._uid ?? '?'}`;
}
function itemDisplayName(item) {
return item?.name || item?.id || 'Unknown Item';
}
function pokedexDisplayName(entry) {
return `${entry.name} (#${entry.speciesId})`;
}
function cloneItem(item) {
if (typeof structuredClone === 'function') return structuredClone(item);
return JSON.parse(JSON.stringify(item));
}
function canSelectedSpeciesEvolve(mon) {
if (!mon || mon.speciesId == null) return false;
try {
return !!Function('speciesId', 'return typeof canEvolve === "function" ? canEvolve(speciesId) : false;')(mon.speciesId);
} catch {
return false;
}
}
function applyEvolutionAtIndex(index) {
try {
return Function('n', `
if (typeof state === "undefined" || !state.team || !state.team[n]) return false;
if (typeof applyEvolution !== "function") return false;
applyEvolution(state.team[n]);
return true;
`)(index);
} catch {
return false;
}
}
function notify(message) {
const el = document.querySelector('#plmm-status');
if (el) el.textContent = message;
}
function triggerGameUpdate() {
window.dispatchEvent(new Event('focus'));
document.dispatchEvent(new Event('visibilitychange'));
window.dispatchEvent(new CustomEvent('pokelike-state-edited', {
detail: { time: Date.now() }
}));
}
function renderTeam() {
const s = getState();
if (!s || !Array.isArray(s.team)) return;
try {
Function('team', 'if (typeof renderTeamBar === "function") renderTeamBar(team);')(s.team);
} catch {
// The game may not expose renderTeamBar in every screen/state.
}
}
function renderItems() {
const s = getState();
if (!s || !Array.isArray(s.items)) return;
try {
Function('items', 'if (typeof renderItemBadges === "function") renderItemBadges(items);')(s.items);
} catch {
// The game may not expose renderItemBadges in every screen/state.
}
}
function injectStyle() {
if (document.getElementById(STYLE_ID)) return;
const style = document.createElement('style');
style.id = STYLE_ID;
style.textContent = `
#${MENU_ID} {
position: fixed;
bottom: 12px;
right: 12px;
width: 360px;
max-height: calc(100vh - 24px);
overflow-y: auto;
z-index: 2147483647;
background: rgba(18, 20, 28, 0.97);
color: #f5f5f5;
border: 1px solid rgba(255, 255, 255, 0.18);
border-radius: 14px;
box-shadow: 0 12px 30px rgba(0, 0, 0, 0.45);
font-family: Arial, sans-serif;
font-size: 13px;
}
#${MENU_ID} * {
box-sizing: border-box;
}
#${MENU_ID} .plmm-header,
#${MENU_ID} .plmm-section-header {
display: flex;
justify-content: space-between;
align-items: center;
gap: 8px;
}
#${MENU_ID} .plmm-header {
padding: 10px 12px;
background: rgba(255,255,255,0.08);
border-bottom: 1px solid rgba(255,255,255,0.12);
}
#${MENU_ID} .plmm-title,
#${MENU_ID} .plmm-card-title {
font-weight: 700;
}
#${MENU_ID} .plmm-title {
font-size: 14px;
}
#${MENU_ID} .plmm-body {
padding: 12px;
display: grid;
gap: 12px;
}
#${MENU_ID} .plmm-card {
border: 1px solid rgba(255,255,255,0.13);
border-radius: 10px;
padding: 10px;
background: rgba(255,255,255,0.055);
display: grid;
gap: 10px;
}
#${MENU_ID} .plmm-section-body {
display: grid;
gap: 10px;
}
#${MENU_ID} .plmm-card-title {
opacity: 0.95;
}
#${MENU_ID} .plmm-subsection-title {
margin-top: 2px;
padding-top: 10px;
border-top: 1px solid rgba(255,255,255,0.12);
font-size: 12px;
font-weight: 700;
opacity: 0.82;
}
#${MENU_ID} .plmm-section-body > .plmm-subsection-title:first-child {
margin-top: 0;
padding-top: 0;
border-top: 0;
}
#${MENU_ID} .plmm-selected-name {
font-size: 16px;
font-weight: 700;
}
#${MENU_ID} .plmm-subtext {
font-size: 12px;
opacity: 0.72;
}
#${MENU_ID} .plmm-grid-2 {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
}
#${MENU_ID} .plmm-grid-3 {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 10px;
}
#${MENU_ID} label {
display: grid;
gap: 5px;
font-size: 12px;
opacity: 0.95;
}
#${MENU_ID} input,
#${MENU_ID} select,
#${MENU_ID} button {
width: 100%;
border: 1px solid rgba(255,255,255,0.24);
border-radius: 8px;
padding: 7px 8px;
font: inherit;
}
#${MENU_ID} input,
#${MENU_ID} select {
background: rgba(255,255,255,0.10);
color: #fff;
}
#${MENU_ID} select option {
background: #ffffff;
color: #111111;
}
#${MENU_ID} input[type="checkbox"] {
width: auto;
transform: scale(1.1);
}
#${MENU_ID} button {
cursor: pointer;
background: rgba(255,255,255,0.14);
color: #fff;
}
#${MENU_ID} button:hover {
background: rgba(255,255,255,0.23);
}
#${MENU_ID} .plmm-primary {
background: rgba(80, 140, 255, 0.32);
}
#${MENU_ID} .plmm-mini {
width: auto;
min-width: 32px;
padding: 4px 8px;
}
#${MENU_ID}.plmm-collapsed .plmm-body,
#${MENU_ID} .plmm-card.plmm-collapsed .plmm-section-body,
#${MENU_ID} .plmm-hidden {
display: none;
}
#${MENU_ID}.plmm-collapsed {
width: auto;
min-width: 150px;
max-height: none;
overflow: hidden;
}
#${MENU_ID}.plmm-collapsed .plmm-header {
padding: 7px 8px;
border-bottom: 0;
}
#${MENU_ID}.plmm-collapsed .plmm-title {
font-size: 12px;
}
#${MENU_ID} .plmm-status {
font-size: 11px;
opacity: 0.75;
min-height: 16px;
}
#${MENU_ID} .plmm-check-row {
display: flex;
align-items: center;
gap: 10px;
min-height: 34px;
}
#${MENU_ID} .plmm-inline-label {
font-size: 12px;
font-weight: 700;
opacity: 0.82;
}
#${MENU_ID} .plmm-setting-row {
display: grid;
grid-template-columns: 1fr auto auto;
align-items: center;
gap: 10px;
min-height: 34px;
border-top: 1px solid rgba(255,255,255,0.08);
}
#${MENU_ID} .plmm-setting-row:first-child {
border-top: 0;
}
#${MENU_ID} .plmm-lock-label {
display: flex;
align-items: center;
gap: 6px;
white-space: nowrap;
}
#${MENU_ID} .plmm-action-with-icon {
display: grid;
grid-template-columns: 1fr auto;
gap: 10px;
}
#${MENU_ID} .plmm-button-row {
display: grid;
gap: 10px;
}
#${MENU_ID} .plmm-button-row:has(.plmm-hidden) {
display: none;
}
`;
document.documentElement.appendChild(style);
}
function buildMenu() {
if (document.getElementById(MENU_ID)) return;
injectStyle();
const menu = document.createElement('div');
menu.id = MENU_ID;
menu.innerHTML = `
<div class="plmm-header">
<div class="plmm-title">PokeLike Editor</div>
<div style="display:flex; gap:6px;">
<button id="plmm-collapse" class="plmm-mini" title="Collapse">-</button>
</div>
</div>
<div class="plmm-body">
<div id="plmm-team-actions" class="plmm-card plmm-collapsed">
<div class="plmm-section-header">
<div class="plmm-card-title">Team</div>
<button id="plmm-team-actions-toggle" class="plmm-mini" title="Expand team">+</button>
</div>
<div class="plmm-section-body">
<div class="plmm-subsection-title">Add Pokemon</div>
<label>
Pokemon
<input id="plmm-add-pokemon-search" list="plmm-pokedex-list" type="search" placeholder="Search Pokedex by name or species ID">
</label>
<datalist id="plmm-pokedex-list"></datalist>
<button id="plmm-catch-pokemon" class="plmm-primary">Catch</button>
<div class="plmm-subsection-title">Edit a Pokemon</div>
<label>
Pokemon
<select id="plmm-team-select"></select>
</label>
<div id="plmm-edit-fields" class="plmm-hidden">
<div class="plmm-grid-3">
<label>
Level
<input id="plmm-level" type="number" min="1" max="999">
</label>
<label>
Current HP
<input id="plmm-current-hp" type="number" min="0" max="99999">
</label>
<label>
Max HP
<input id="plmm-max-hp" type="number" min="1" max="99999">
</label>
</div>
<label>
Move Tier
<select id="plmm-move-tier">
<option value="0">Tier 1</option>
<option value="1">Tier 2</option>
<option value="2">Tier 3</option>
</select>
</label>
<div class="plmm-check-row">
<input id="plmm-shiny" type="checkbox">
<label for="plmm-shiny" style="display:block;">Shiny</label>
</div>
<div class="plmm-button-row">
<button id="plmm-evolve-selected" class="plmm-hidden">Evolve</button>
</div>
<div class="plmm-subsection-title">Base Stats</div>
<div class="plmm-grid-2">
${STAT_FIELDS.map(([key, label]) => `
<label>
${label}
<input id="plmm-stat-${key}" type="number" min="1" max="9999">
</label>
`).join('')}
</div>
<div class="plmm-action-with-icon">
<button id="plmm-apply-edits" class="plmm-primary">Apply Stat Changes</button>
<button id="plmm-reset-stats" class="plmm-mini" title="Reset base stats">↺</button>
</div>
<div class="plmm-button-row">
<button id="plmm-remove-selected">Remove From Team</button>
</div>
</div>
<div class="plmm-subsection-title">Team Actions</div>
<div class="plmm-grid-2">
<button id="plmm-heal-all">Heal All</button>
<button id="plmm-all-shiny">All Shiny</button>
</div>
<div class="plmm-inline-label">Increase level of all by:</div>
<div class="plmm-grid-3">
<button id="plmm-level-all-1">+1</button>
<button id="plmm-level-all-5">+5</button>
<button id="plmm-level-all-10">+10</button>
</div>
</div>
</div>
<div id="plmm-run-settings" class="plmm-card plmm-collapsed">
<div class="plmm-section-header">
<div>
<div class="plmm-card-title">Run Settings</div>
<div class="plmm-subtext">for various Achievements</div>
</div>
<button id="plmm-run-settings-toggle" class="plmm-mini" title="Expand run settings">+</button>
</div>
<div class="plmm-section-body">
<button id="plmm-always-roll-shiny">Always Roll Shiny</button>
<div id="plmm-always-roll-shiny-note" class="plmm-subtext plmm-hidden">Requires browser refresh to turn off</div>
${RUN_SETTING_FIELDS.map(([key, label]) => `
<div class="plmm-setting-row">
<span>${label}</span>
<input id="plmm-setting-${key}" data-setting-key="${key}" class="plmm-run-setting-toggle" type="checkbox">
<label class="plmm-lock-label">
<input id="plmm-lock-${key}" data-setting-key="${key}" class="plmm-run-setting-lock" type="checkbox">
Lock
</label>
</div>
`).join('')}
<label>
Max Team Size
<input id="plmm-max-team-size" type="number" min="1" max="999">
</label>
</div>
</div>
<div id="plmm-spawn-actions" class="plmm-card plmm-collapsed">
<div class="plmm-section-header">
<div class="plmm-card-title">Items</div>
<button id="plmm-spawn-toggle" class="plmm-mini" title="Expand items">+</button>
</div>
<div class="plmm-section-body">
<label>
Spawn Item
<input id="plmm-spawn-item-search" list="plmm-item-pool-list" type="search" placeholder="Search item pools by name or id">
</label>
<datalist id="plmm-item-pool-list"></datalist>
<button id="plmm-spawn-item" class="plmm-primary">Spawn Item</button>
<div class="plmm-subsection-title">Current Items</div>
<label>
Item
<select id="plmm-current-item-select"></select>
</label>
<button id="plmm-remove-item">Remove Selected Item</button>
</div>
</div>
<div id="plmm-status" class="plmm-status">Ready.</div>
</div>
`;
document.documentElement.appendChild(menu);
bindEvents();
refreshItemPoolList();
refreshPokedexList();
refreshCurrentItemSelect();
refreshTeamSelect();
refreshSelectedCard();
refreshRunSettings();
}
function bindEvents() {
document.querySelector('#plmm-collapse').addEventListener('click', () => {
const menu = document.getElementById(MENU_ID);
const btn = document.querySelector('#plmm-collapse');
menu.classList.toggle('plmm-collapsed');
btn.textContent = menu.classList.contains('plmm-collapsed') ? '+' : '-';
});
bindSectionToggle('#plmm-team-actions', '#plmm-team-actions-toggle');
bindSectionToggle('#plmm-run-settings', '#plmm-run-settings-toggle');
bindSectionToggle('#plmm-spawn-actions', '#plmm-spawn-toggle');
document.querySelector('#plmm-team-select').addEventListener('change', e => {
selectedUid = e.target.value;
refreshSelectedCard();
});
document.querySelector('#plmm-apply-edits').addEventListener('click', applySelectedEdits);
document.querySelector('#plmm-reset-stats').addEventListener('click', resetBaseStatInputs);
document.querySelector('#plmm-evolve-selected').addEventListener('click', evolveSelectedPokemon);
document.querySelector('#plmm-remove-selected').addEventListener('click', removeSelectedPokemon);
document.querySelector('#plmm-catch-pokemon').addEventListener('click', catchSelectedPokemon);
document.querySelector('#plmm-spawn-item').addEventListener('click', spawnSelectedItem);
document.querySelector('#plmm-remove-item').addEventListener('click', removeSelectedItem);
document.querySelector('#plmm-always-roll-shiny').addEventListener('click', enableAlwaysRollShiny);
document.querySelector('#plmm-max-team-size').addEventListener('change', updateMaxTeamSize);
for (const input of document.querySelectorAll('.plmm-run-setting-toggle')) {
input.addEventListener('change', updateRunSetting);
}
for (const input of document.querySelectorAll('.plmm-run-setting-lock')) {
input.addEventListener('change', updateRunSettingLock);
}
document.querySelector('#plmm-heal-all').addEventListener('click', () => {
const team = getTeam();
if (!team.length) return notify('No team found.');
for (const mon of team) {
mon.currentHp = Number(mon.maxHp) || 1;
}
renderTeam();
triggerGameUpdate();
refreshTeamSelect();
refreshSelectedCard();
markSynced();
notify('Healed all team Pokemon.');
});
document.querySelector('#plmm-all-shiny').addEventListener('click', () => {
const team = getTeam();
if (!team.length) return notify('No team found.');
for (const mon of team) {
mon.isShiny = true;
}
renderTeam();
triggerGameUpdate();
refreshTeamSelect();
refreshSelectedCard();
markSynced();
notify('Set all team Pokemon to shiny.');
});
document.querySelector('#plmm-level-all-1').addEventListener('click', () => increaseAllLevels(1));
document.querySelector('#plmm-level-all-5').addEventListener('click', () => increaseAllLevels(5));
document.querySelector('#plmm-level-all-10').addEventListener('click', () => increaseAllLevels(10));
}
function bindSectionToggle(cardSelector, buttonSelector) {
const card = document.querySelector(cardSelector);
const button = document.querySelector(buttonSelector);
if (!card || !button) return;
button.addEventListener('click', () => {
card.classList.toggle('plmm-collapsed');
button.textContent = card.classList.contains('plmm-collapsed') ? '+' : '-';
});
}
function enableAlwaysRollShiny() {
const button = document.querySelector('#plmm-always-roll-shiny');
const note = document.querySelector('#plmm-always-roll-shiny-note');
try {
Function('rollFn', 'rollShiny = rollFn;')(() => true);
} catch {
return notify('Could not replace browser function: rollShiny.');
}
if (button) {
button.disabled = true;
button.textContent = 'Always Roll Shiny: On';
}
note?.classList.remove('plmm-hidden');
notify('Always Roll Shiny enabled. Refresh the browser to turn it off.');
}
function updateRunSetting(event) {
const key = event.target.dataset.settingKey;
const s = getState();
if (!s || !key) return notify('Could not find global variable: state.');
s[key] = event.target.checked;
if (Object.prototype.hasOwnProperty.call(lockedRunSettings, key)) {
lockedRunSettings[key] = s[key];
}
triggerGameUpdate();
refreshRunSettings();
markSynced();
notify(`Set ${key} to ${s[key]}.`);
}
function updateRunSettingLock(event) {
const key = event.target.dataset.settingKey;
const s = getState();
if (!s || !key) return notify('Could not find global variable: state.');
if (event.target.checked) {
lockedRunSettings[key] = !!s[key];
notify(`Locked ${key} to ${lockedRunSettings[key]}.`);
} else {
delete lockedRunSettings[key];
notify(`Unlocked ${key}.`);
}
}
function updateMaxTeamSize(event) {
const s = getState();
if (!s) return notify('Could not find global variable: state.');
s.maxTeamSize = clampNumber(event.target.value, s.maxTeamSize || 1, 1, 999);
event.target.value = s.maxTeamSize;
triggerGameUpdate();
markSynced();
notify(`Set maxTeamSize to ${s.maxTeamSize}.`);
}
function applyLockedRunSettings() {
const s = getState();
if (!s) return false;
let changed = false;
for (const [key, lockedValue] of Object.entries(lockedRunSettings)) {
if (s[key] === lockedValue) continue;
s[key] = lockedValue;
changed = true;
}
if (changed) triggerGameUpdate();
return changed;
}
function refreshRunSettings() {
const s = getState();
if (!s) return;
for (const [key] of RUN_SETTING_FIELDS) {
const toggle = document.querySelector(`#plmm-setting-${key}`);
const lock = document.querySelector(`#plmm-lock-${key}`);
if (toggle) toggle.checked = !!s[key];
if (lock) lock.checked = Object.prototype.hasOwnProperty.call(lockedRunSettings, key);
}
const maxTeamSize = document.querySelector('#plmm-max-team-size');
if (maxTeamSize) maxTeamSize.value = s.maxTeamSize ?? '';
}
function refreshMenuFromState() {
refreshItemPoolList();
refreshPokedexList();
refreshCurrentItemSelect();
refreshTeamSelect();
refreshSelectedCard();
refreshRunSettings();
}
function getSyncSnapshot() {
const s = getState();
if (!s) return '';
try {
return JSON.stringify(s);
} catch {
return JSON.stringify({
team: s.team || [],
items: s.items || [],
runSettings: Object.fromEntries(RUN_SETTING_FIELDS.map(([key]) => [key, s[key]])),
maxTeamSize: s.maxTeamSize
});
}
}
function syncFromExternalState() {
const hadLockedChanges = applyLockedRunSettings();
const snapshot = getSyncSnapshot();
if (!snapshot) return;
if (hadLockedChanges || snapshot !== lastSyncSnapshot) {
lastSyncSnapshot = getSyncSnapshot();
refreshMenuFromState();
}
}
function markSynced() {
lastSyncSnapshot = getSyncSnapshot();
}
function refreshTeamSelect() {
const select = document.querySelector('#plmm-team-select');
if (!select) return;
const team = getTeam();
const previous = selectedUid ?? select.value;
select.innerHTML = '';
const noneOpt = document.createElement('option');
noneOpt.value = '';
noneOpt.textContent = 'None';
select.appendChild(noneOpt);
if (!team.length) {
notify('Could not find global variable: state.team');
selectedUid = '';
select.value = '';
return;
}
for (const mon of team) {
const opt = document.createElement('option');
opt.value = String(mon._uid);
opt.textContent = `${mon.isShiny ? '★ ' : ''}${displayName(mon)} | Lv ${mon.level} | HP ${mon.currentHp}/${mon.maxHp}`;
select.appendChild(opt);
}
if (previous && [...select.options].some(o => o.value === String(previous))) {
select.value = String(previous);
selectedUid = String(previous);
} else {
selectedUid = '';
select.value = '';
}
}
function refreshItemPoolList() {
const list = document.querySelector('#plmm-item-pool-list');
if (!list) return;
const pool = getItemPool();
list.innerHTML = '';
for (const item of pool) {
const opt = document.createElement('option');
opt.value = itemDisplayName(item);
list.appendChild(opt);
}
}
function refreshPokedexList() {
const list = document.querySelector('#plmm-pokedex-list');
if (!list) return;
const entries = getPokedexEntries();
list.innerHTML = '';
for (const entry of entries) {
const opt = document.createElement('option');
opt.value = pokedexDisplayName(entry);
list.appendChild(opt);
}
}
function getSelectedPokedexSpeciesId() {
const input = document.querySelector('#plmm-add-pokemon-search');
const value = input?.value.trim();
if (!value) return '';
const idMatch = value.match(/#(\d+)/);
if (idMatch) return idMatch[1];
const entries = getPokedexEntries();
const lowerValue = value.toLowerCase();
const match = entries.find(entry =>
entry.speciesId === value ||
entry.name.toLowerCase() === lowerValue ||
pokedexDisplayName(entry).toLowerCase() === lowerValue
);
return match?.speciesId || '';
}
function refreshCurrentItemSelect() {
const select = document.querySelector('#plmm-current-item-select');
if (!select) return;
const s = getState();
const items = s && Array.isArray(s.items) ? s.items : [];
const previous = select.value;
select.innerHTML = '';
const noneOpt = document.createElement('option');
noneOpt.value = '';
noneOpt.textContent = items.length ? 'Select item' : 'No items';
select.appendChild(noneOpt);
for (const [index, item] of items.entries()) {
const opt = document.createElement('option');
opt.value = String(index);
opt.textContent = itemDisplayName(item);
select.appendChild(opt);
}
if (previous && [...select.options].some(o => o.value === previous)) {
select.value = previous;
}
}
function refreshSelectedCard() {
const mon = getSelectedMon();
const editFields = document.querySelector('#plmm-edit-fields');
const evolveButton = document.querySelector('#plmm-evolve-selected');
if (!mon) {
editFields?.classList.add('plmm-hidden');
evolveButton?.classList.add('plmm-hidden');
clearInputs();
return;
}
editFields?.classList.remove('plmm-hidden');
evolveButton?.classList.toggle('plmm-hidden', !canSelectedSpeciesEvolve(mon));
document.querySelector('#plmm-level').value = mon.level ?? 1;
document.querySelector('#plmm-current-hp').value = mon.currentHp ?? 0;
document.querySelector('#plmm-max-hp').value = mon.maxHp ?? 1;
document.querySelector('#plmm-move-tier').value = mon.moveTier ?? 0;
document.querySelector('#plmm-shiny').checked = !!mon.isShiny;
const baseStats = mon.baseStats || {};
for (const [key] of STAT_FIELDS) {
document.querySelector(`#plmm-stat-${key}`).value = baseStats[key] ?? 1;
}
}
function clearInputs() {
for (const input of document.querySelectorAll('#plmm-edit-fields input')) {
if (input.type === 'checkbox') {
input.checked = false;
} else {
input.value = '';
}
}
const moveTier = document.querySelector('#plmm-move-tier');
if (moveTier) moveTier.value = '0';
}
function applySelectedEdits() {
const mon = getSelectedMon();
if (!mon) return notify('No selected Pokemon found.');
mon.level = clampNumber(document.querySelector('#plmm-level').value, mon.level || 1, 1, 999);
mon.maxHp = clampNumber(document.querySelector('#plmm-max-hp').value, mon.maxHp || 1, 1, 99999);
mon.currentHp = clampNumber(
document.querySelector('#plmm-current-hp').value,
mon.currentHp || mon.maxHp,
0,
99999
);
mon.moveTier = clampNumber(document.querySelector('#plmm-move-tier').value, mon.moveTier || 0, 0, 2);
mon.isShiny = document.querySelector('#plmm-shiny').checked;
const nextBaseStats = {};
for (const [key] of STAT_FIELDS) {
nextBaseStats[key] = clampNumber(
document.querySelector(`#plmm-stat-${key}`).value,
mon.baseStats?.[key] || 1,
1,
9999
);
}
mon.baseStats = nextBaseStats;
renderTeam();
triggerGameUpdate();
refreshTeamSelect();
refreshSelectedCard();
markSynced();
notify(`Applied edits to ${displayName(mon)}.`);
}
async function resetBaseStatInputs() {
const mon = getSelectedMon();
if (!mon || mon.speciesId == null) return notify('No selected Pokemon found.');
const button = document.querySelector('#plmm-reset-stats');
if (button) button.disabled = true;
try {
const basePokemon = await Function('speciesId', `
return (async () => {
if (typeof fetchPokemonById !== "function") throw new Error("fetchPokemonById is not available");
return fetchPokemonById(speciesId);
})();
`)(mon.speciesId);
const baseStats = basePokemon?.baseStats;
if (!baseStats) return notify(`No baseStats found for ${displayName(mon)}.`);
for (const [key] of STAT_FIELDS) {
document.querySelector(`#plmm-stat-${key}`).value = baseStats[key] ?? 1;
}
notify(`Reset stat inputs for ${displayName(mon)}. Click Apply Stat Changes to save.`);
} catch (error) {
notify(error?.message || 'Could not reset base stats.');
} finally {
if (button) button.disabled = false;
}
}
function evolveSelectedPokemon() {
const s = getState();
const mon = getSelectedMon();
if (!s || !Array.isArray(s.team) || !mon) return notify('No selected Pokemon found.');
if (!canSelectedSpeciesEvolve(mon)) return notify(`${displayName(mon)} cannot evolve.`);
const index = s.team.findIndex(candidate => String(candidate._uid) === String(mon._uid));
if (index < 0) return notify('Selected Pokemon is no longer in state.team.');
const oldName = displayName(mon);
const evolved = applyEvolutionAtIndex(index);
if (!evolved) return notify('applyEvolution(state.team[n]) was not available.');
renderTeam();
triggerGameUpdate();
refreshTeamSelect();
refreshSelectedCard();
markSynced();
notify(`Evolved ${oldName}.`);
}
function removeSelectedPokemon() {
const s = getState();
const mon = getSelectedMon();
if (!s || !Array.isArray(s.team) || !mon) return notify('No selected Pokemon found.');
const index = s.team.findIndex(candidate => String(candidate._uid) === String(mon._uid));
if (index < 0) return notify('Selected Pokemon is no longer in state.team.');
if (!window.confirm(`Remove ${displayName(mon)} from your team?`)) return;
const removed = s.team.splice(index, 1)[0];
selectedUid = '';
renderTeam();
triggerGameUpdate();
refreshTeamSelect();
refreshSelectedCard();
markSynced();
notify(`Removed ${displayName(removed)} from the team.`);
}
async function catchSelectedPokemon() {
const s = getState();
if (!s) return notify('Could not find global variable: state.');
const speciesId = getSelectedPokedexSpeciesId();
if (!speciesId) return notify('Select a Pokemon from the Pokedex list first.');
const node = s.map?.nodes?.n0_0;
if (!node) return notify('Could not find state.map.nodes.n0_0.');
const button = document.querySelector('#plmm-catch-pokemon');
const input = document.querySelector('#plmm-add-pokemon-search');
if (button) button.disabled = true;
try {
const caughtName = await Function('speciesId', 'node', `
return (async () => {
if (typeof fetchPokemonById !== "function") throw new Error("fetchPokemonById is not available");
if (typeof createInstance !== "function") throw new Error("createInstance is not available");
if (typeof catchPokemon !== "function") throw new Error("catchPokemon is not available");
if (typeof getLevelForNode !== "function") throw new Error("getLevelForNode is not available");
const pkm = await fetchPokemonById(speciesId);
const level = getLevelForNode(node);
const pkmInstance = createInstance(pkm, level, false, 0x0);
const previousCurrentNode = state?.map?.currentNode;
catchPokemon(pkmInstance, node);
if (state?.map) state.map.currentNode = previousCurrentNode;
return pkmInstance?.name || pkm?.name || "Pokemon";
})();
`)(speciesId, node);
if (input) input.value = '';
renderTeam();
triggerGameUpdate();
refreshTeamSelect();
refreshSelectedCard();
markSynced();
notify(`Caught ${caughtName}.`);
} catch (error) {
notify(error?.message || 'Could not catch selected Pokemon.');
} finally {
if (button) button.disabled = false;
}
}
function spawnSelectedItem() {
const s = getState();
if (!s) return notify('Could not find global variable: state.');
const input = document.querySelector('#plmm-spawn-item-search');
const value = input?.value.trim();
if (!value) return notify('Choose an item from ITEM_POOL or USABLE_ITEM_POOL first.');
const pool = getItemPool();
const item = pool.find(candidate =>
String(candidate?.name || '').toLowerCase() === value.toLowerCase() ||
String(candidate?.id || '').toLowerCase() === value.toLowerCase()
);
if (!item) return notify(`No item pool entry matched "${value}".`);
if (!Array.isArray(s.items)) s.items = [];
const spawnedItem = cloneItem(item);
s.items.push(spawnedItem);
input.value = '';
renderItems();
triggerGameUpdate();
refreshCurrentItemSelect();
markSynced();
notify(`Spawned item: ${itemDisplayName(spawnedItem)}.`);
}
function removeSelectedItem() {
const s = getState();
if (!s || !Array.isArray(s.items)) return notify('No state.items found.');
const select = document.querySelector('#plmm-current-item-select');
const index = Number(select?.value);
if (!Number.isInteger(index) || index < 0 || index >= s.items.length) {
return notify('Select a current item to remove.');
}
const removed = s.items.splice(index, 1)[0];
renderItems();
triggerGameUpdate();
refreshCurrentItemSelect();
markSynced();
notify(`Removed item: ${itemDisplayName(removed)}.`);
}
function increaseAllLevels(amount) {
const team = getTeam();
if (!team.length) return notify('No team found.');
for (const mon of team) {
mon.level = Math.min(999, Math.max(1, Number(mon.level || 1) + amount));
}
renderTeam();
triggerGameUpdate();
refreshTeamSelect();
refreshSelectedCard();
markSynced();
notify(`Increased all team levels by ${amount}.`);
}
function init() {
buildMenu();
markSynced();
setInterval(syncFromExternalState, 500);
setTimeout(() => {
refreshItemPoolList();
refreshPokedexList();
refreshCurrentItemSelect();
refreshTeamSelect();
refreshSelectedCard();
refreshRunSettings();
markSynced();
}, 1000);
setTimeout(() => {
refreshItemPoolList();
refreshPokedexList();
refreshCurrentItemSelect();
refreshTeamSelect();
refreshSelectedCard();
refreshRunSettings();
markSynced();
}, 3000);
}
init();
})();