您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Replace Flash box with background, clickable coconuts with animations, HUD, layout mode (D), persistent positions. Hides UI when the daily limit message appears.
// ==UserScript== // @name Neopets Coconut Shy — Custom Images + Layout Mode + Save Positions (Non-Flash Coconut Shy) // @namespace neopets // @version 1.4.3 // @description Replace Flash box with background, clickable coconuts with animations, HUD, layout mode (D), persistent positions. Hides UI when the daily limit message appears. // @match https://www.neopets.com/halloween/coconutshy.phtml* // @run-at document-end // @grant GM_addStyle // @grant GM_getValue // @grant GM_setValue // @license MIT // ==/UserScript== (function () { 'use strict'; if (!/\/halloween\/coconutshy\.phtml/i.test(location.pathname)) return; const CONFIG = { stage: { w: 500, h: 500, bg: 'https://i.imgur.com/fGgQAqS.png' }, coconuts: [ { id: 1, url: 'https://i.imgur.com/r139vGo.png', width: 72, height: 83, left: 24, top: 94 }, { id: 2, url: 'https://i.imgur.com/N3ofVPr.png', width: 72, height: 83, left: 121, top: 94 }, { id: 3, url: 'https://i.imgur.com/PLOQOpv.png', width: 57, height: 83, left: 220, top: 94 }, { id: 4, url: 'https://i.imgur.com/r139vGo.png', width: 72, height: 83, left: 300, top: 94 }, { id: 5, url: 'https://i.imgur.com/r139vGo.png', width: 72, height: 83, left: 387, top: 94 } ] }; const POS_KEY = 'coco_positions_v1'; const store = { get(k, d) { try { if (typeof GM_getValue === 'function') return GM_getValue(k, d); const raw = localStorage.getItem(k); return raw == null ? d : JSON.parse(raw); } catch (e) { return d; } }, set(k, v) { try { if (typeof GM_setValue === 'function') return GM_setValue(k, v); localStorage.setItem(k, JSON.stringify(v)); } catch (e) {} } }; function loadPositionsIntoConfig() { const saved = store.get(POS_KEY, {}); CONFIG.coconuts.forEach(c => { if (saved[c.id]) { c.left = saved[c.id].left; c.top = saved[c.id].top; } }); } function savePosition(id, left, top) { const saved = store.get(POS_KEY, {}); saved[id] = { left, top }; store.set(POS_KEY, saved); } function resetSavedPositions() { store.set(POS_KEY, {}); } function reachedLimit() { const txt = (document.body.innerText || '').toLowerCase(); return txt.includes("had your lot of throws today") || txt.includes("come back tomorrow"); } const FALLBACK_DATA_URI = (() => { const svg = encodeURIComponent(` <svg xmlns="http://www.w3.org/2000/svg" width="72" height="72" viewBox="0 0 72 72"> <defs><radialGradient id="g" cx="35%" cy="30%" r="70%"><stop offset="0%" stop-color="#8a5a2b"/><stop offset="60%" stop-color="#6d3f17"/><stop offset="100%" stop-color="#4e2c10"/></radialGradient></defs> <circle cx="36" cy="36" r="34" fill="url(#g)"/><circle cx="28" cy="26" r="3" fill="#2e1608"/><circle cx="37" cy="24" r="3" fill="#2e1608"/><circle cx="32" cy="31" r="3" fill="#2e1608"/><ellipse cx="50" cy="50" rx="14" ry="10" fill="rgba(255,255,255,0.08)"/> </svg> `.trim()); return `data:image/svg+xml;charset=utf-8,${svg}`; })(); const cursorDataURI = (() => { const svg = encodeURIComponent(` <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32"> <circle cx="16" cy="16" r="12" fill="none" stroke="#111" stroke-width="2"/> <circle cx="16" cy="16" r="3" fill="#111"/> <path d="M16 1v6M16 25v6M1 16h6M25 16h6" stroke="#111" stroke-width="2" stroke-linecap="round"/> </svg> `); return `data:image/svg+xml;charset=utf-8,${svg}`; })(); const css = ` .flashRIP__2020,.flashRIP-content__2020,.flashRIP-imgwrapper__2020,.flashRIP-img__2020{display:none!important} #coco-wrap{display:block;width:${CONFIG.stage.w}px;margin:0 auto;position:relative;clear:both} #coco-stage{position:relative;width:${CONFIG.stage.w}px;height:${CONFIG.stage.h}px;background-image:url("${CONFIG.stage.bg}");background-size:cover;background-position:center center;background-repeat:no-repeat;border-radius:8px;overflow:hidden;box-shadow:0 2px 12px rgba(0,0,0,0.25);cursor:url("${cursorDataURI}") 16 16,crosshair;user-select:none} #coco-hud{position:absolute;left:8px;top:8px;right:8px;padding:8px 10px;background:rgba(0,0,0,0.55);color:#fff;font:12px/1.3 system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif;border-radius:6px;white-space:pre-wrap} #coco-hud .title{font-weight:700;margin-right:6px} #coco-hud .msg{display:block;margin-top:4px} #coco-hud.flash{animation:cocoFlash 700ms ease} @keyframes cocoFlash{0%{box-shadow:0 0 0 rgba(255,255,255,0)}30%{box-shadow:0 0 0 6px rgba(255,255,255,0.25)}100%{box-shadow:0 0 0 rgba(255,255,255,0)}} .coconut{position:absolute;transform-origin:50% 50%;animation:idleBob 3.2s ease-in-out infinite;will-change:transform,left,top} .coconut img{display:block;width:100%;height:100%;pointer-events:none} .coconut:hover{animation:hoverWobble 500ms ease 1} .coconut:active{transform:scale(0.96)} .coconut[data-coconut="1"]{animation-delay:.0s} .coconut[data-coconut="2"]{animation-delay:.4s} .coconut[data-coconut="3"]{animation-delay:.8s} .coconut[data-coconut="4"]{animation-delay:1.2s} .coconut[data-coconut="5"]{animation-delay:1.6s} @keyframes idleBob{0%{transform:translateY(0) rotate(0)}50%{transform:translateY(-6px) rotate(-2deg)}100%{transform:translateY(0) rotate(0)}} @keyframes hoverWobble{0%{transform:rotate(0) scale(1)}25%{transform:rotate(-6deg) scale(1.02)}50%{transform:rotate(6deg) scale(1.02)}75%{transform:rotate(-3deg) scale(1.01)}100%{transform:rotate(0) scale(1)}} .coconut.throwing{animation:toss 600ms cubic-bezier(.2,.6,.2,1) 1} @keyframes toss{0%{transform:translate(0,0) scale(1) rotate(0)}20%{transform:translate(0,-10px) scale(1.03) rotate(-6deg)}50%{transform:translate(0,-42px) scale(1.07) rotate(-12deg)}80%{transform:translate(0,-8px) scale(1.02) rotate(0deg)}100%{transform:translate(0,0) scale(1) rotate(0)}} .coconut.success{animation:successPop 520ms ease-out 1} @keyframes successPop{0%{transform:scale(1);filter:drop-shadow(0 0 0 rgba(255,255,0,0))}30%{transform:scale(1.15);filter:drop-shadow(0 0 10px rgba(255,255,0,0.6))}100%{transform:scale(1);filter:drop-shadow(0 0 0 rgba(255,255,0,0))}} .coconut.fail{animation:failShake 450ms cubic-bezier(.36,.07,.19,.97) 1} @keyframes failShake{0%,100%{transform:translateX(0) rotate(0)}15%{transform:translateX(-6px) rotate(-4deg)}30%{transform:translateX(6px) rotate(4deg)}45%{transform:translateX(-4px) rotate(-3deg)}60%{transform:translateX(4px) rotate(3deg)}75%{transform:translateX(-2px) rotate(-2deg)}90%{transform:translateX(2px) rotate(2deg)}} #coco-stage.layout-mode,#coco-stage.layout-mode .coconut{cursor:move!important} #coco-stage.layout-mode{outline:3px dashed rgba(255,255,0,0.7)} .coconut.layout-handle{outline:2px solid rgba(0,200,255,0.8);border-radius:8px} `; try { GM_addStyle(css); } catch (e) { const s = document.createElement('style'); s.textContent = css; document.documentElement.appendChild(s); } function makeStage() { const wrap = document.createElement('div'); wrap.id = 'coco-wrap'; const stage = document.createElement('div'); stage.id = 'coco-stage'; const hud = document.createElement('div'); hud.id = 'coco-hud'; hud.innerHTML = `<span class="title">Coconut Shy</span> Click a coconut to throw.<span class="msg">Results will appear here.</span>`; stage.appendChild(hud); CONFIG.coconuts.forEach(cfg => { const a = document.createElement('a'); a.href = `https://www.neopets.com/halloween/process_cocoshy.phtml?coconut=${cfg.id}`; a.className = 'coconut'; a.dataset.coconut = String(cfg.id); a.style.left = `${clamp(cfg.left, 0, CONFIG.stage.w - cfg.width)}px`; a.style.top = `${clamp(cfg.top, 0, CONFIG.stage.h - cfg.height)}px`; a.style.width = `${cfg.width}px`; a.style.height = `${cfg.height}px`; const img = document.createElement('img'); img.alt = `Coconut ${cfg.id}`; img.src = cfg.url && cfg.url.startsWith('http') ? cfg.url : FALLBACK_DATA_URI; img.width = cfg.width; img.height = cfg.height; a.appendChild(img); a.addEventListener('click', onCoconutClick, false); a.addEventListener('animationend', onCoconutAnimEnd, false); stage.appendChild(a); }); wrap.appendChild(stage); return wrap; } function replaceFlashBoxWith(stageWrap) { const ripLink = document.querySelector('link[href*="flash_rip.css"]'); let blackHost = ripLink ? ripLink.closest('div[style]') : null; if (blackHost) { const st = (blackHost.getAttribute('style') || '').toLowerCase(); if (!(st.includes('background-color:black') && st.includes('width:500px'))) blackHost = null; } if (!blackHost) { blackHost = Array.from(document.querySelectorAll('div[style]')).find(el => { const s = (el.getAttribute('style') || '').replace(/\s+/g, '').toLowerCase(); return s.includes('background-color:black') && s.includes('width:500px'); }) || null; } if (blackHost && blackHost.parentElement) { blackHost.replaceWith(stageWrap); return true; } const content = document.querySelector('#content') || document.body; const center = document.createElement('div'); center.style.textAlign = 'center'; center.appendChild(stageWrap); content.prepend(center); return false; } function ensureStage() { if (reachedLimit()) return; if (document.getElementById('coco-stage')) return; const wrap = makeStage(); replaceFlashBoxWith(wrap); } async function onCoconutClick(e) { const a = e.currentTarget; if (state.layoutMode) { e.preventDefault(); e.stopPropagation(); return; } if (a.__dragConsumedClick) { a.__dragConsumedClick = false; e.preventDefault(); e.stopPropagation(); return; } e.preventDefault(); e.stopPropagation(); const n = a.dataset.coconut; addAnimClass(a, 'throwing'); const hud = document.getElementById('coco-hud'); setHUD(hud, `Throwing at coconut ${n}…`); try { const res = await fetch(a.href, { credentials: 'include' }); const text = await res.text(); let kvLine = text.trim(); const m = kvLine.match(/(^|\b|\s)points=\d+[^<\n\r]*/i); if (m) kvLine = m[0].trim(); const params = new URLSearchParams(kvLine.replace(/^[^p]*points=/i, 'points=')); const award = toInt(params.get('points')); const totalNP = toInt(params.get('totalnp')); const success = params.get('success'); const msg = params.get('error') || ''; const awardTxt = Number.isFinite(award) ? formatNP(award) : '0'; const totalTxt = Number.isFinite(totalNP) ? formatNP(totalNP) : '—'; setHUD(hud, `Award: ${awardTxt} NP • Total NP: ${totalTxt}`, msg || 'No message.'); if (success) console.log('Coconut success code:', success); if (Number.isFinite(totalNP)) updateNpHeader(totalTxt); if (Number.isFinite(award) && award > 0) addAnimClass(a, 'success'); else addAnimClass(a, 'fail'); hud.classList.remove('flash'); void hud.offsetWidth; hud.classList.add('flash'); } catch (err) { setHUD(hud, 'Error fetching result.', String(err)); addAnimClass(a, 'fail'); } } const state = { layoutMode: false }; document.addEventListener('click', ev => { if (!state.layoutMode) return; if (ev.target.closest('.coconut')) { ev.preventDefault(); ev.stopPropagation(); } }, true); function updateNpHeader(totalTxt) { try { if (window.Neo && typeof window.Neo.setNp === 'function') { window.Neo.setNp(totalTxt); } else { const np = document.getElementById('npanchor'); if (np) np.textContent = totalTxt; } } catch {} } function addAnimClass(el, cls) { el.classList.remove(cls); void el.offsetWidth; el.classList.add(cls); } function onCoconutAnimEnd(e) { if (e.animationName === 'toss') e.currentTarget.classList.remove('throwing'); if (e.animationName === 'successPop') e.currentTarget.classList.remove('success'); if (e.animationName === 'failShake') e.currentTarget.classList.remove('fail'); } function setHUD(hudEl, line1, line2) { if (!hudEl) return; hudEl.innerHTML = `<span class="title">Coconut Shy</span> ${escapeHTML(line1 || '')}` + (line2 ? `<span class="msg">${escapeHTML(line2)}</span>` : ''); } document.addEventListener('keydown', ev => { const k = ev.key.toLowerCase(); if (k === 'd') { state.layoutMode = !state.layoutMode; const stage = document.getElementById('coco-stage'); if (stage) stage.classList.toggle('layout-mode', state.layoutMode); document.querySelectorAll('.coconut').forEach(el => el.classList.toggle('layout-handle', state.layoutMode)); const hud = document.getElementById('coco-hud'); if (hud) { setHUD(hud, state.layoutMode ? 'Layout Mode ON — drag coconuts; positions saved. Press D to exit. Press R to clear saved positions.' : 'Layout Mode OFF — click a coconut to throw.'); hud.classList.remove('flash'); void hud.offsetWidth; hud.classList.add('flash'); } } if (state.layoutMode && k === 'r') { resetSavedPositions(); const hud = document.getElementById('coco-hud'); if (hud) { setHUD(hud, 'Saved positions cleared. Reload the page to revert to defaults.'); hud.classList.remove('flash'); void hud.offsetWidth; hud.classList.add('flash'); } } }); document.addEventListener('mousedown', startDrag, true); function startDrag(ev) { if (!state.layoutMode) return; const target = ev.target.closest('.coconut'); if (!target) return; ev.preventDefault(); ev.stopPropagation(); const startX = ev.clientX; const startY = ev.clientY; const origL = parseFloat(target.style.left) || 0; const origT = parseFloat(target.style.top) || 0; const w = parseFloat(target.style.width) || 72; const h = parseFloat(target.style.height) || 72; let moved = false; function onMove(e2) { const dx = e2.clientX - startX; const dy = e2.clientY - startY; if (Math.abs(dx) + Math.abs(dy) > 2) moved = true; const nx = clamp(origL + dx, 0, CONFIG.stage.w - w); const ny = clamp(origT + dy, 0, CONFIG.stage.h - h); target.style.left = `${nx}px`; target.style.top = `${ny}px`; } function onUp() { document.removeEventListener('mousemove', onMove, true); document.removeEventListener('mouseup', onUp, true); if (moved) target.__dragConsumedClick = true; const id = Number(target.dataset.coconut); const left = parseInt(target.style.left, 10); const top = parseInt(target.style.top, 10); savePosition(id, left, top); const hud = document.getElementById('coco-hud'); if (hud) { setHUD(hud, `Saved Coconut ${id} → left: ${left}, top: ${top}`, 'Positions persist across reloads.'); hud.classList.remove('flash'); void hud.offsetWidth; hud.classList.add('flash'); } console.log(`{ id: ${id}, left: ${left}, top: ${top} },`); } document.addEventListener('mousemove', onMove, true); document.addEventListener('mouseup', onUp, true); } function toInt(v) { const n = parseInt(String(v || '').replace(/[^\d-]/g, ''), 10); return Number.isFinite(n) ? n : NaN; } function formatNP(n) { return Number(n).toLocaleString('en-US'); } function clamp(v, min, max) { return Math.max(min, Math.min(max, v)); } function escapeHTML(s) { return String(s).replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>').replaceAll('"', '"').replaceAll("'", '''); } loadPositionsIntoConfig(); ensureStage(); const mo = new MutationObserver(() => { if (reachedLimit()) { const wrap = document.getElementById('coco-wrap') || document.getElementById('coco-stage'); if (wrap) wrap.remove(); return; } if (!document.getElementById('coco-stage')) ensureStage(); }); mo.observe(document.documentElement, { childList: true, subtree: true }); if (reachedLimit()) { const wrap = document.getElementById('coco-wrap') || document.getElementById('coco-stage'); if (wrap) wrap.remove(); } })();