Greasy Fork

Greasy Fork is available in English.

落雷

雷霆万钧!

// ==UserScript==
// @name         落雷
// @namespace    http://tampermonkey.net/
// @version      1.8
// @description  雷霆万钧!
// @author       TaichiSlippers (Modified by whosyourdaddy)
// @match        https://www.milkywayidle.com/*
// @match        https://test.milkywayidle.com/*
// @icon         https://www.milkywayidle.com/favicon.svg
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // 页面可见性检测
    let isPageVisible = true;
    const visibilityChangeEvent = (() => {
        if (typeof document.hidden !== 'undefined') {
            return 'visibilitychange';
        } else if (typeof document.msHidden !== 'undefined') {
            return 'msvisibilitychange';
        } else if (typeof document.webkitHidden !== 'undefined') {
            return 'webkitvisibilitychange';
        }
        return null;
    })();

    const hiddenProp = (() => {
        if (typeof document.hidden !== 'undefined') {
            return 'hidden';
        } else if (typeof document.msHidden !== 'undefined') {
            return 'msHidden';
        } else if (typeof document.webkitHidden !== 'undefined') {
            return 'webkitHidden';
        }
        return null;
    })();

    if (visibilityChangeEvent && hiddenProp) {
        document.addEventListener(visibilityChangeEvent, () => {
            isPageVisible = !document[hiddenProp];
            console.log(`页面可见性变化: ${isPageVisible ? '可见' : '隐藏'}`);
        });
    }

    // 创建全屏 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.timeScale = 1.2; //按需求修改 ←←←←←← timeScale > 1 会减慢所有动画(包括消失速度) timeScale < 1 会加速所有动画

            // 融合颜色配置
            this.lightningColors = [
                '#3B82F6', // 主蓝(千鸟)
                '#87CEFA', // 天蓝
                '#FFFFFF', // 白色
                '#DBEAFE', // 淡蓝
                '#1E40AF', // 深蓝
            ];

            // 爆炸颜色配置
            this.explosionColors = [
                '#3B82F6', // 蓝色
                '#2563EB', // 中蓝
                '#FFFFFF', // 白色
                '#93C5FD', // 浅蓝
            ];

            // 绑定事件处理函数
            this.animate = this.animate.bind(this);
            this.resize = this.resize.bind(this);

            // 监听窗口大小变化
            window.addEventListener('resize', this.resize);

            // 开始动画循环
            this.isPaused = false;
            this.lastTimestamp = 0;
            requestAnimationFrame(this.animate);
        }

        // 暂停/恢复渲染
        setPaused(paused) {
            this.isPaused = paused;
            if (!paused) {
                // 恢复时重置时间戳,避免大的时间跳跃
                this.lastTimestamp = 0;
                requestAnimationFrame(this.animate);
            }
            console.log(`特效渲染 ${paused ? '已暂停' : '已恢复'}`);
        }

        // 调整Canvas大小
        resize() {
            this.canvas.width = window.innerWidth;
            this.canvas.height = window.innerHeight;
        }

        // 创建复合特效
        createCombinedEffect(targetX, targetY, radius, intensity = 1, damage = 100) {
            // 如果页面不可见或渲染已暂停,不创建新特效
            if (!isPageVisible || this.isPaused) return;

            // 计算攻击范围
            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);//按需求修改 ←←←←←← 10为闪电数量

            // 生成闪电的时间间隔
            const interval = 2 / count;//按需求修改 ←←←←←←

            // 创建多道闪电

           for (var i = 0; i < count; i++) {
           const currentIsPageVisible = isPageVisible;
           setTimeout(() => {
        // 如果页面不可见或渲染已暂停,不创建新闪电
           if (!currentIsPageVisible || this.isPaused) return;
        // 随机闪电起点和终点
           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;//1不透明 0透明
        thunder.fadeSpeed = 0.03 / this.timeScale;
    }, i * interval);
}

            // 创建中心主闪电
            setTimeout(() => {
                // 如果页面不可见或渲染已暂停,不创建新闪电
                if (!isPageVisible || this.isPaused) return;

                const thunder = this.createCombinedLightning(
                    targetX,
                    -50, // 从屏幕外开始
                    targetX,
                    targetY,
                    25, // 增加主闪电分段数    按需求修改 ←←←←←← 默认20分段
                    0.4, // 增加主闪电分支概率 按需求修改 ←←←←←← 默认40%概率分支
                    3, // 增加主闪电分支层级   按需求修改 ←←←←←← 默认层级3
                    damage
                );

                // 调整主闪电参数
                thunder.alpha = 1; //1不透明 0透明
                thunder.fadeSpeed = 0.04 / this.timeScale;

                // 创建中心爆炸效果
                const explosion = this.createCombinedExplosion(targetX, targetY, 40 + Math.min(60, 15 * intensity), damage);

                // 创建千鸟风格粒子
                this.createChidoriParticles(targetX, targetY, damage);
            }, 1); // 延迟时间
        }

        // 创建融合闪电
        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.4, // 40%概率出现主闪电 按需求修改 ←←←←←←
            };

            // 生成主闪电路径
            thunder.points = this.generateCombinedLightningPath(startX, startY, endX, endY, segments);

            // 生成分支闪电
            if (branchLevel > 0) {
                this.generateCombinedBranches(thunder, branchLevel);
            }

            this.thunders.push(thunder);
            return thunder;
        }

        // 生成融合闪电路径
        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 * 2,
                alpha: 1,
                fadeSpeed: 0.05 / this.timeScale,
                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) {
            // 如果页面不可见或渲染已暂停,不创建新粒子
            if (!isPageVisible || this.isPaused) return;

            // 雷霆风格粒子
            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.03 / this.timeScale,
                    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.02 / this.timeScale,
                    color: this.lightningColors[Math.floor(Math.random() * this.lightningColors.length)],
                    type: 'chidori'
                };
                this.particles.push(particle);
            }
        }

        // 创建千鸟风格粒子
        createChidoriParticles(x, y, damage) {
            // 如果页面不可见或渲染已暂停,不创建新粒子
            if (!isPageVisible || this.isPaused) return;

            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.02 / this.timeScale,
                    color: this.lightningColors[Math.floor(Math.random() * this.lightningColors.length)],
                    type: 'chidori'
                });
            }
        }

        // 绘制融合闪电
        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) ://按需求修改 ←←←←←← //主闪电的线条宽度计算:基础值2.5px + 最小化后的伤害比例(伤害/150,最高3px) 基础宽度越大视觉上更粗更明显
                1.5 + Math.min(2, lightning.damage / 300);//按需求修改 ←←←←←←// 普通闪电的线条宽度计算:基础值1.5px + 最小化后的伤害比例(伤害/300,最高2px)

            // 主闪电使用多层渲染增强视觉效果
            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.03 / this.timeScale;
            }

            // 绘制爆炸光圈
            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;
        }

        // 颜色工具函数 - 添加透明度
        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) {
            // 如果页面不可见或渲染已暂停,跳过渲染但保持动画循环
            if (!isPageVisible || this.isPaused) {
                this.lastTimestamp = 0;
                requestAnimationFrame(this.animate);
                return;
            }

            // 计算时间增量
            if (!this.lastTimestamp) {
                this.lastTimestamp = timestamp;
            }
            const deltaTime = (timestamp - this.lastTimestamp) / 1000;
            this.lastTimestamp = timestamp;

            // 清除画布
            this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);

            // 更新和绘制闪电
            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);

    // 监听页面可见性变化,控制特效渲染
    if (visibilityChangeEvent && hiddenProp) {
        document.addEventListener(visibilityChangeEvent, () => {
            if (document[hiddenProp]) {
                combinedEffect.setPaused(true);
            } else {
                combinedEffect.setPaused(false);
            }
        });
    }

    // 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);

                            // 根据伤害值计算攻击强度(控制闪电数量、粗细等)
                            // 公式:伤害 / 1000,强度上限为5
                            // 例如:伤害1000对应强度1,伤害5000对应强度5(超过5000伤害强度不再增加)
                            // 可按需更改 👇
                            const intensity = Math.min(5, dmg / 1000);//按需求修改 ←←←←←←


                            // 计算攻击范围(像素)
                            // 基础范围60px,加上最小化后的伤害比例(伤害 / 5),但最多叠加150px
                            // 可按需更改 👇 500伤害可以获得60+100=160的范围像素,伤害超过750到达150px上限
                            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);
})();