MWI-Hit-Tracker-More-Animation

战斗过程中实时显示攻击命中目标,增加了更多的特效

当前为 2025-05-11 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         MWI-Hit-Tracker-More-Animation
// @namespace    http://tampermonkey.net/
// @version      1.9.1
// @description  战斗过程中实时显示攻击命中目标,增加了更多的特效
// @author       Artintel (Artintel), Yuk111
// @license MIT
// @match        https://www.milkywayidle.com/*
// @match        https://test.milkywayidle.com/*
// @icon         https://www.milkywayidle.com/favicon.svg
// @grant        none
// ==/UserScript==

(function () {
    'use strict';

    // 状态变量,存储战斗相关信息
    const battleState = {
        monstersHP: [],
        monstersMP: [],
        playersHP: [],
        playersMP: []
    };

    // 存储是否已添加窗口大小改变监听器
    let isResizeListenerAdded = false;

    // 标记脚本是否暂停
    let isPaused = false;

    // 粒子对象池
    const particlePool = [];

    // 标记按钮是否已添加
    let isCustomColorButtonAdded = false;

    // 保存初始颜色
    const initialLineColor = [
        "rgba(255, 99, 132, 1)", // 浅粉色
        "rgba(54, 162, 235, 1)", // 浅蓝色
        "rgba(255, 206, 86, 1)", // 浅黄色
        "rgba(75, 192, 192, 1)", // 浅绿色
        "rgba(153, 102, 255, 1)", // 浅紫色
        "rgba(255, 159, 64, 1)", // 浅橙色
        "rgba(255, 0, 0, 1)" // 敌人攻击颜色
    ];
    const initialFilterColor = [
        "rgba(255, 99, 132, 0.8)", // 浅粉色
        "rgba(54, 162, 235, 0.8)", // 浅蓝色
        "rgba(255, 206, 86, 0.8)", // 浅黄色
        "rgba(75, 192, 192, 0.8)", // 浅绿色
        "rgba(153, 102, 255, 0.8)", // 浅紫色
        "rgba(255, 159, 64, 0.8)", // 浅橙色
        "rgba(255, 0, 0, 0.8)" // 敌人攻击颜色
    ];

    // 存储每个玩家的勾选状态,默认全部勾选
    const playerDrawEnabled = new Array(7).fill(true);
    //存储特效的勾选状态,默认全勾选
    // 索引含义:0-伤害数字,1-线条绘制,2-粒子拖尾,3-击中特效,4-震动特效
    const effectDrawEnabled = new Array(5).fill(true);

    // 定义线条颜色数组,用于不同角色的攻击线条颜色
    const lineColor = [
        "rgba(255, 99, 132, 1)", // 浅粉色
        "rgba(54, 162, 235, 1)", // 浅蓝色
        "rgba(255, 206, 86, 1)", // 浅黄色
        "rgba(75, 192, 192, 1)", // 浅绿色
        "rgba(153, 102, 255, 1)", // 浅紫色
        "rgba(255, 159, 64, 1)", // 浅橙色
        "rgba(255, 0, 0, 1)" // 敌人攻击颜色
    ];
    // 定义滤镜颜色数组,用于线条的外发光效果颜色
    const filterColor = [
        "rgba(255, 99, 132, 0.8)", // 浅粉色
        "rgba(54, 162, 235, 0.8)", // 浅蓝色
        "rgba(255, 206, 86, 0.8)", // 浅黄色
        "rgba(75, 192, 192, 0.8)", // 浅绿色
        "rgba(153, 102, 255, 0.8)", // 浅紫色
        "rgba(255, 159, 64, 0.8)", // 浅橙色
        "rgba(255, 0, 0, 0.8)" // 敌人攻击颜色
    ];

    // 从 localStorage 加载保存的设置
    function readSettings() {
        const ls = localStorage.getItem("MWI_Hit_Tracker_Settings");
        if (ls) {
            const lsObj = JSON.parse(ls);
            lineColor.splice(0, lineColor.length, ...lsObj.lineColor);
            filterColor.splice(0, filterColor.length, ...lsObj.filterColor);
            playerDrawEnabled.splice(0, playerDrawEnabled.length, ...lsObj.playerDrawEnabled);
        }

        // 读取特效设置
        const effectLs = localStorage.getItem("MWI_Hit_Tracker_Effect_Settings");
        if (effectLs) {
            const effectLsObj = JSON.parse(effectLs);
            effectDrawEnabled.splice(0, effectDrawEnabled.length, ...effectLsObj.effectDrawEnabled);
        }
    }

    // 保存设置到 localStorage
    function saveSettings() {
        const settings = {
            lineColor: lineColor,
            filterColor: filterColor,
            playerDrawEnabled: playerDrawEnabled
        };
        localStorage.setItem("MWI_Hit_Tracker_Settings", JSON.stringify(settings));

        // 保存特效设置
        const effectSettings = {
            effectDrawEnabled: effectDrawEnabled
        };
        localStorage.setItem("MWI_Hit_Tracker_Effect_Settings", JSON.stringify(effectSettings));
    }

    // 在初始化时加载设置
    readSettings();

    // 创建自定义颜色按钮
    /**
     * 创建自定义颜色设置按钮,用于打开设置弹出窗口,可设置玩家攻击线条颜色和显示状态。
     */
    function createCustomColorButton() {
        // 使用选择器,查找按钮的父元素
        const tabsContainer = document.querySelector("#root > div > div > div.GamePage_gamePanel__3uNKN > div.GamePage_contentPanel__Zx4FH > div.GamePage_middlePanel__uDts7 > div.GamePage_mainPanel__2njyb > div > div:nth-child(1) > div > div > div > div.TabsComponent_tabsContainer__3BDUp > div > div > div");
        // 获取参考标签,如果 tabsContainer 存在,则取其第二个子元素
        const referenceTab = tabsContainer ? tabsContainer.children[1] : null;

        // 检查是否找到目标元素,如果未找到则输出提示信息并返回
        if (!tabsContainer || !referenceTab) {
            console.log('未找到目标元素,请检查选择器是否正确。');
            return;
        }
        // 检查是否已经存在自定义颜色按钮,如果存在则返回
        if (tabsContainer.querySelector('.Button_customColor__custom')) return;

        // 创建自定义颜色设置按钮
        const customColorButton = document.createElement('button');
        // 为按钮设置自定义类名
        customColorButton.className = 'Button_customColor__custom css-1q2h7u5';
        // 设置按钮的显示文本
        customColorButton.textContent = 'Hit自定义设置';

        // 获取标签容器中的最后一个标签
        const lastTab = tabsContainer.children[tabsContainer.children.length - 1];

        // 遍历标签容器中的所有标签,检查是否存在文本内容为"商品列表"的标签,如果存在则返回
        for (let i = 0; i < tabsContainer.children.length; i++) {
            if (tabsContainer.children[i].textContent === "商品列表") {
                return;
            }
        }

        // 将自定义颜色设置按钮插入到最后一个标签之后
        lastTab.insertAdjacentElement('afterend', customColorButton);

        // 创建样式元素,用于设置按钮和弹出窗口相关的样式
        const style = document.createElement('style');
        // 设置样式内容
        style.innerHTML = `
            .Button_customColor__custom {
                background-color: #546ddb;
                color: white;
                border-radius: 5px;
                padding: 5px 10px;
                cursor: pointer;
                transition: background-color 0.3s;
            }
            .Button_customColor__custom:hover {
                background-color: #131419;
            }
            .expandable-section {
                cursor: pointer;
                padding: 10px;
                background-color: #e0e0e0;
                margin-bottom: 10px;
                border-radius: 5px;
            }
            .expandable-content {
                display: none;
                padding-left: 20px;
            }
            .expandable-content.show {
                display: block;
            }
            .draggable {
                cursor: move;
            }
        `;
        // 将样式元素添加到文档头部
        document.head.appendChild(style);

        // 为自定义颜色设置按钮添加点击事件监听器
        customColorButton.addEventListener('click', () => {
            // 检查文档中是否已存在类名为 "自定义菜单" 的元素,如果有则销毁它
            const customMenu = document.querySelector('.自定义菜单');
            if (customMenu) {
                document.body.removeChild(customMenu);
                return;
            }
            // 创建弹出窗口元素并添加类名 "自定义菜单"
            const popup = document.createElement('div');
            popup.classList.add('自定义菜单');
            // 设置弹出窗口的定位方式为固定定位
            popup.style.position = 'fixed';
            // 设置弹出窗口的垂直居中位置
            popup.style.top = '50%';
            // 设置弹出窗口的水平居中位置
            popup.style.left = '50%';
            // 调整弹出窗口的位置,使其完全居中
            //popup.style.transform = 'translate(-50%, -50%)';
            // 设置弹出窗口的背景颜色
            popup.style.backgroundColor = '#f9f9f9';
            // 设置弹出窗口的内边距
            popup.style.padding = '30px';
            // 设置弹出窗口的边框样式
            popup.style.border = '2px solid #ddd';
            // 设置弹出窗口的边框圆角
            popup.style.borderRadius = '10px';
            // 设置弹出窗口的阴影效果
            popup.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.1)';
            // 设置弹出窗口的层级,确保其显示在最上层
            popup.style.zIndex = '9999';
            // 设置弹出窗口的最小宽度
            popup.style.minWidth = '300px';

            // 使弹出窗口可拖动
            let isDragging = false;
            let startX, startY, initialX, initialY;

            // 鼠标按下事件,开始拖动
            popup.addEventListener('mousedown', (e) => {
                isDragging = true;
                startX = e.clientX;
                startY = e.clientY;
                initialX = popup.offsetLeft;
                initialY = popup.offsetTop;
                // 移除居中 transform 样式
                //popup.style.transform = 'none';
            });

            // 鼠标移动事件,处理拖动
            document.addEventListener('mousemove', (e) => {
                if (isDragging) {
                    e.preventDefault();
                    const dx = e.clientX - startX;
                    const dy = e.clientY - startY;
                    popup.style.left = initialX + dx + 'px';
                    popup.style.top = initialY + dy + 'px';
                }
            });

            // 鼠标释放事件,结束拖动
            document.addEventListener('mouseup', () => {
                isDragging = false;
            });

            // 封装创建可展开部分的函数
            function createExpandableSection(title, contentGenerator = null) {
                // 创建可展开区域元素
                const expandableSection = document.createElement('div');
                // 为可展开区域元素设置类名
                expandableSection.className = 'expandable-section draggable';
                // 初始化展开状态为未展开
                let isExpanded = false;

                // 根据展开状态设置可展开区域的显示文本
                function updateExpandableSectionText() {
                    expandableSection.textContent = isExpanded ? `${title}          ▼` : `${title}           ▶`;
                }

                // 初始设置文本
                updateExpandableSectionText();

                // 创建可展开内容元素
                const expandableContent = document.createElement('div');
                // 为可展开内容元素设置类名
                expandableContent.className = 'expandable-content';

                // 如果有内容生成函数,则调用该函数生成内容
                if (contentGenerator) {
                    contentGenerator(expandableContent);
                }

                // 修改点击事件监听器,更新展开状态并更新文本,同时切换可展开内容的显示状态
                expandableSection.addEventListener('click', () => {
                    isExpanded = !isExpanded;
                    expandableContent.classList.toggle('show');
                    updateExpandableSectionText();
                });

                return { expandableSection, expandableContent };
            }

            // 生成玩家和颜色部分内容的函数
            function generatePlayerColorContent(expandableContent) {
                // 定义玩家名称数组
                const players = ['玩家一', '玩家二', '玩家三', '玩家四', '玩家五', '待定', '敌人'];

                // 封装创建勾选框和标签的通用函数
                function createCheckboxAndLabel(container, labelText, checked, changeHandler) {
                    // 创建勾选框元素
                    const checkbox = document.createElement('input');
                    checkbox.type = 'checkbox';
                    checkbox.checked = checked;
                    checkbox.addEventListener('change', changeHandler);
                    container.appendChild(checkbox);

                    // 创建标签元素
                    const label = document.createElement('span');
                    label.textContent = labelText;
                    label.style.flex = '1';
                    label.style.fontSize = '14px';
                    label.style.marginLeft = '10px';
                    container.appendChild(label);
                }

                // 遍历玩家名称数组,为每个玩家创建颜色选择器和预览
                players.forEach((player, index) => {
                    // 创建容器元素,用于包裹勾选框、标签、颜色选择器和预览
                    const container = document.createElement('div');
                    container.style.marginBottom = '15px';
                    container.style.display = 'flex';
                    container.style.alignItems = 'center';

                    // 创建勾选框和标签
                    createCheckboxAndLabel(container, `${player}: `, playerDrawEnabled[index], (e) => {
                        playerDrawEnabled[index] = e.target.checked;
                    });

                    // 创建颜色选择器元素
                    const colorInput = document.createElement('input');
                    colorInput.type = 'color';
                    colorInput.value = lineColor[index];
                    colorInput.addEventListener('input', (e) => {
                        if (playerDrawEnabled[index]) {
                            lineColor[index] = e.target.value;
                            filterColor[index] = e.target.value.replace('1)', '0.8)');
                            saveSettings(); // 保存设置
                        }
                    });
                    colorInput.style.marginRight = '10px';

                    // 创建颜色预览元素
                    const preview = document.createElement('div');
                    preview.style.width = '30px';
                    preview.style.height = '30px';
                    preview.style.border = '1px solid #ccc';
                    preview.style.borderRadius = '4px';
                    preview.style.backgroundColor = lineColor[index];
                    colorInput.addEventListener('input', (e) => {
                        preview.style.backgroundColor = e.target.value;
                    });

                    // 将颜色选择器和预览元素添加到容器元素中
                    container.appendChild(colorInput);
                    container.appendChild(preview);

                    // 将容器元素添加到可展开内容元素中
                    expandableContent.appendChild(container);
                });
            }

            // 生成特效自定义部分内容的函数
            function generateEffectCustomContent(expandableContent) {
                // 定义特效名称集合,方便后期维护(调整顺序,线条绘制移到第一位)
                const effectNames = [
                    '线条绘制',
                    '伤害数字',
                    '击中特效',
                    '震动'
                ];

                // 定义子菜单配置
                const subMenuConfig = {
                    '伤害数字': ['粒子拖尾'] // 粒子拖尾作为伤害数字的子菜单
                };

                // 封装创建勾选框和标签的通用函数
                function createCheckboxAndLabel(container, labelText, checked, changeHandler, indent = false) {
                    // 创建勾选框元素
                    const checkbox = document.createElement('input');
                    checkbox.type = 'checkbox';
                    checkbox.checked = checked;
                    checkbox.addEventListener('change', changeHandler);

                    // 如果需要缩进(用于子菜单项)
                    if (indent) {
                        const indentSpan = document.createElement('span');
                        indentSpan.innerHTML = '&nbsp;&nbsp;&nbsp;&nbsp;';
                        container.appendChild(indentSpan);
                    }

                    container.appendChild(checkbox);

                    // 创建标签元素
                    const label = document.createElement('span');
                    label.textContent = labelText;
                    label.style.flex = '1';
                    label.style.fontSize = '14px';
                    label.style.marginLeft = '10px';
                    container.appendChild(label);

                    return checkbox;
                }

                // 保存子菜单的勾选框引用,用于控制可见性和勾选状态
                const subMenuCheckboxes = {};

                // 处理父菜单项的变更事件,控制子菜单项
                function handleParentMenuChange(parentName, isChecked) {
                    if (subMenuConfig[parentName]) {
                        subMenuConfig[parentName].forEach(subItem => {
                            if (subMenuCheckboxes[`${parentName}_${subItem}`]) {
                                // 当父菜单取消勾选时,禁用子菜单项
                                subMenuCheckboxes[`${parentName}_${subItem}`].disabled = !isChecked;
                                if (!isChecked) {
                                    // 当父菜单取消勾选时,也取消子菜单的勾选
                                    subMenuCheckboxes[`${parentName}_${subItem}`].checked = false;
                                    // 更新对应的特效状态
                                    if (subItem === '粒子拖尾') {
                                        effectDrawEnabled[2] = false;
                                    }
                                }
                            }
                        });
                    }
                    saveSettings();
                }

                // 遍历特效名称数组,为每个特效创建勾选框和标签
                effectNames.forEach((effect, index) => {
                    // 创建容器元素,用于包裹勾选框和标签
                    const container = document.createElement('div');
                    container.style.marginBottom = '15px';
                    container.style.display = 'flex';
                    container.style.alignItems = 'center';

                    // 使线条绘制对应index=1,伤害数字对应index=0,击中特效对应index=3,震动对应index=4
                    let effectIndex;
                    if (effect === '线条绘制') effectIndex = 1;
                    else if (effect === '伤害数字') effectIndex = 0;
                    else if (effect === '击中特效') effectIndex = 3;
                    else if (effect === '震动') effectIndex = 4;

                    // 创建勾选框和标签
                    const checkbox = createCheckboxAndLabel(container, effect, effectDrawEnabled[effectIndex], (e) => {
                        // 更新全局变量effectDrawEnabled的状态
                        effectDrawEnabled[effectIndex] = e.target.checked;

                        // 如果这个特效有子菜单,处理子菜单的可见性和状态
                        handleParentMenuChange(effect, e.target.checked);

                        // 保存设置
                        saveSettings();
                    });

                    // 将容器元素添加到可展开内容元素中
                    expandableContent.appendChild(container);

                    // 处理子菜单
                    if (subMenuConfig[effect]) {
                        subMenuConfig[effect].forEach(subItem => {
                            // 创建子菜单项容器
                            const subContainer = document.createElement('div');
                            subContainer.style.marginBottom = '10px';
                            subContainer.style.marginLeft = '20px';
                            subContainer.style.display = 'flex';
                            subContainer.style.alignItems = 'center';

                            let subEffectIndex;
                            if (subItem === '粒子拖尾') subEffectIndex = 2;

                            // 创建子菜单项勾选框和标签
                            const subCheckbox = createCheckboxAndLabel(subContainer, subItem, effectDrawEnabled[subEffectIndex], (e) => {
                                // 更新全局变量effectDrawEnabled的状态
                                effectDrawEnabled[subEffectIndex] = e.target.checked;
                                // 保存设置
                                saveSettings();
                            }, true);

                            // 如果父菜单未勾选,禁用子菜单项
                            subCheckbox.disabled = !effectDrawEnabled[effectIndex];

                            // 保存子菜单勾选框引用
                            subMenuCheckboxes[`${effect}_${subItem}`] = subCheckbox;

                            // 将子菜单容器添加到可展开内容元素中
                            expandableContent.appendChild(subContainer);
                        });
                    }
                });
            }

            // 创建玩家和颜色可展开部分
            const { expandableSection: playerColorSection, expandableContent: playerColorContent } = createExpandableSection('玩家和颜色', generatePlayerColorContent);
            popup.appendChild(playerColorSection);
            popup.appendChild(playerColorContent);

            // 创建特效自定义可展开部分
            const { expandableSection: effectCustomSection, expandableContent: effectCustomContent } = createExpandableSection('特效自定义', generateEffectCustomContent);
            popup.appendChild(effectCustomSection);
            popup.appendChild(effectCustomContent);

            // 创建特效参数可展开部分
            const { expandableSection: effectParamsSection, expandableContent: effectParamsContent } = createExpandableSection('特效参数');
            popup.appendChild(effectParamsSection);
            popup.appendChild(effectParamsContent);

            // 创建重置按钮元素
            const resetButton = document.createElement('button');
            // 设置重置按钮的显示文本
            resetButton.textContent = '重置';
            // 设置重置按钮的背景颜色
            resetButton.style.backgroundColor = '#ff4444';
            // 设置重置按钮的文本颜色
            resetButton.style.color = 'white';
            // 设置重置按钮无边框
            resetButton.style.border = 'none';
            // 设置重置按钮的边框圆角
            resetButton.style.borderRadius = '4px';
            // 设置重置按钮的内边距
            resetButton.style.padding = '8px 15px';
            // 设置重置按钮的右外边距
            resetButton.style.marginRight = '10px';
            // 设置重置按钮的鼠标指针样式
            resetButton.style.cursor = 'pointer';
            // 为重置按钮添加点击事件监听器,点击时重置所有设置
            resetButton.addEventListener('click', () => {
                // 添加确认对话框
                const confirmReset = confirm('确定要重置所有设置吗?这将恢复所有默认值。');

                // 如果用户取消,则不执行重置操作
                if (!confirmReset) {
                    return;
                }

                // 重置线条颜色数组
                lineColor.splice(0, lineColor.length, ...initialLineColor);
                // 重置滤镜颜色数组
                filterColor.splice(0, filterColor.length, ...initialFilterColor);
                // 重置玩家显示状态数组,全部设置为勾选状态
                playerDrawEnabled.fill(true);
                // 重置特效选项数组,全部设置为勾选状态
                effectDrawEnabled.fill(true);

                // 保存重置后的设置
                saveSettings();

                // 更新颜色选择器和预览
                // 获取玩家和颜色可展开内容中的所有颜色选择器元素
                const colorInputs = playerColorContent.querySelectorAll('input[type="color"]');
                // 获取玩家和颜色可展开内容中的所有颜色预览元素
                const previews = playerColorContent.querySelectorAll('div:last-child');
                // 遍历颜色选择器和预览元素,更新其值和背景颜色
                colorInputs.forEach((input, index) => {
                    input.value = initialLineColor[index];
                    previews[index].style.backgroundColor = initialLineColor[index];
                });

                // 更新特效选项的勾选状态
                // 获取所有效果设置部分的勾选框
                const effectCheckboxes = effectCustomContent.querySelectorAll('input[type="checkbox"]');
                // 遍历所有勾选框,将其设置为勾选状态
                effectCheckboxes.forEach(checkbox => {
                    checkbox.checked = true;

                    // 如果这是子菜单项,确保它是启用的
                    if (checkbox.disabled) {
                        checkbox.disabled = false;
                    }
                });

                // 更新玩家勾选框状态
                const playerCheckboxes = playerColorContent.querySelectorAll('input[type="checkbox"]');
                playerCheckboxes.forEach(checkbox => {
                    checkbox.checked = true;
                });

                //关闭菜单后再打开新的菜单
                document.body.removeChild(popup);
                //点击customColorButton
                customColorButton.click();
            });

            // 创建保存按钮元素
            const closeButton = document.createElement('button');
            // 设置保存按钮的显示文本
            closeButton.textContent = '保存';
            // 设置保存按钮的背景颜色
            closeButton.style.backgroundColor = '#2196F3';
            // 设置保存按钮的文本颜色
            closeButton.style.color = 'white';
            // 设置保存按钮无边框
            closeButton.style.border = 'none';
            // 设置保存按钮的边框圆角
            closeButton.style.borderRadius = '4px';
            // 设置保存按钮的内边距
            closeButton.style.padding = '8px 15px';
            // 设置保存按钮的鼠标指针样式
            closeButton.style.cursor = 'pointer';
            // 为保存按钮添加点击事件监听器,点击时保存设置并关闭弹出窗口
            closeButton.addEventListener('click', () => {
                saveSettings();
                document.body.removeChild(popup);
            });

            // 创建按钮容器元素
            const buttonContainer = document.createElement('div');
            // 设置按钮容器元素的顶部外边距
            buttonContainer.style.marginTop = '20px';
            // 设置按钮容器元素的显示方式为弹性布局
            buttonContainer.style.display = 'flex';
            // 设置按钮容器元素内子元素的水平靠右对齐
            buttonContainer.style.justifyContent = 'flex-end';
            // 将重置按钮添加到按钮容器元素中
            buttonContainer.appendChild(resetButton);
            // 将保存按钮添加到按钮容器元素中
            buttonContainer.appendChild(closeButton);

            // 将按钮容器元素添加到弹出窗口中
            popup.appendChild(buttonContainer);

            // 将弹出窗口添加到文档主体中
            document.body.appendChild(popup);
        });

        // 标记自定义颜色设置按钮已添加
        isCustomColorButtonAdded = true;
        // 输出提示信息,表示自定义颜色按钮已成功添加
        console.log('自定义颜色按钮已成功添加。');
    }

    // 循环检查按钮是否创建成功
    function checkAndCreateButton() {
        const created = createCustomColorButton();
        if (!created) {
            setTimeout(checkAndCreateButton, 500); // 每 500 毫秒检查一次
        }
    }

    // 修改初始化函数,添加对自定义颜色按钮的调用
    function init() {
        console.log('初始化函数已调用。');
        // 先加载设置
        readSettings();
        // 劫持 WebSocket 消息,以便处理战斗相关的消息
        hookWS();
        // 添加网页可见性变化监听器,当网页从后台恢复时进行清理操作
        addVisibilityChangeListener();
        // 创建动画样式,用于攻击路径的闪烁效果和目标震动效果
        createAnimationStyle();
        // 调用循环检查函数
        checkAndCreateButton();
    }

    // 创建动画样式,包括路径闪烁和目标震动效果
    function createAnimationStyle() {
        // console.log('动画样式函数已调用。');
        const style = document.createElement('style');
        style.textContent = `
            @keyframes lineFlash {
                0% { stroke-opacity: 0.7; }
                50% { stroke-opacity: 0.3; }
                100% { stroke-opacity: 0.7; }
            }

            @keyframes shake {
                0%, 100% { transform: translateX(0); }
                50% { transform: translateX(-1px); } /* 减小震动幅度 */
            }

            .mwht-shake {
                animation: shake 0.2s cubic-bezier(.36,.07,.19,.97) forwards; /* 固定0.2秒持续时间 */
                transform-origin: center;
                position: relative;
                z-index: 200;
            }
        `;
        document.head.appendChild(style);
    }

    // 劫持 WebSocket 消息,拦截并处理战斗相关的消息
    function hookWS() {
        // console.log('劫持函数已调用。');
        const dataProperty = Object.getOwnPropertyDescriptor(MessageEvent.prototype, "data");
        const oriGet = dataProperty.get;

        dataProperty.get = function hookedGet() {
            const socket = this.currentTarget;
            if (!(socket instanceof WebSocket)) {
                return oriGet.call(this);
            }
            if (socket.url.indexOf("api.milkywayidle.com/ws") <= -1 && socket.url.indexOf("api-test.milkywayidle.com/ws") <= -1) {
                return oriGet.call(this);
            }

            if (isPaused) {
                return oriGet.call(this);
            }

            const message = oriGet.call(this);
            Object.defineProperty(this, "data", { value: message });

            return handleMessage(message);
        };

        Object.defineProperty(MessageEvent.prototype, "data", dataProperty);
    }

    // 计算元素中心点坐标
    function getElementCenter(element) {
        const rect = element.getBoundingClientRect();
        if (element.innerText.trim() === '') {
            return {
                x: rect.left + rect.width / 2,
                y: rect.top
            };
        }
        return {
            x: rect.left + rect.width / 2,
            y: rect.top + rect.height / 2
        };
    }

    // 创建抛物线路径,用于攻击动画的路径显示
    function createParabolaPath(startElem, endElem, reversed = false) {
        const start = getElementCenter(startElem);
        const end = getElementCenter(endElem);

        const curveRatio = reversed ? 4 : 2.5;
        const curveHeight = -Math.abs(start.x - end.x) / curveRatio;

        const controlPoint = {
            x: (start.x + end.x) / 2,
            y: Math.min(start.y, end.y) + curveHeight
        };

        if (reversed) {
            return `M ${end.x} ${end.y} Q ${controlPoint.x} ${controlPoint.y}, ${start.x} ${start.y}`;
        }
        return `M ${start.x} ${start.y} Q ${controlPoint.x} ${controlPoint.y}, ${end.x} ${end.y}`;
    }

    // 为目标元素的第三个父级元素添加震动效果,根据第五个父级元素决定震动方向
    function shakeTarget(element) {
        if (!element || isPaused) return;

        // 检查震动特效是否启用
        if (!effectDrawEnabled[4]) return;

        // 向上查找第三个父级元素(用于实际震动)
        let shakeElement = element;
        for (let i = 0; i < 3 && shakeElement; i++) {
            shakeElement = shakeElement.parentElement;
        }

        // 向上查找第五个父级元素(用于判断震动方向)
        let directionElement = element;
        for (let i = 0; i < 5 && directionElement; i++) {
            directionElement = directionElement.parentElement;
        }

        // 如果找到了相应的父级元素,应用震动效果
        if (shakeElement && directionElement) {
            const className = directionElement.className;
            let transformValue = 'translate(0, 0)';

            // 根据第五个父级元素的类名决定震动方向
            if (className.includes('playersArea')) {
                transformValue = 'translate(-2px, 2px)';
            } else if (className.includes('monstersArea')) {
                transformValue = 'translate(2px, 2px)';
            }

            // 添加震动类并设置动画
            shakeElement.classList.add('mwht-shake');

            // 使用自定义动画实现不同方向的震动
            shakeElement.style.animation = `customShake 0.2s cubic-bezier(.36,.07,.19,.97) forwards`;
            shakeElement.style.transformOrigin = 'center';
            shakeElement.style.willChange = 'transform';

            // 存储原始transform值,动画结束后恢复
            const originalTransform = shakeElement.style.transform;

            // 动画帧函数
            let startTime = null;
            const duration = 200; // 200ms = 0.2s

            function animate(currentTime) {
                if (isPaused) return;

                if (!startTime) startTime = currentTime;
                const elapsed = currentTime - startTime;
                const progress = Math.min(elapsed / duration, 1);

                // 计算动画曲线
                const easeOut = 1 - Math.pow(1 - progress, 3);

                // 应用变换
                if (progress < 0.5) {
                    // 前半段:从0到目标偏移
                    const scale = easeOut * 2;
                    shakeElement.style.transform = `translate(${parseFloat(transformValue.split('(')[1]) * scale}px, ${parseFloat(transformValue.split(',')[1]) * scale}px)`;
                } else {
                    // 后半段:从目标偏移回到0
                    const scale = 2 - (easeOut * 2);
                    shakeElement.style.transform = `translate(${parseFloat(transformValue.split('(')[1]) * scale}px, ${parseFloat(transformValue.split(',')[1]) * scale}px)`;
                }

                if (progress < 1) {
                    requestAnimationFrame(animate);
                } else {
                    // 动画结束,恢复原始transform
                    shakeElement.style.transform = originalTransform;
                    shakeElement.classList.remove('mwht-shake');
                    shakeElement.style.animation = '';
                }
            }

            // 启动动画
            requestAnimationFrame(animate);
        }
    }

    // 创建动画效果,包括攻击路径和伤害数字的动画
    function createEffect(startElem, endElem, hpDiff, index, reversed = false) {
        if (isPaused) return;
        // 检查玩家是否被勾选,如果未勾选则不绘制
        if (!playerDrawEnabled[index]) return;

        if (reversed) {
            const dmgDivs = startElem.querySelector('.CombatUnit_splatsContainer__2xcc0')?.querySelectorAll('div') || [];
            for (const div of dmgDivs) {
                if (div.innerText.trim() === '') {
                    startElem = div;
                    break;
                }
            }
        } else {
            const dmgDivs = endElem.querySelector('.CombatUnit_splatsContainer__2xcc0')?.querySelectorAll('div') || [];
            for (const div of dmgDivs) {
                if (div.innerText.trim() === '') {
                    endElem = div;
                    break;
                }
            }
        }

        const svg = document.getElementById('svg-container');
        const frag = document.createDocumentFragment();

        // 根据reversed参数决定目标元素
        const targetElem = reversed ? startElem : endElem;
        // 存储需要在结束时触发击中特效的位置
        const effectPosition = {
            x: 0,
            y: 0
        };

        let path = null;
        let text = null;
        let pathLength = 0;
        let hasTextOrPath = false;

        // 只有当线条绘制特效启用时才创建路径
        if (effectDrawEnabled[1]) {
            hasTextOrPath = true;
            let strokeWidth = '1px';
            let filterWidth = '1px';
            if (hpDiff >= 1000) {
                strokeWidth = '5px';
                filterWidth = '6px';
            } else if (hpDiff >= 700) {
                strokeWidth = '4px';
                filterWidth = '5px';
            } else if (hpDiff >= 500) {
                strokeWidth = '3px';
                filterWidth = '4px';
            } else if (hpDiff >= 300) {
                strokeWidth = '2px';
                filterWidth = '3px';
            } else if (hpDiff >= 100) {
                filterWidth = '2px';
            }

            path = document.createElementNS("http://www.w3.org/2000/svg", "path");
            if (reversed) index = 6;
            Object.assign(path.style, {
                stroke: lineColor[index],
                strokeWidth,
                fill: 'none',
                strokeLinecap: 'round',
                filter: `drop-shadow(0 0 ${filterWidth} ${filterColor[index]})`,
                willChange: 'stroke-dashoffset, opacity'
            });
            path.setAttribute('d', createParabolaPath(startElem, endElem, reversed));
            pathLength = path.getTotalLength();
            path.style.strokeDasharray = pathLength;
            path.style.strokeDashoffset = pathLength;

            // 计算路径终点位置,用于后续触发击中特效
            const endPoint = path.getPointAtLength(pathLength);
            effectPosition.x = endPoint.x;
            effectPosition.y = endPoint.y;

            frag.appendChild(path);
        }

        // 只有当伤害数字特效启用时才创建文本
        if (effectDrawEnabled[0]) {
            hasTextOrPath = true;
            text = document.createElementNS("http://www.w3.org/2000/svg", "text");
            text.textContent = hpDiff;
            const baseFontSize = 5;
            const fontSize = Math.floor(200 * Math.pow(hpDiff / (20000 + hpDiff), 0.45)) - baseFontSize;
            text.setAttribute('font-size', fontSize);
            text.setAttribute('fill', lineColor[index]);
            Object.assign(text.style, {
                opacity: 0,
                filter: `drop-shadow(0 0 5px ${lineColor[index]})`,
                transformOrigin: 'center',
                fontWeight: 'bold',
                willChange: 'transform, opacity, x, y'
            });
            frag.appendChild(text);
        }

        // 如果没有任何可视化效果但仍然需要处理伤害,直接触发震动和击中特效
        if (!hasTextOrPath) {
            // 获取目标元素的位置用于粒子效果
            const targetCenter = getElementCenter(targetElem);
            effectPosition.x = targetCenter.x;
            effectPosition.y = targetCenter.y;

            // 独立触发震动和击中特效
            if (effectDrawEnabled[3]) {
                createParticleEffect(effectPosition.x, effectPosition.y, lineColor[index]);
            }

            if (effectDrawEnabled[4]) {
                shakeTarget(targetElem);
            }
            return;
        }

        svg.appendChild(frag);

        // 如果创建了路径,设置路径动画
        if (path) {
            setTimeout(() => {
                requestAnimationFrame(() => {
                    path.style.transition = 'stroke-dashoffset 1s linear';
                    path.style.strokeDashoffset = '0';
                });
            }, 100);

            setTimeout(() => {
                requestAnimationFrame(() => {
                    path.style.transition = 'stroke-dashoffset 1s cubic-bezier(0.25, 0.46, 0.45, 0.94), opacity 1s cubic-bezier(0.25, 0.46, 0.45, 0.94)';
                    path.style.strokeDashoffset = -pathLength;
                    path.style.opacity = 0;

                    const removePath = () => {
                        path.remove();

                        // 如果没有启用伤害数字特效,在路径移除后触发击中特效和震动
                        if (!text) {
                            if (effectDrawEnabled[3]) {
                                createParticleEffect(effectPosition.x, effectPosition.y, lineColor[index]);
                            }

                            if (effectDrawEnabled[4]) {
                                shakeTarget(targetElem);
                            }
                        }
                    };
                    path.addEventListener('transitionend', removePath, { once: true });
                });
            }, 900);
        }

        // 如果创建了文本,设置文本动画
        if (text) {
            // 如果同时有路径和文本,让文本沿着路径移动
            if (path) {
                setTimeout(() => {
                    requestAnimationFrame(() => {
                        animateText(path, text, pathLength, lineColor[index], targetElem);
                    });
                }, 100);
            } else {
                // 如果只有文本没有路径,创建一个虚拟路径用于文本动画
                const virtualPath = document.createElementNS("http://www.w3.org/2000/svg", "path");
                virtualPath.setAttribute('d', createParabolaPath(startElem, endElem, reversed));
                const virtualPathLength = virtualPath.getTotalLength();

                // 计算虚拟路径终点位置,用于后续触发击中特效
                const endPoint = virtualPath.getPointAtLength(virtualPathLength);
                effectPosition.x = endPoint.x;
                effectPosition.y = endPoint.y;

                // 直接设置文本动画,不添加虚拟路径到DOM
                animateText(virtualPath, text, virtualPathLength, lineColor[index], targetElem);
            }
        }
    }

    // 从对象池获取粒子元素
    function getParticleFromPool() {
        if (particlePool.length > 0) {
            return particlePool.pop();
        }
        return document.createElementNS("http://www.w3.org/2000/svg", "circle");
    }

    // 将粒子元素返回对象池
    function returnParticleToPool(particle) {
        particle.removeAttribute('r');
        particle.removeAttribute('fill');
        particle.removeAttribute('cx');
        particle.removeAttribute('cy');
        particle.style.opacity = 1;
        particle.style.transform = 'none';
        particle.removeEventListener('transitionend', () => {});
        particlePool.push(particle);
    }

    // 创建击中粒子特效,在伤害数字消失时显示
    function createParticleEffect(x, y, color) {
        if (isPaused) return;
        // 击中特效的启用状态已在调用前检查,不需要再检查
        // if (!effectDrawEnabled[3]) return;

        const svg = document.getElementById('svg-container');
        const numParticles = 20;
        const frag = document.createDocumentFragment();

        const batchSize = 5;
        let batchCount = 0;

        function createBatch() {
            for (let i = 0; i < batchSize && batchCount * batchSize + i < numParticles; i++) {
                const particle = getParticleFromPool();
                particle.setAttribute('r', '2');
                particle.setAttribute('fill', color);
                particle.setAttribute('cx', x);
                particle.setAttribute('cy', y);
                particle.style.opacity = 1;
                particle.style.transformOrigin = 'center';
                particle.style.willChange = 'transform, opacity';

                const angle = ((batchCount * batchSize + i) / numParticles) * 2 * Math.PI;
                const distance = Math.random() * 30 + 10;
                const endX = parseFloat(x) + distance * Math.cos(angle);
                const endY = parseFloat(y) + distance * Math.sin(angle);

                frag.appendChild(particle);

                requestAnimationFrame(() => {
                    particle.style.transition = 'all 0.3s ease-out';
                    particle.setAttribute('cx', endX);
                    particle.setAttribute('cy', endY);
                    particle.style.opacity = 0;

                    particle.addEventListener('transitionend', () => {
                        returnParticleToPool(particle);
                    }, { once: true });

                    setTimeout(() => {
                        if (particle.parentNode) {
                            particle.parentNode.removeChild(particle);
                            returnParticleToPool(particle);
                        }
                    }, 5000);
                });
            }
            batchCount++;
            if (batchCount * batchSize < numParticles) {
                setTimeout(createBatch, 50);
            } else {
                svg.appendChild(frag);
            }
        }
        createBatch();
    }

    // 文本动画函数 - 使用 requestAnimationFrame 实现更流畅的动画
    /**
     * 执行文本动画,让文本沿着指定路径移动,并在移动过程中产生粒子效果。
     *
     * @param {SVGPathElement} path - 文本移动的路径元素。
     * @param {SVGTextElement} text - 要进行动画的文本元素。
     * @param {number} pathLength - 路径的总长度。
     * @param {string} color - 文本和粒子的颜色。
     * @param {HTMLElement} targetElem - 目标元素,用于震动效果。
     */
    function animateText(path, text, pathLength, color, targetElem) {
        // 动画配置对象,包含动画持续时间、淡入开始和结束时间、粒子生成间隔
        const animationConfig = {
            duration: 1350, // 动画总持续时间,单位为毫秒
            fadeInStart: 0.0, // 淡入开始的进度比例
            fadeInEnd: 0.3, // 淡入结束的进度比例
            particleInterval: 3 // 粒子生成的间隔百分比
        };

        let startTime = null; // 动画开始的时间戳
        let lastParticleFrame = 0; // 上一次生成粒子时的进度百分比

        /**
         * 动画循环函数,使用 requestAnimationFrame 不断更新文本和粒子的状态。
         *
         * @param {number} currentTime - 当前的时间戳。
         */
        function animate(currentTime) {
            // 如果脚本处于暂停状态,则停止动画
            if (isPaused) return;

            // 如果动画还未开始,记录当前时间为开始时间
            if (!startTime) startTime = currentTime;

            // 计算从动画开始到现在经过的时间
            const elapsed = currentTime - startTime;
            // 计算动画的进度,取值范围为 0 到 1
            const progress = Math.min(elapsed / animationConfig.duration, 1);

            // 根据进度获取路径上的点
            const point = path.getPointAtLength(progress * pathLength);

            // 更新文本的位置
            text.setAttribute('x', point.x);
            text.setAttribute('y', point.y);

            let opacity = 1; // 文本的透明度
            // 如果进度小于淡入开始时间,文本完全透明
            if (progress < animationConfig.fadeInStart) {
                opacity = 0;
            }
            // 如果进度在淡入开始和结束时间之间,计算透明度的渐变值
            else if (progress < animationConfig.fadeInEnd) {
                opacity = 0.7 + 0.3 * ((progress - animationConfig.fadeInStart) / (animationConfig.fadeInEnd - animationConfig.fadeInStart));
            }
            // 更新文本的透明度
            text.style.opacity = opacity;

            // 检查是否达到粒子生成的间隔,并且上次生成粒子的进度不同
            if (Math.floor(progress * 100) % animationConfig.particleInterval === 0 && lastParticleFrame !== Math.floor(progress * 100)) {
                // 记录当前生成粒子的进度
                lastParticleFrame = Math.floor(progress * 100);
            }

            /**
             * 创建粒子拖尾效果
             * @param {Object} point - 粒子的起始坐标,包含 x 和 y 属性
             * @param {string} color - 粒子的填充颜色
             */
            function createParticleTrail(point, color) {
                // 粒子拖尾特效的启用状态已在调用前检查,不需要再检查
                // if (!effectDrawEnabled[2]) return;

                // 从粒子对象池中获取一个粒子
                const particle = getParticleFromPool();
                // 设置粒子的半径
                particle.setAttribute('r', '2');
                // 设置粒子的填充颜色
                particle.setAttribute('fill', color);
                // 设置粒子的 x 坐标,添加随机偏移
                particle.setAttribute('cx', point.x + (Math.random() - 0.5) * 10);
                // 设置粒子的 y 坐标,添加随机偏移
                particle.setAttribute('cy', point.y + (Math.random() - 0.5) * 10);
                // 设置粒子的初始透明度为 1
                particle.style.opacity = 1;
                // 设置粒子的过渡效果
                particle.style.transition = 'all 0.2s ease-out';
                // 告诉浏览器哪些属性会发生变化,优化性能
                particle.style.willChange = 'opacity, transform';

                // 获取 SVG 容器元素
                const svg = document.getElementById('svg-container');
                // 将粒子添加到 SVG 容器中
                svg.appendChild(particle);

                // 在下一帧请求动画,开始粒子的淡出效果
                requestAnimationFrame(() => {
                    particle.style.opacity = 0;
                    // 监听粒子过渡结束事件,过渡结束后将粒子放回对象池
                    particle.addEventListener('transitionend', () => {
                        returnParticleToPool(particle);
                    }, { once: true });
                });
            }

            // 如果动画进度小于 1,继续请求下一帧动画
            if (progress < 1) {
                requestAnimationFrame(animate);

                // 只在粒子拖尾特效启用时才创建粒子
                if (effectDrawEnabled[2]) {
                    // 调用粒子拖尾效果函数,传入粒子的起始坐标和颜色
                    createParticleTrail(point, color);
                }
            }
            // 动画进度达到 1,执行结束动画
            else {
                // 设置文本的过渡效果
                text.style.transition = 'all 0.2s ease-out';
                // 放大文本
                text.style.transform = 'scale(1.5)';
                // 让文本透明
                text.style.opacity = 0;

                // 保存文本最终位置,用于后续触发击中特效
                const finalX = text.getAttribute('x');
                const finalY = text.getAttribute('y');

                // 延迟 100 毫秒后执行后续操作
                setTimeout(() => {
                    // 移除文本元素
                    text.remove();

                    // 独立触发击中特效和震动
                    if (effectDrawEnabled[3]) {
                        // 在文本的位置创建粒子效果
                        createParticleEffect(finalX, finalY, color);
                    }

                    if (effectDrawEnabled[4]) {
                        // 触发震动效果
                        shakeTarget(targetElem);
                    }
                }, 100);
            }
        }

        // 启动动画循环
        requestAnimationFrame(animate);
    }

    // 创建线条动画,根据攻击信息创建攻击路径和伤害数字动画
    /**
     * 创建从一个角色到另一个角色的攻击线条,并触发相应的特效。
     *
     * @param {number} from - 攻击发起者的索引。
     * @param {number} to - 攻击目标的索引。
     * @param {number} hpDiff - 伤害值,即生命值的差值。
     * @param {boolean} [reversed=false] - 指示攻击是否是反向的,默认为 false。
     */
    function createLine(from, to, hpDiff, reversed = false) {
        // 如果脚本处于暂停状态,则直接返回,不执行后续操作
        if (isPaused) return;
        // 查找玩家区域元素
        const playerArea = document.querySelector(".BattlePanel_playersArea__vvwlB");
        // 查找怪物区域元素
        const monsterArea = document.querySelector(".BattlePanel_monstersArea__2dzrY");
        // 查找游戏主面板元素
        const gamePanel = document.querySelector(".GamePage_mainPanel__2njyb");

        // 如果任何一个必要元素未找到,则直接返回,不执行后续操作
        if (!playerArea || !monsterArea || !gamePanel) return;

        // 获取玩家区域的第一个子元素,作为玩家容器
        const playersContainer = playerArea.firstElementChild;
        // 获取怪物区域的第一个子元素,作为怪物容器
        const monsterContainer = monsterArea.firstElementChild;

        // 获取攻击发起者的元素
        const effectFrom = playersContainer?.children[from];
        // 获取攻击目标的元素
        const effectTo = monsterContainer?.children[to];

        // 如果攻击发起者或目标元素未找到,则直接返回,不执行后续操作
        if (!effectFrom || !effectTo) return;

        // 查找 SVG 容器元素
        let svgContainer = document.getElementById('svg-container');

        // 如果 SVG 容器元素不存在,则创建一个新的 SVG 容器
        if (!svgContainer) {
            // 定义 SVG 的命名空间
            const svgNS = 'http://www.w3.org/2000/svg';
            // 创建 SVG 元素
            svgContainer = document.createElementNS(svgNS, 'svg');
            // 设置 SVG 元素的 ID
            svgContainer.id = 'svg-container';

            // 设置 SVG 元素的样式
            Object.assign(svgContainer.style, {
                position: 'fixed',
                top: '0',
                left: '0',
                width: '100%',
                height: '100%',
                pointerEvents: 'none',
                overflow: 'visible',
                zIndex: '190'
            });

            // 定义一个函数,用于设置 SVG 的 viewBox 属性
            const setViewBox = () => {
                // 获取窗口的宽度
                const width = window.innerWidth;
                // 获取窗口的高度
                const height = window.innerHeight;
                // 设置 SVG 的 viewBox 属性
                svgContainer.setAttribute('viewBox', `0 0 ${width} ${height}`);
            };

            // 首次调用 setViewBox 函数,设置 SVG 的 viewBox 属性
            setViewBox();
            // 设置 SVG 的 preserveAspectRatio 属性
            svgContainer.setAttribute('preserveAspectRatio', 'none');
            // 将 SVG 元素添加到游戏主面板中
            gamePanel.appendChild(svgContainer);

            // 如果窗口大小改变监听器还未添加,则添加该监听器
            if (!isResizeListenerAdded) {
                // 监听窗口大小改变事件,当窗口大小改变时调用 setViewBox 函数
                window.addEventListener('resize', setViewBox);
                // 标记窗口大小改变监听器已添加
                isResizeListenerAdded = true;
            }
        }

        // 根据攻击是否反向,确定攻击发起者的索引
        const originIndex = reversed ? to : from;
        // 调用 createEffect 函数,创建攻击特效
        createEffect(effectFrom, effectTo, hpDiff, originIndex, reversed);
    }

    // 处理伤害信息,根据新旧生命值计算伤害差值并创建动画
    /**
     * 处理伤害数据,根据旧的生命值数组和新的实体映射,计算生命值差值,并在满足条件时创建攻击线条。
     *
     * @param {Array} oldHPArr - 旧的生命值数组,存储每个实体的旧生命值。
     * @param {Object} newMap - 新的实体映射,键为实体索引,值为包含当前生命值(cHP)的实体对象。
     * @param {number} castIndex - 施法者的索引。
     * @param {Array} attackerIndices - 攻击者的索引数组。
     * @param {boolean} [isReverse=false] - 可选参数,指示攻击方向是否反转。
     */
    function processDamage(oldHPArr, newMap, castIndex, attackerIndices, isReverse = false) {
        // 遍历旧的生命值数组
        oldHPArr.forEach((oldHP, index) => {
            // 从新的实体映射中获取对应索引的实体
            const entity = newMap[index];
            // 如果实体不存在,则跳过当前循环
            if (!entity) return;

            // 计算旧生命值和当前生命值的差值
            const hpDiff = oldHP - entity.cHP;
            // 更新旧生命值数组中的值为当前生命值
            oldHPArr[index] = entity.cHP;

            // 如果生命值差值大于 0 且攻击者索引数组不为空
            if (hpDiff > 0 && attackerIndices.length > 0) {
                // 如果攻击者索引数组长度大于 1
                if (attackerIndices.length > 1) {
                    // 遍历攻击者索引数组
                    attackerIndices.forEach(attackerIndex => {
                        // 如果攻击者索引等于施法者索引
                        if (attackerIndex === castIndex) {
                            // 调用 createLine 函数创建攻击线条
                            createLine(attackerIndex, index, hpDiff, isReverse);
                        }
                    });
                } else {
                    // 如果攻击者索引数组长度为 1,直接调用 createLine 函数创建攻击线条
                    createLine(attackerIndices[0], index, hpDiff, isReverse);
                }
            }
        });
    }

    // 检测施法者,通过比较新旧魔法值找出施法者索引
    function detectCaster(oldMPArr, newMap) {
        let casterIndex = -1;
        Object.keys(newMap).forEach(index => {
            const newMP = newMap[index].cMP;
            if (newMP < oldMPArr[index]) {
                casterIndex = index;
            }
            oldMPArr[index] = newMP;
        });
        return casterIndex;
    }

    // 处理 WebSocket 消息,根据消息类型更新战斗状态并创建攻击动画
    function handleMessage(message) {
        if (isPaused) {
            return message;
        }

        let obj;
        try {
            obj = JSON.parse(message);
        } catch (error) {
            console.error('Failed to parse WebSocket message:', error);
            return message;
        }

        if (obj && obj.type === "new_battle") {
            battleState.monstersHP = obj.monsters.map((monster) => monster.currentHitpoints);
            battleState.monstersMP = obj.monsters.map((monster) => monster.currentManapoints);
            battleState.playersHP = obj.players.map((player) => player.currentHitpoints);
            battleState.playersMP = obj.players.map((player) => player.currentManapoints);

            const svg = document.getElementById('svg-container');
            if (svg) {
                while (svg.firstChild) {
                    svg.removeChild(svg.firstChild);
                }
            }
            particlePool.length = 0;
        } else if (obj && obj.type === "battle_updated" && battleState.monstersHP.length) {
            const mMap = obj.mMap;
            const pMap = obj.pMap;
            const monsterIndices = Object.keys(obj.mMap);
            const playerIndices = Object.keys(obj.pMap);

            const castMonster = detectCaster(battleState.monstersMP, mMap);
            const castPlayer = detectCaster(battleState.playersMP, pMap);

            processDamage(battleState.monstersHP, mMap, castPlayer, playerIndices, false);
            processDamage(battleState.playersHP, pMap, castMonster, monsterIndices, true);
        }

        return message;
    }

    // 检测网页是否从后台恢复,当网页从后台恢复时清理 SVG 容器中的元素
    function addVisibilityChangeListener() {
        document.addEventListener('visibilitychange', function () {
            if (document.visibilityState === 'hidden') {
                isPaused = true;
            } else if (document.visibilityState === 'visible') {
                isPaused = false;
                const svg = document.getElementById('svg-container');
                if (svg) {
                    while (svg.firstChild) {
                        svg.removeChild(svg.firstChild);
                    }
                }
                document.querySelectorAll('[id^="mwi-hit-tracker-"]').forEach(el => {
                    if (el) {
                        el.remove();
                    }
                });
                document.querySelectorAll('circle[fill^="rgba"]').forEach(el => {
                    if (el.parentNode === svg) {
                        el.parentNode.removeChild(el);
                    }
                });
            }
        });
    }

    // 启动初始化函数
    init();

})();