放生鱼鱼小助手

基于页面源码数据的概率统计与收益分析,提供可视化的掉落概率与倍率展示。

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         放生鱼鱼小助手
// @namespace    http://tampermonkey.net/
// @version      2.0
// @description  基于页面源码数据的概率统计与收益分析,提供可视化的掉落概率与倍率展示。
// @author       kiwi4814
// @license      MIT
// @match        https://si-qi.xyz/free_fishes.php*
// @grant        none
// @run-at       document-end
// ==/UserScript==

(function () {
    'use strict';

    /**
     * 配置常量
     */
    const CONFIG = {
        MULTIPLIERS: {
            'common': 1,
            'uncommon': 1,
            'rare': 1,
            'epic': 2,
            'legendary': 4
        },
        LABELS: {
            'mowan': '⚗️ 魔丸',
            'corn': '🌽 玉米',
            'carrot': '🥕 胡萝卜',
            'worm': '🪱 蚯蚓',
            'popularity': '🍽️ 受欢迎值',
            'tomato': '🍅 西红柿',
            'mushroom': '🍄 蘑菇',
            'eggplant': '🍆 茄子',
            'none': '无收益'
        },
        PRIORITY_KEYS: ['mowan', 'popularity'] // 优先展示的物品
    };

    /**
     * 核心逻辑类
     */
    class FishAnalytics {
        constructor() {
            this.data = this.fetchGameData();
            if (this.data) {
                this.initUI();
            }
        }

        /**
         * 从页面源码中提取 FREE_FISH_DATA
         */
        fetchGameData() {
            try {
                const regex = /const\s+FREE_FISH_DATA\s*=\s*(\{.*?\});/s;
                const match = document.documentElement.innerHTML.match(regex);
                if (match && match[1]) {
                    return JSON.parse(match[1]);
                }
            } catch (error) {
                console.error('[数据分析面板] 数据解析异常:', error);
            }
            console.error('[数据分析面板] 未检测到源数据');
            return null;
        }

        /**
         * 解析文本中的数值 (例如 "受欢迎 +20" -> "+20")
         */
        extractValue(label, key) {
            if (!label) return '';
            const plusMatch = label.match(/\+(\d+)/);
            if (plusMatch) return `+${plusMatch[1]}`;

            const quantityMatch = label.match(/(\d+)\s*个/);
            if (quantityMatch) return `x${quantityMatch[1]}`;

            if (key !== 'popularity' && key !== 'none') return 'x1';
            return '';
        }

        /**
         * 构建概率概览表格 HTML
         */
        renderOverviewTable() {
            const rarities = this.data.rarity_order || ['common', 'uncommon', 'rare', 'epic', 'legendary'];

            let rows = rarities.map(key => {
                const rewards = this.data.release_rewards[key] || [];
                const noneItem = rewards.find(r => r.key === 'none');
                // 假设总权重为1000,计算百分比
                const failRate = noneItem ? (noneItem.weight / 10) : 0;
                const successRate = (100 - failRate).toFixed(1);
                const multiplier = CONFIG.MULTIPLIERS[key];
                const meta = this.data.rarity_map[key];

                // 动态计算颜色,与原站稀有度颜色保持一致或使用通用警告色
                const rateColor = failRate > 50 ? '#d9534f' : '#2c9b61'; // Bootstrap danger/success colors matches theme

                return `
                    <tr>
                        <td class="text-left">
                            <div class="rarity-cell">
                                <img src="${meta.icon}" alt="${meta.label}">
                                <span class="rarity-name" data-rarity="${key}">${meta.label}</span>
                            </div>
                        </td>
                        <td><span class="badge-pill type-${multiplier}">x${multiplier}</span></td>
                        <td style="color: ${rateColor}; font-weight: bold;">${failRate}%</td>
                        <td>${successRate}%</td>
                    </tr>
                `;
            }).join('');

            return `
                <div class="analytics-section">
                    <div class="section-title">📊 概率概览</div>
                    <div class="table-responsive">
                        <table class="analytics-table">
                            <thead>
                                <tr>
                                    <th class="text-left">稀有度</th>
                                    <th>收益倍率</th>
                                    <th>无收益概率</th>
                                    <th>物品掉落率</th>
                                </tr>
                            </thead>
                            <tbody>${rows}</tbody>
                        </table>
                    </div>
                </div>
            `;
        }

        /**
         * 构建详细掉落表格 HTML
         */
        renderDetailTable() {
            const rarities = this.data.rarity_order || [];
            const rewardsMap = this.data.release_rewards;

            // 收集所有可能的物品key
            const allKeys = new Set();
            Object.values(rewardsMap).forEach(list => {
                list.forEach(item => {
                    if (item.key !== 'none') allKeys.add(item.key);
                });
            });

            // 排序:高优先级 -> 其他
            const sortedKeys = Array.from(allKeys).sort((a, b) => {
                const pA = CONFIG.PRIORITY_KEYS.indexOf(a);
                const pB = CONFIG.PRIORITY_KEYS.indexOf(b);
                if (pA !== -1 && pB !== -1) return pA - pB;
                if (pA !== -1) return -1;
                if (pB !== -1) return 1;
                return a.localeCompare(b);
            });

            // 表头
            const headerCols = rarities.map(r => {
                const label = this.data.rarity_map[r].label.replace('鱼', '');
                return `<th>${label}</th>`;
            }).join('');

            // 表内容
            const rows = sortedKeys.map(itemKey => {
                const itemName = CONFIG.LABELS[itemKey] || itemKey;
                const isRare = CONFIG.PRIORITY_KEYS.includes(itemKey);

                const cols = rarities.map(rarityKey => {
                    const list = rewardsMap[rarityKey] || [];
                    const item = list.find(r => r.key === itemKey);
                    const prob = item ? (item.weight / 10) : 0;

                    if (prob === 0) return `<td class="muted">-</td>`;

                    const valueText = this.extractValue(item.label, itemKey);
                    // 高亮稀有物品的掉率
                    const style = isRare ? 'font-weight: bold; color: var(--free-primary-dark);' : '';

                    return `
                        <td style="${style}">
                            <div>${prob}%</div>
                            ${valueText ? `<div class="sub-text">${valueText}</div>` : ''}
                        </td>
                    `;
                }).join('');

                return `<tr class="${isRare ? 'highlight-row' : ''}"><td class="text-left item-name">${itemName}</td>${cols}</tr>`;
            }).join('');

            return `
                <div class="analytics-section">
                    <div class="section-title">🎁 物品掉落详情</div>
                    <div class="table-responsive">
                        <table class="analytics-table compact">
                            <thead>
                                <tr>
                                    <th class="text-left">物品名称</th>
                                    ${headerCols}
                                </tr>
                            </thead>
                            <tbody>${rows}</tbody>
                        </table>
                    </div>
                </div>
            `;
        }

        /**
         * 注入CSS样式
         */
        injectStyles() {
            const style = document.createElement('style');
            style.textContent = `
                :root {
                    --analytics-border: rgba(247, 163, 37, 0.25);
                    --analytics-bg-header: rgba(247, 163, 37, 0.1);
                    --analytics-row-hover: rgba(255, 250, 240, 0.8);
                }
                .analytics-card {
                    margin-bottom: 24px;
                    transition: all 0.3s ease;
                }
                .analytics-header {
                    cursor: pointer;
                    display: flex;
                    justify-content: space-between;
                    align-items: center;
                    user-select: none;
                }
                .toggle-icon {
                    font-size: 14px;
                    color: var(--free-primary-dark);
                    transition: transform 0.3s ease;
                }
                .analytics-content {
                    margin-top: 20px;
                    border-top: 1px dashed var(--analytics-border);
                    padding-top: 20px;
                    display: none;
                    animation: fadeIn 0.3s ease-in-out;
                }
                .analytics-section {
                    margin-bottom: 24px;
                }
                .section-title {
                    font-size: 15px;
                    font-weight: 600;
                    color: var(--free-primary-dark);
                    margin-bottom: 12px;
                    display: flex;
                    align-items: center;
                    gap: 6px;
                }
                .table-responsive {
                    overflow-x: auto;
                    border-radius: 12px;
                    border: 1px solid var(--analytics-border);
                }
                .analytics-table {
                    width: 100%;
                    border-collapse: collapse;
                    font-size: 14px;
                    text-align: center;
                    background: rgba(255, 255, 255, 0.5);
                }
                .analytics-table th {
                    background: var(--analytics-bg-header);
                    color: var(--free-text);
                    padding: 10px 12px;
                    font-weight: 600;
                    white-space: nowrap;
                }
                .analytics-table td {
                    padding: 8px 12px;
                    border-top: 1px solid rgba(0,0,0,0.04);
                    color: var(--free-text);
                    vertical-align: middle;
                }
                .analytics-table tr:hover {
                    background-color: var(--analytics-row-hover);
                }
                .analytics-table .text-left { text-align: left; }
                .analytics-table.compact td { padding: 6px 8px; font-size: 13px; }

                /* 单元格样式 */
                .rarity-cell { display: flex; align-items: center; gap: 8px; }
                .rarity-cell img { width: 22px; height: 22px; }
                .rarity-name { font-weight: 600; }

                /* 稀有度颜色适配 */
                .rarity-name[data-rarity="uncommon"] { color: #3c9d9b; }
                .rarity-name[data-rarity="rare"] { color: #4169e1; }
                .rarity-name[data-rarity="epic"] { color: #7b3fa1; }
                .rarity-name[data-rarity="legendary"] { color: #d79f00; }

                /* 徽章样式 */
                .badge-pill {
                    display: inline-block;
                    padding: 2px 10px;
                    border-radius: 99px;
                    font-size: 12px;
                    font-weight: bold;
                    color: #fff;
                    background: #a27a37;
                }
                .badge-pill.type-2 { background: #7b3fa1; }
                .badge-pill.type-4 { background: #d79f00; box-shadow: 0 0 5px rgba(215, 159, 0, 0.4); }

                .item-name { font-weight: 500; }
                .sub-text { font-size: 11px; color: #999; margin-top: 2px; }
                .muted { color: #ccc; }
                .highlight-row { background-color: rgba(255, 248, 220, 0.4); }

                @keyframes fadeIn {
                    from { opacity: 0; transform: translateY(-5px); }
                    to { opacity: 1; transform: translateY(0); }
                }
            `;
            document.head.appendChild(style);
        }

        /**
         * 初始化界面
         */
        initUI() {
            this.injectStyles();

            const container = document.createElement('div');
            container.className = 'card analytics-card';

            // 组合HTML
            container.innerHTML = `
                <div class="card-header analytics-header" id="analytics-toggle">
                    <div>
                        <div class="title">📈 放生数据统计面板</div>
                        <div class="subtitle">基于当前版本算法的收益模型分析</div>
                    </div>
                    <div class="toggle-icon">▼</div>
                </div>
                <div class="analytics-content" id="analytics-body">
                    ${this.renderOverviewTable()}
                    ${this.renderDetailTable()}
                    <div style="font-size: 12px; color: var(--free-muted); margin-top: 10px; text-align: right;">
                        * 数据来源于游戏源码实时计算,仅供参考
                    </div>
                </div>
            `;

            // 插入到页面合适位置 (在 freeFishApp 之前或之后)
            const app = document.getElementById('freeFishApp');
            if (app) {
                app.parentNode.insertBefore(container, app);
            } else {
                // 兜底插入
                const main = document.querySelector('.mainouter');
                if (main) main.appendChild(container);
            }

            // 绑定折叠事件
            this.bindEvents(container);
        }

        bindEvents(container) {
            const header = container.querySelector('#analytics-toggle');
            const body = container.querySelector('#analytics-body');
            const icon = container.querySelector('.toggle-icon');

            // 读取本地存储的状态
            const isExpanded = localStorage.getItem('siqi_analytics_expanded') === 'true';

            const updateState = (expanded) => {
                body.style.display = expanded ? 'block' : 'none';
                icon.style.transform = expanded ? 'rotate(180deg)' : 'rotate(0deg)';
            };

            // 初始化状态
            updateState(isExpanded);

            header.addEventListener('click', () => {
                const currentDisplay = body.style.display;
                const newState = currentDisplay === 'none';
                updateState(newState);
                localStorage.setItem('siqi_analytics_expanded', newState);
            });
        }
    }

    // 启动脚本
    new FishAnalytics();

})();