// ==UserScript== // @name 落雷攻击特效 // @namespace http://tampermonkey.net/ // @version 1.4 // @description 温馨提示使用前请带好墨镜 可在代码增加或减少闪电 // @author // @match https://www.milkywayidle.com/* // @match https://test.milkywayidle.com/* // @icon https://www.milkywayidle.com/favicon.svg // @grant none // @downloadURL none // ==/UserScript== (function() { 'use strict'; // 创建全屏 Canvas const canvas = document.createElement('canvas'); canvas.id = 'eggTrackerCanvas'; Object.assign(canvas.style, { position: 'fixed', top: 0, left: 0, width: '100%', height: '100%', pointerEvents: 'none', zIndex: 999 }); document.body.appendChild(canvas); const ctx = canvas.getContext('2d'); function resize() { canvas.width = window.innerWidth; canvas.height = window.innerHeight; } resize(); window.addEventListener('resize', resize); // ===== 复合特效类 ===== class CombinedThunderEffect { constructor(canvas) { this.canvas = canvas; this.ctx = canvas.getContext('2d'); this.thunders = []; this.particles = []; this.explosions = []; this.flashes = []; // 融合颜色配置 this.lightningColors = [ '#3B82F6', // 主蓝(千鸟) '#87CEFA', // 天蓝 '#FFFFFF', // 白色 '#DBEAFE', // 淡蓝 '#1E40AF', // 深蓝 ]; // 爆炸颜色配置 this.explosionColors = [ '#3B82F6', // 蓝色 '#2563EB', // 中蓝 '#FFFFFF', // 白色 '#93C5FD', // 浅蓝 ]; // 背景闪光配置 this.flashColors = [ 'rgba(59, 130, 246, 0.3)', // 蓝色闪光 'rgba(219, 234, 254, 0.2)', // 淡蓝闪光 ]; // 绑定事件处理函数 this.animate = this.animate.bind(this); this.resize = this.resize.bind(this); // 监听窗口大小变化 window.addEventListener('resize', this.resize); // 开始动画循环 requestAnimationFrame(this.animate); } // 调整Canvas大小 resize() { this.canvas.width = window.innerWidth; this.canvas.height = window.innerHeight; } // 创建复合特效 createCombinedEffect(targetX, targetY, radius, intensity = 1, damage = 100) { // 创建背景闪光 this.createFlash(); // 计算攻击范围 const minX = Math.max(0, targetX - radius); const maxX = Math.min(this.canvas.width, targetX + radius); const minY = 0; // 从屏幕顶部开始 const maxY = targetY; // 计算闪电数量 const count = 10 + Math.floor(intensity * 8); // 生成闪电的时间间隔 const interval = 0.3 / count; // 创建多道闪电 for (let i = 0; i < count; i++) { setTimeout(() => { // 随机闪电起点和终点 const startX = minX + Math.random() * (maxX - minX); const startY = minY + Math.random() * (maxY * 0.3); // 从屏幕上方开始 const endX = targetX - radius*0.7 + Math.random() * (radius*1.4); const endY = targetY - radius*0.5 + Math.random() * (radius); // 创建融合闪电 const thunder = this.createCombinedLightning(startX, startY, endX, endY, damage); // 调整闪电参数 thunder.alpha = 1; thunder.fadeSpeed = 0.05 + Math.random() * 0.03; }, i * interval); } // 创建中心主闪电 setTimeout(() => { const thunder = this.createCombinedLightning( targetX, -50, // 从屏幕外开始 targetX, targetY, 25, // 增加主闪电分段数 0.4, // 增加主闪电分支概率 3, // 增加主闪电分支层级 damage ); // 调整主闪电参数 thunder.alpha = 1; thunder.fadeSpeed = 0.06; // 创建中心爆炸效果(融合版) const explosion = this.createCombinedExplosion(targetX, targetY, 40 + Math.min(60, 15 * intensity), damage); // 再创建一次背景闪光 this.createFlash(); // 创建千鸟风格粒子 this.createChidoriParticles(targetX, targetY, damage); }, 0.15); } // 创建融合闪电 createCombinedLightning(startX, startY, endX, endY, segments = 15, branchChance = 0.2, branchLevel = 2, damage = 100) { const thunder = { startX, startY, endX, endY, segments, branchChance, branchLevel, alpha: 1, fadeSpeed: 0.05, damage, color: this.lightningColors[Math.floor(Math.random() * this.lightningColors.length)], branches: [], isMain: Math.random() < 0.2, // 20%概率是主闪电 }; // 生成主闪电路径 thunder.points = this.generateCombinedLightningPath(startX, startY, endX, endY, segments); // 生成分支闪电 if (branchLevel > 0) { this.generateCombinedBranches(thunder, branchLevel); } this.thunders.push(thunder); return thunder; } // 创建背景闪光 createFlash() { const flash = { alpha: 1, fadeSpeed: 0.15 + Math.random() * 0.05, color: this.flashColors[Math.floor(Math.random() * this.flashColors.length)] }; this.flashes.push(flash); } // 生成融合闪电路径 generateCombinedLightningPath(startX, startY, endX, endY, segments) { const points = [{ x: startX, y: startY }]; const dx = endX - startX; const dy = endY - startY; const totalLength = Math.sqrt(dx * dx + dy * dy); const segmentLength = totalLength / segments; const angle = Math.atan2(dy, dx); // 生成中间点 for (let i = 1; i < segments; i++) { const posOnLine = i / segments; const baseX = startX + dx * posOnLine; const baseY = startY + dy * posOnLine; // 添加随机偏移(结合雷霆和千鸟的特点) const offset = (Math.random() - 0.5) * totalLength * 0.1 * (1 - posOnLine); const perpAngle = angle + Math.PI / 2; // 千鸟风格的不规则偏移 const zigzagOffset = (Math.random() - 0.5) * totalLength * 0.08 * (1 - posOnLine); const x = baseX + Math.cos(perpAngle) * offset + Math.sin(angle) * zigzagOffset; const y = baseY + Math.sin(perpAngle) * offset + Math.cos(angle) * zigzagOffset; points.push({ x, y }); } points.push({ x: endX, y: endY }); return points; } // 生成分支闪电 generateCombinedBranches(thunder, branchLevel) { const points = thunder.points; const branchLengthFactor = 0.5; // 从主路径上选择一些点作为分支起点 const branchPoints = []; for (let i = 1; i < points.length - 2; i += 2) { if (Math.random() < thunder.branchChance) { branchPoints.push(i); } } // 为每个分支点创建分支 for (const idx of branchPoints) { const startPoint = points[idx]; const endPoint = points[idx + 1]; // 计算分支方向 const angle = Math.atan2(endPoint.y - startPoint.y, endPoint.x - startPoint.x); const branchAngle = angle + (Math.random() - 0.5) * Math.PI / 2; const branchLength = Math.sqrt( Math.pow(endPoint.x - startPoint.x, 2) + Math.pow(endPoint.y - startPoint.y, 2) ) * branchLengthFactor; const branchEndX = startPoint.x + Math.cos(branchAngle) * branchLength; const branchEndY = startPoint.y + Math.sin(branchAngle) * branchLength; // 创建分支 const branch = { startX: startPoint.x, startY: startPoint.y, endX: branchEndX, endY: branchEndY, segments: Math.max(3, Math.floor(thunder.segments * 0.5)), alpha: 0.8, color: thunder.color, points: this.generateCombinedLightningPath(startPoint.x, startPoint.y, branchEndX, branchEndY, Math.max(3, Math.floor(thunder.segments * 0.5))), branches: [] }; // 递归生成子分支 if (branchLevel > 1) { this.generateCombinedBranches(branch, branchLevel - 1); } thunder.branches.push(branch); } } // 创建融合爆炸效果 createCombinedExplosion(x, y, size, damage) { const explosion = { x, y, size, maxSize: size * 1.5, alpha: 1, fadeSpeed: 0.08 + Math.random() * 0.02, color: this.explosionColors[Math.floor(Math.random() * this.explosionColors.length)], damage }; this.explosions.push(explosion); // 创建爆炸粒子 this.createCombinedExplosionParticles(x, y, size, damage); return explosion; } // 创建融合爆炸粒子 createCombinedExplosionParticles(x, y, size, damage) { // 雷霆风格粒子 const thunderParticleCount = 20 + Math.floor(Math.random() * 10); for (let i = 0; i < thunderParticleCount; i++) { const angle = Math.random() * Math.PI * 2; const speed = 1 + Math.random() * 3; const particle = { x, y, vx: Math.cos(angle) * speed, vy: Math.sin(angle) * speed, size: 1.5 + Math.random() * 2, alpha: 1, fadeSpeed: 0.05 + Math.random() * 0.05, color: this.explosionColors[Math.floor(Math.random() * this.explosionColors.length)], type: 'thunder' }; this.particles.push(particle); } // 千鸟风格粒子 const chidoriParticleCount = Math.min(100, damage / 2); for (let i = 0; i < chidoriParticleCount; i++) { const angle = Math.random() * Math.PI * 2; const speed = 0.5 + Math.random() * 2; const particle = { x, y, vx: Math.cos(angle) * speed, vy: Math.sin(angle) * speed, size: 0.8 + Math.random(), alpha: 1, fadeSpeed: 0.03 + Math.random() * 0.02, color: this.lightningColors[Math.floor(Math.random() * this.lightningColors.length)], type: 'chidori' }; this.particles.push(particle); } } // 创建千鸟风格粒子 createChidoriParticles(x, y, damage) { const particleCount = Math.min(150, damage); for (let i = 0; i < particleCount; i++) { const angle = Math.random() * Math.PI * 2; const speed = 1 + Math.random() * 2; const size = 0.8 + Math.random(); this.particles.push({ x, y, vx: Math.cos(angle) * speed, vy: Math.sin(angle) * speed, size, alpha: 1, fadeSpeed: 0.03 + Math.random() * 0.02, color: this.lightningColors[Math.floor(Math.random() * this.lightningColors.length)], type: 'chidori' }); } } // 绘制背景闪光 drawFlash(flash) { this.ctx.fillStyle = flash.color.replace(/,\s*\d+\)/, `,${flash.alpha})`); this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); } // 绘制融合闪电 drawLightning(lightning) { if (lightning.alpha <= 0) return; const points = lightning.points; // 绘制主路径 this.ctx.beginPath(); this.ctx.moveTo(points[0].x, points[0].y); for (let i = 1; i < points.length; i++) { this.ctx.lineTo(points[i].x, points[i].y); } // 根据闪电强度调整线条粗细 const baseWidth = lightning.isMain ? 2.5 + Math.min(3, lightning.damage / 150) : 1.5 + Math.min(2, lightning.damage / 300); // 主闪电使用多层渲染增强视觉效果 if (lightning.isMain) { // 外层光晕 this.ctx.strokeStyle = this.getAlphaColor('#DBEAFE', lightning.alpha * 0.3); this.ctx.lineWidth = baseWidth * 2.5; this.ctx.stroke(); // 中层 this.ctx.strokeStyle = this.getAlphaColor('#93C5FD', lightning.alpha * 0.6); this.ctx.lineWidth = baseWidth * 1.5; this.ctx.stroke(); } // 内层核心 this.ctx.strokeStyle = this.getAlphaColor(lightning.color, lightning.alpha); this.ctx.lineWidth = baseWidth; this.ctx.stroke(); // 白色高光 this.ctx.strokeStyle = this.getAlphaColor('#FFFFFF', lightning.alpha * 0.8); this.ctx.lineWidth = baseWidth * 0.5; this.ctx.stroke(); // 绘制分支 for (const branch of lightning.branches) { this.drawLightning(branch); } } // 绘制融合爆炸 drawExplosion(explosion) { if (explosion.alpha <= 0) return; // 爆炸扩展然后收缩 if (explosion.size < explosion.maxSize) { explosion.size += explosion.maxSize * 0.05; } // 绘制爆炸光圈(融合蓝色调) this.ctx.beginPath(); this.ctx.arc(explosion.x, explosion.y, explosion.size, 0, Math.PI * 2); const gradient = this.ctx.createRadialGradient( explosion.x, explosion.y, 0, explosion.x, explosion.y, explosion.size ); // 爆炸核心使用更亮的颜色 const coreColor = this.getAlphaColor( explosion.color === '#FFFFFF' ? '#DBEAFE' : explosion.color, explosion.alpha * 0.9 ); gradient.addColorStop(0, coreColor); gradient.addColorStop(1, this.getAlphaColor(explosion.color, 0)); this.ctx.fillStyle = gradient; this.ctx.fill(); // 绘制冲击波 this.ctx.beginPath(); this.ctx.arc(explosion.x, explosion.y, explosion.size * 1.2, 0, Math.PI * 2); const shockGradient = this.ctx.createRadialGradient( explosion.x, explosion.y, explosion.size * 1.1, explosion.x, explosion.y, explosion.size * 1.2 ); shockGradient.addColorStop(0, this.getAlphaColor('#DBEAFE', explosion.alpha * 0.3)); shockGradient.addColorStop(1, this.getAlphaColor('#DBEAFE', 0)); this.ctx.strokeStyle = shockGradient; this.ctx.lineWidth = 2; this.ctx.stroke(); } // 绘制粒子 drawParticle(particle) { if (particle.alpha <= 0) return; this.ctx.beginPath(); this.ctx.arc(particle.x, particle.y, particle.size, 0, Math.PI * 2); // 不同类型的粒子使用不同的绘制方式 if (particle.type === 'chidori') { // 千鸟粒子添加辉光效果 const gradient = this.ctx.createRadialGradient( particle.x, particle.y, 0, particle.x, particle.y, particle.size * 2 ); gradient.addColorStop(0, this.getAlphaColor(particle.color, particle.alpha)); gradient.addColorStop(1, this.getAlphaColor(particle.color, 0)); this.ctx.fillStyle = gradient; } else { this.ctx.fillStyle = this.getAlphaColor(particle.color, particle.alpha); } this.ctx.fill(); } // 更新闪电 updateLightning(lightning) { lightning.alpha -= lightning.fadeSpeed; // 更新分支 for (let i = lightning.branches.length - 1; i >= 0; i--) { const branch = lightning.branches[i]; this.updateLightning(branch); if (branch.alpha <= 0) { lightning.branches.splice(i, 1); } } } // 更新爆炸 updateExplosion(explosion) { explosion.alpha -= explosion.fadeSpeed; } // 更新粒子 updateParticle(particle) { particle.x += particle.vx; particle.y += particle.vy; particle.alpha -= particle.fadeSpeed; } // 更新闪光 updateFlash(flash) { flash.alpha -= flash.fadeSpeed; } // 颜色工具函数 - 添加透明度 getAlphaColor(color, alpha) { // 处理rgb和rgba格式 if (color.startsWith('rgba')) { return color.replace(/rgba\(([^,]+,[^,]+,[^,]+),[^)]+\)/, `rgba($1,${alpha})`); } else if (color.startsWith('rgb')) { return color.replace(/rgb\(([^)]+)\)/, `rgba($1,${alpha})`); } else if (color.startsWith('#')) { // 将#RGB或#RGBA转换为rgba let r, g, b; if (color.length === 4) { r = parseInt(color.charAt(1) + color.charAt(1), 16); g = parseInt(color.charAt(2) + color.charAt(2), 16); b = parseInt(color.charAt(3) + color.charAt(3), 16); } else if (color.length === 7) { r = parseInt(color.substring(1, 3), 16); g = parseInt(color.substring(3, 5), 16); b = parseInt(color.substring(5, 7), 16); } return `rgba(${r},${g},${b},${alpha})`; } return color; } // 动画循环 animate(timestamp) { // 计算时间增量 deltaTime = (timestamp - lastTime) / 1000; lastTime = timestamp; // 清除画布 this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); // 更新和绘制背景闪光 for (let i = this.flashes.length - 1; i >= 0; i--) { const flash = this.flashes[i]; this.updateFlash(flash); this.drawFlash(flash); if (flash.alpha <= 0) { this.flashes.splice(i, 1); } } // 更新和绘制闪电 for (let i = this.thunders.length - 1; i >= 0; i--) { const thunder = this.thunders[i]; this.updateLightning(thunder); this.drawLightning(thunder); if (thunder.alpha <= 0 && thunder.branches.length === 0) { this.thunders.splice(i, 1); } } // 更新和绘制爆炸 for (let i = this.explosions.length - 1; i >= 0; i--) { const explosion = this.explosions[i]; this.updateExplosion(explosion); this.drawExplosion(explosion); if (explosion.alpha <= 0) { this.explosions.splice(i, 1); } } // 更新和绘制粒子 for (let i = this.particles.length - 1; i >= 0; i--) { const particle = this.particles[i]; this.updateParticle(particle); this.drawParticle(particle); if (particle.alpha <= 0) { this.particles.splice(i, 1); } } // 继续动画循环 requestAnimationFrame(this.animate); } } // 计算元素中心位置 function center(el) { const r = el.getBoundingClientRect(); return { x: r.left + r.width / 2, y: r.top + r.height / 2 }; } // 创建复合特效实例 const combinedEffect = new CombinedThunderEffect(canvas); // Websocket 劫持 (function() { const desc = Object.getOwnPropertyDescriptor(MessageEvent.prototype, 'data'); const orig = desc.get; desc.get = function() { const sock = this.currentTarget; const msg = orig.call(this); if (sock instanceof WebSocket && sock.url.includes('api.milkywayidle.com/ws')) { return handle(msg); } return msg; }; Object.defineProperty(MessageEvent.prototype, 'data', desc); })(); // 上一帧状态 const prev = { pMP: [], mHP: [] }; let autoIdx = 0; function handle(message) { let obj; try { obj = JSON.parse(message); } catch { return message; } if (obj.type === 'new_battle') { prev.pMP = obj.players.map(p => p.currentManapoints); prev.mHP = obj.monsters.map(m => m.currentHitpoints); autoIdx = 0; } else if (obj.type === 'battle_updated' && prev.mHP.length) { const pMap = obj.pMap, mMap = obj.mMap; // 检测施法者 let caster = -1; Object.keys(pMap).forEach(i => { if (pMap[i].cMP < prev.pMP[i]) caster = +i; prev.pMP[i] = pMap[i].cMP; }); const players = document.querySelectorAll('[class*="BattlePanel_playersArea"] [class*="CombatUnit_unit"]'); const monsters = document.querySelectorAll('[class*="BattlePanel_monstersArea"] [class*="CombatUnit_unit"]'); const playerCount = players.length; Object.keys(mMap).forEach(idx => { const i = +idx; const oldHP = prev.mHP[i], newHP = mMap[i].cHP; if (newHP < oldHP) { const dmg = oldHP - newHP; if (dmg > 0) { const src = caster >= 0 ? caster : autoIdx % playerCount; const fromEl = players[src], toEl = monsters[i]; if (fromEl && toEl) { const s = center(fromEl), t = center(toEl); // 根据伤害值计算攻击强度 const intensity = Math.min(5, dmg / 200); // 计算攻击范围(像素) const radius = 60 + Math.min(150, dmg / 5); // 创建融合特效 combinedEffect.createCombinedEffect(t.x, t.y, radius, intensity, dmg); } if (caster < 0) autoIdx++; } } prev.mHP[i] = newHP; }); } return message; } // 动画循环 let deltaTime = 0; let lastTime = 0; function animate(timestamp) { deltaTime = (timestamp - lastTime) / 1000; lastTime = timestamp; requestAnimationFrame(animate); } animate(0); })();