// ==UserScript==
// @name MoYuIdleHelper
// @namespace https://tampermonkey.net/
// @version 1.0
// @description 摸鱼放置助手 - 全新UI
// @author Mid
// @license MIT
// @match https://www.moyu-idle.com/*
// @match https://moyu-idle.com/*
// @grant none
// @require https://cdn.jsdelivr.net/npm/[email protected]/dist/pako.min.js
// ==/UserScript==
(function () {
'use strict';
let combatLogEnabled = true;
const damageAccum = new Map(); // uuid → 累计伤害
const actionCount = new Map(); // uuid → 动作次数
const healAccum = new Map(); // uuid → 累计治疗
// 仓库物品变动跟踪
let inventory = {}; // 当前仓库状态
let inventoryChanges = new Map(); // 物品变动记录
// 技能名称映射
const skillNames = {
baseAttack: "普通攻击",
boneShield: "骨盾",
corrosiveBreath: "腐蚀吐息",
summonBerryBird: "召唤浆果鸟",
baseHeal: "基础治疗",
poison: "中毒",
selfHeal: "自我疗愈",
sweep: "横扫",
baseGroupHeal: "基础群体治疗",
powerStrike: "重击",
guardianLaser: "守护者激光",
lavaBreath: "熔岩吐息",
dragonRoar: "龙之咆哮",
doubleStrike: "双重打击",
lowestHpStrike: "弱点打击",
explosiveShot: "爆炸射击",
freeze: "冻结",
iceBomb: "冰弹",
lifeDrain: "吸血",
roar: "咆哮",
blizzard: "暴风雪",
ironWall: "铁壁",
curse: "诅咒",
shadowBurst: "暗影爆发",
groupCurse: "群体诅咒",
holyLight: "神圣之光",
bless: "祝福",
revive: "复活",
groupRegen: "群体再生",
astralBarrier: "星辉结界",
astralBlast: "星辉冲击",
groupSilence: "群体沉默",
selfRepair: "自我修复",
cleanse: "驱散",
cometStrike: "彗星打击",
armorBreak: "破甲",
starTrap: "星辰陷阱",
emperorCatFinale_forAstralEmpressBoss: "星辉终极裁决",
astralStorm: "星辉风暴",
groupShield: "群体护盾",
sneak: "潜行",
ambush: "偷袭",
poisonClaw: "毒爪",
shadowStep: "暗影步",
silenceStrike: "沉默打击",
slientSmokeScreen: "静默烟雾弹",
mirrorImage: "镜像影分身",
shadowAssassinUlt: "绝影连杀",
stardustMouseSwap: "偷天换日",
dizzySpin: "眩晕旋转",
carouselOverdrive: "失控加速",
candyBomb: "糖果爆裂",
prankSmoke: "恶作剧烟雾",
plushTaunt: "毛绒嘲讽",
starlightSanctuary: "星光治愈",
ghostlyStrike: "鬼影冲锋",
paradeHorn: "狂欢号角",
clownSummon: "小丑召集令",
kingAegis: "猫王庇护"
};
// 物品ID到名称映射
const itemIdNameMap = {
"__satiety": "饱食度",
"__cat": "小猫咪",
"gold": "金币",
"catPawCoin": "猫爪古钱币",
"wood": "木材",
"stone": "矿石",
"coal": "煤炭",
"iron": "铁",
"steel": "钢",
"silverOre": "银矿",
"silverIngot": "银锭",
"mithrilOre": "秘银矿",
"mithrilIngot": "秘银锭",
"bamboo": "竹子",
"fish": "鱼",
"mushroom": "蘑菇",
"berry": "浆果",
"chickenEgg": "鸡蛋",
"milk": "牛奶",
"salmon": "鲑鱼",
"tuna": "金枪鱼",
"honey": "蜂蜜",
"herb": "草药",
"wool": "羊毛",
"silk": "蚕丝",
"cashmere": "羊绒布料",
"silkFabric": "丝绸布料",
"axe": "斧头",
"pickaxe": "铁镐",
"baseHealSkillBook": "基础治疗技能书",
"sweepSkillBook": "横扫",
"collectRing": "采集戒指",
"collectRing2": "附魔采集戒指",
"catTailorClothes": "毛毛裁缝服",
"catTailorGloves": "毛毛裁缝手套",
"woolTailorClothes": "羊毛裁缝服",
"woolTailorGloves": "羊毛裁缝手套",
"goblinDaggerPlus": "哥布林匕首·改",
"wolfPeltArmor": "狼皮甲",
"skeletonShieldPlus": "骷髅盾·强化",
"trollClubPlus": "巨魔木棒·重型",
"scorpionStingerSpear": "巨蝎毒矛",
"guardianCoreAmulet": "守护者核心护符",
"moonlightGuardianCoreAmulet": "月光守护者",
"dragonScaleArmor": "龙鳞甲",
"woolCoat": "羊毛衣",
"woolHat": "羊毛帽",
"woolGloves": "羊毛手套",
"woolPants": "羊毛裤",
"ironCoat": "铁甲衣",
"ironHat": "铁头盔",
"ironGloves": "铁护手",
"ironPants": "铁护腿",
"steelCoat": "钢甲衣",
"steelHat": "钢头盔",
"steelGloves": "钢护手",
"steelPants": "钢护腿",
"silverSword": "银质剑",
"silverDagger": "银质匕首",
"silverCoat": "银护甲",
"silverHat": "银头盔",
"silverGloves": "银护手",
"silverPants": "银护腿",
"simpleSalad": "野草沙拉",
"wildFruitMix": "野果拼盘",
"fishSoup": "鱼汤",
"berryPie": "浆果派",
"mushroomStew": "蘑菇炖汤",
"catMint": "猫薄荷饼干",
"catSnack": "猫咪零食",
"luxuryCatFood": "豪华猫粮",
"sashimiPlatter": "鲜鱼刺身拼盘",
"catGiftBag": "猫猫礼袋",
"luckyCatBox": "幸运猫盒",
"mysteryCan": "神秘罐头",
"catnipSurprise": "猫薄荷惊喜包",
"meowEnergyBall": "喵能量球",
"dreamFeatherBag": "梦羽袋",
"woodSword": "木剑",
"ironSword": "铁剑",
"steelSword": "钢剑",
"catFurCoat": "毛毛衣",
"catFurHat": "毛毛帽",
"catFurGloves": "毛毛手套",
"catFurPants": "毛毛裤",
"collectingBracelet": "采集手环",
"fishingHat": "钓鱼帽",
"miningBelt": "采矿工作服",
"farmingGloves": "园艺手套",
"heavyMinerGloves": "重型矿工手套",
"agileGatherBoots": "灵巧采集靴",
"focusedFishingCap": "钓鱼专注帽",
"woodFishingRod": "木钓竿",
"chefHat": "厨师帽",
"ancientFishboneNecklace": "远古鱼骨项链",
"moonlightPendant": "月光吊坠",
"testResource": "测试资源",
"forestDagger": "冰霜匕首",
"snowWolfCloak": "雪狼皮披风",
"iceFeatherBoots": "冰羽靴",
"icePickaxe": "冰稿",
"woolBurqa": "羊毛罩袍",
"woolMageHat": "羊毛法师帽",
"woolMageLongGloves": "羊毛法师手套",
"woolMagePants": "羊毛法师裤",
"silkMageBurqa": "丝质罩袍",
"silkMageHat": "丝质法师帽",
"silkMageLongGloves": "丝质法师手套",
"silkMagePants": "丝质法师裤",
"woolTightsCloth": "羊毛紧身衣",
"woolDexHeadScarf": "羊毛裹头巾",
"woolDexGloves": "羊毛绑带手套",
"woolTightsPants": "羊毛紧身裤",
"silkTightsCloth": "丝质夜行衣",
"silkDexHeadScarf": "丝质裹头巾",
"silkDexGloves": "丝质绑带手套",
"silkTightsPants": "丝质宽松裤",
"woodStaff": "木法杖",
"ironDagger": "铁匕首",
"moonlightStaff": "月光法杖",
"mewShadowStaff": "喵影法杖",
"groupShieldSkillBook": "群体护盾技能书",
"silverNecklace": "银项链",
"silverBracelet": "银手链",
"catPotionSilverBracelet": "猫薄荷手链",
"catFurCuteHat": "毛毛可爱帽",
"woolCuteHat": "羊毛可爱帽",
"catPawStamp": "猫爪印章",
"rareCatfish": "稀有猫鱼",
"mysticalKoi": "神秘锦鲤",
"treasureMap": "藏宝图",
"catPawFossil": "猫爪化石",
"catStatue": "猫雕像",
"mysteriousBell": "神秘铃铛",
"ancientCatBowl": "古代猫碗",
"catScroll": "猫之卷轴",
"catAntiqueShard": "猫咪文物碎片",
"catHairball": "猫毛球",
"luckyCatCharm": "招财猫护符",
"catnipGem": "猫薄荷宝石",
"ancientFishBone": "远古鱼骨",
"whiskerFeather": "胡须羽毛",
"moonlightBell": "月光铃铛",
"shell": "贝壳",
"mysticalEssence": "神秘精华",
"catPotion": "猫薄荷药剂",
"magicScroll": "魔法卷轴",
"catRelic": "猫咪圣物",
"blessedBell": "祝福铃铛",
"slimeGel": "史莱姆凝胶",
"slimeCore": "史莱姆核心",
"goblinEar": "哥布林耳朵",
"goblinDagger": "哥布林匕首",
"batWing": "蝙蝠翅膀",
"batTooth": "蝙蝠牙",
"wolfFang": "狼牙",
"wolfPelt": "狼皮",
"skeletonBone": "骷髅骨",
"skeletonShield": "骷髅残盾",
"toxicSpore": "毒孢子",
"mushroomCap": "蘑菇怪帽",
"lizardScale": "蜥蜴鳞片",
"lizardTail": "蜥蜴尾巴",
"spiritEssence": "幽灵精华",
"ectoplasm": "灵质",
"trollHide": "巨魔兽皮",
"trollClub": "巨魔木棒",
"scorpionStinger": "巨蝎毒针",
"scorpionCarapace": "巨蝎甲壳",
"guardianCore": "守护者核心",
"ancientGear": "古代齿轮",
"lavaHeart": "熔岩之心",
"dragonScale": "龙鳞",
"venomDagger": "剧毒匕首",
"emberAegis": "余烬庇护",
"iceGel": "冰霜凝胶",
"frostCrystal": "霜之结晶",
"snowWolfFur": "雪狼皮",
"frostDagger": "冰霜匕首",
"iceBomb": "冰弹",
"iceBatWing": "冰蝙蝠翅膀",
"snowRabbitFur": "雪兔皮",
"frostEssence": "霜之精华",
"snowBeastFang": "巨兽獠牙",
"snowBeastHide": "巨兽皮",
"frostCrown": "霜之王冠",
"shadowFur": "暗影猫皮",
"catShadowGem": "猫影宝石",
"dungeonKey": "地牢钥匙",
"ironPawArmor": "铁爪护甲",
"phantomWhisker": "幻影胡须",
"curseWing": "诅咒之翼",
"golemCore": "猫偶核心",
"witchHat": "巫术猫帽",
"shadowOrb": "暗影法球",
"abyssalCloak": "深渊披风",
"ancestorCrown": "猫祖王冠",
"starEssence": "星辉精华",
"starShard": "星辰碎片",
"trapParts": "陷阱零件",
"starDust": "星尘",
"starCrown": "星辉王冠",
"starRelic": "星辉遗物",
"nightEyeGem": "夜瞳宝石",
"toxicFur": "剧毒皮毛",
"whiskerCharm": "胡须护符",
"shadowCape": "暗影披风",
"rareClaw": "稀有利爪",
"smokeBall": "烟雾弹",
"candyBomb": "糖果炸弹",
"plushFur": "毛绒绒",
"ghostEssence": "幽灵精华",
"loadOfamusementPark": "游乐园之王",
"paradeCape": "游行披风",
"empressCloak": "女皇披风"
// ...如有遗漏可继续补充
};
// 运行时间
const startTime = Date.now();
// 创建主面板
const panel = document.createElement('div');
panel.id = 'moyu-helper-panel';
panel.style.cssText = `
position: fixed;
top: 40px;
left: 40px;
min-width: 320px;
max-width: 90vw;
min-height: 60px;
background: rgba(30, 32, 40, 0.96);
color: #fff;
border-radius: 12px;
box-shadow: 0 4px 24px 0 rgba(0,0,0,0.25);
z-index: 99999;
font-size: 14px;
user-select: none;
transition: box-shadow 0.2s;
`;
// 标题栏
const titleBar = document.createElement('div');
titleBar.style.cssText = `
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 16px 8px 16px;
border-bottom: 1px solid #444;
cursor: move;
font-weight: bold;
background: transparent;
`;
titleBar.innerHTML = `
<span>摸鱼放置助手 v1.0</span>
<div style="display:flex;align-items:center;gap:12px;">
<span id="moyu-runtime" style="font-size:12px;color:#ffd700;"></span>
<button id="moyu-collapse" style="background:none;border:none;color:#fff;font-size:18px;cursor:pointer;">−</button>
</div>
`;
// 标签栏
const tabBar = document.createElement('div');
tabBar.style.cssText = `
display: flex;
gap: 2px;
padding: 0 10px;
border-bottom: 1px solid #333;
background: transparent;
`;
const tabs = [
{ key: 'combat', label: '战斗统计' },
{ key: 'inventory', label: '物品统计' }
];
let activeTab = 'combat';
const tabBtns = {};
tabs.forEach(tab => {
const btn = document.createElement('button');
btn.textContent = tab.label;
btn.style.cssText = `
flex: 1;
padding: 6px 0;
background: none;
border: none;
border-bottom: 2px solid transparent;
color: #fff;
font-size: 15px;
cursor: pointer;
transition: border-color 0.2s, color 0.2s;
`;
btn.addEventListener('click', () => setActiveTab(tab.key));
tabBar.appendChild(btn);
tabBtns[tab.key] = btn;
});
// 内容区
const content = document.createElement('div');
content.style.cssText = `
padding: 12px 16px 16px 16px;
min-height: 120px;
max-height: 60vh;
overflow-y: auto;
font-size: 14px;
`;
// 折叠逻辑
let isCollapsed = false;
const collapseBtn = titleBar.querySelector('#moyu-collapse');
collapseBtn.addEventListener('click', toggleCollapse);
function toggleCollapse() {
isCollapsed = !isCollapsed;
content.style.display = isCollapsed ? 'none' : '';
tabBar.style.display = isCollapsed ? 'none' : 'flex';
collapseBtn.textContent = isCollapsed ? '+' : '−';
}
// 运行时间刷新
function updateRuntime() {
const now = Date.now();
let diff = Math.floor((now - startTime) / 1000);
const h = Math.floor(diff / 3600);
diff %= 3600;
const m = Math.floor(diff / 60);
const s = diff % 60;
document.getElementById('moyu-runtime').textContent =
`已运行: ${h > 0 ? h + '小时' : ''}${m > 0 ? m + '分' : ''}${s}秒`;
}
setInterval(updateRuntime, 1000);
setTimeout(updateRuntime, 100);
// 拖动逻辑(仅标题栏可拖动)
(function makeDraggable(handle, el) {
let isDown = false, offsetX = 0, offsetY = 0;
handle.addEventListener('mousedown', e => {
isDown = true;
el.style.right = 'auto';
offsetX = e.clientX - el.offsetLeft;
offsetY = e.clientY - el.offsetTop;
e.preventDefault();
});
document.addEventListener('mousemove', e => {
if (!isDown) return;
el.style.left = (e.clientX - offsetX) + 'px';
el.style.top = (e.clientY - offsetY) + 'px';
});
document.addEventListener('mouseup', () => { isDown = false; });
})(titleBar, panel);
// 标签切换逻辑
function setActiveTab(key) {
activeTab = key;
for (const k in tabBtns) {
tabBtns[k].style.borderBottomColor = (k === key) ? '#ffd700' : 'transparent';
tabBtns[k].style.color = (k === key) ? '#ffd700' : '#fff';
}
renderContent();
// 切换后刷新对应数据
if (activeTab === 'combat') {
setTimeout(() => {
// 需要有 playerUuids 和 memberMap 数据
if (typeof updateDpsPanel === 'function' && window.playerUuids && window.memberMap) {
updateDpsPanel(window.playerUuids, window.memberMap);
}
}, 0);
} else if (activeTab === 'inventory') {
setTimeout(() => {
if (typeof updateInventoryPanel === 'function') {
updateInventoryPanel();
}
}, 0);
}
}
// 内容渲染
function renderContent() {
if (activeTab === 'combat') {
content.innerHTML = renderCombatPanel();
} else if (activeTab === 'inventory') {
content.innerHTML = renderInventoryPanel();
}
}
// 战斗统计内容(可根据实际数据结构填充)
function renderCombatPanel() {
return `
<table style="width:100%;border-collapse:collapse;font-size:13px;">
<thead>
<tr style="color:#ffd700;">
<th style="text-align:left;">玩家</th>
<th>输出效率</th>
<th>治疗效率</th>
<th>总伤害</th>
<th>总治疗</th>
</tr>
</thead>
<tbody id="dpsTableBody">
<!-- 动态填充 -->
</tbody>
</table>
<div style="margin-top:12px;">
<div style="font-weight:bold;margin-bottom:4px;">战斗日志</div>
<ul id="battleLog" style="max-height:120px;overflow-y:auto;padding-left:18px;font-size:13px;"></ul>
</div>
`;
}
// 物品统计内容(可根据实际数据结构填充)
function renderInventoryPanel() {
return `
<table style="width:100%;border-collapse:collapse;font-size:13px;">
<thead>
<tr style="color:#ffd700;">
<th style="text-align:left;">物品</th>
<th>数量</th>
</tr>
</thead>
<tbody id="inventoryTableBody">
<!-- 动态填充 -->
</tbody>
</table>
`;
}
// 组装面板
panel.appendChild(titleBar);
panel.appendChild(tabBar);
panel.appendChild(content);
document.body.appendChild(panel);
// 初始化
setActiveTab('combat');
// 你可以在此处挂载实际的统计数据渲染逻辑
// 例如:动态填充dpsTableBody、battleLog、inventoryTableBody等
// 判断压缩格式
function detectCompression(buf) {
const b = new Uint8Array(buf);
if (b.length >= 2) {
if (b[0] === 0x1f && b[1] === 0x8b) return 'gzip';
if (b[0] === 0x78 && (((b[0] << 8) | b[1]) % 31) === 0) return 'zlib';
}
return 'deflate';
}
// WebSocket 拦截与解压
const NativeWS = window.WebSocket;
window.WebSocket = function (url, protocols) {
const ws = protocols ? new NativeWS(url, protocols) : new NativeWS(url);
const originalSend = ws.send;
ws.send = function (data) {
console.log('[WS 发送]', data);
return originalSend.call(this, data);
};
let messageID = 0;
let lastCmd = null;
ws.addEventListener('message', ev => {
messageID++;
let obj = null;
let text = null;
if (ev.data instanceof ArrayBuffer) {
const format = detectCompression(ev.data);
switch (format) {
case 'gzip':
text = pako.ungzip(new Uint8Array(ev.data), { to: 'string' });
break;
case 'zlib':
text = pako.inflate(new Uint8Array(ev.data), { to: 'string' });
break;
default:
text = pako.inflateRaw(new Uint8Array(ev.data), { to: 'string' });
}
obj = JSON.parse(text);
} else {
text = ev.data
if (typeof text === 'string' && /^\d+-/.test(text)) {
const idx = text.indexOf('-');
if (idx !== -1) {
try {
const arr = JSON.parse(text.slice(idx + 1));
if (Array.isArray(arr) && typeof arr[0] === 'string') {
lastCmd = arr[0];
}
} catch (e) {
lastCmd = null;
console.log(e)
}
}
return;
}
console.log(`[WS 未处理数据]`, text);
}
// 根据 lastCmd 分发处理
if (obj && lastCmd) {
if (lastCmd.startsWith('inventory:increase:success')) {
updateInventory(obj.data);
} else if (lastCmd.startsWith('battle:fullInfo:success')) {
// battle:fullInfo:success、battle:round:success等
obj = obj.data
if (obj && obj.battleInfo && obj.thisRoundAction) {
const battle = obj.battleInfo;
const action = obj.thisRoundAction;
const members = battle.members || [];
const playerUuids = (battle.groups?.player) || [];
const memberMap = Object.fromEntries(
members.map(m => [m.uuid, m])
);
const srcUuid = action.sourceUnitUuid;
const dmgObj = action.damage || {}; // { uuid: amount, … }
const healObj = action.heal || {}; // { uuid: amount, … }
const skill = action.castSkillId || '未知技能';
const targets = action.targetUnitUuidList || [];
// 只统计玩家组
if (playerUuids.includes(srcUuid)) {
let hasAction = false;
for (const [tUuid, amt] of Object.entries(dmgObj)) {
damageAccum.set(srcUuid, (damageAccum.get(srcUuid) || 0) + amt);
hasAction = true;
}
for (const [tUuid, amt] of Object.entries(healObj)) {
healAccum.set(srcUuid, (healAccum.get(srcUuid) || 0) + amt);
hasAction = true;
}
if (hasAction) {
actionCount.set(srcUuid, (actionCount.get(srcUuid) || 0) + 1);
}
window.playerUuids = playerUuids;
window.memberMap = memberMap;
updateDpsPanel(playerUuids, memberMap);
}
// 战斗日志输出
if (combatLogEnabled) {
let logLines = [];
if (playerUuids.includes(srcUuid) || targets.some(t => playerUuids.includes(t))) {
const srcName = memberMap[srcUuid]?.name || srcUuid;
for (const tUuid of targets) {
const tName = memberMap[tUuid]?.name || tUuid;
const dmg = dmgObj[tUuid] || 0;
const heal = healObj[tUuid] || 0;
const skillName = skillNames[skill] || skill;
if (dmg > 0) logLines.push(`🗡️ <b>${srcName}</b> 用 <b>${skillName}</b> 对 <b>${tName}</b> 造成 <span style='color:#ff7675;'>${dmg}</span> 伤害`);
if (heal > 0) logLines.push(`💚 <b>${srcName}</b> 用 <b>${skillName}</b> 治疗 <b>${tName}</b> <span style='color:#00e676;'>${heal}</span> 生命`);
}
}
if (logLines.length) addBattleLog(logLines);
}
}
} else {
console.log(`[WS 未处理指令]`, lastCmd);
console.log(`[WS 未处理对象]`, obj);
}
// 其他指令可在此扩展
lastCmd = null;
} else {
// 没有指令时,打印日志
}
});
return ws;
};
window.WebSocket.prototype = NativeWS.prototype;
Object.getOwnPropertyNames(NativeWS).forEach(prop => {
if (!(prop in window.WebSocket)) {
window.WebSocket[prop] = NativeWS[prop];
}
});
console.log('✅ MoYuIdleHelper 已启动');
// 新增:DPS面板渲染函数
function updateDpsPanel(playerUuids, memberMap) {
const tbody = document.querySelector('#dpsTableBody');
if (!tbody) return;
if (!playerUuids || !memberMap || playerUuids.length === 0) {
tbody.innerHTML = `<tr><td colspan="5" style="text-align:center;color:#888;">暂无数据</td></tr>`;
return;
}
// 收集玩家数据
const rows = playerUuids.map(uuid => {
const total = damageAccum.get(uuid) || 0;
const heal = healAccum.get(uuid) || 0;
const cnt = actionCount.get(uuid) || 1;
const name = memberMap[uuid]?.name || uuid;
const dps = Math.round(total / cnt); // 输出效率
const hps = Math.round(heal / cnt); // 治疗效率
return { name, dps, hps, total, heal };
});
// 按输出效率降序
rows.sort((a, b) => b.dps - a.dps);
tbody.innerHTML = rows.map(r => `<tr><td>${r.name}</td><td style='text-align:right;'>${r.dps}</td><td style='text-align:right;'>${r.hps}</td><td style='text-align:right;'>${r.total}</td><td style='text-align:right;'>${r.heal}</td></tr>`).join('');
}
// 新增:战斗日志管理
const logList = [];
// 日志自动滚动控制
let logAutoScroll = true;
// 日志面板增加滚动条美化和事件监听
const logPanelCss = document.createElement('style');
logPanelCss.innerHTML = `
#logPanel::-webkit-scrollbar {
width: 8px;
background: transparent;
}
#logPanel::-webkit-scrollbar-thumb {
background: linear-gradient(120deg, #444 30%, #888 100%);
border-radius: 6px;
}
#logPanel:hover::-webkit-scrollbar-thumb {
background: linear-gradient(120deg, #666 30%, #aaa 100%);
}
#logPanel {
scrollbar-width: thin;
scrollbar-color: #888 #222;
}
`;
document.head.appendChild(logPanelCss);
setTimeout(() => {
const logPanel = document.getElementById('battleLog');
if (logPanel) {
logPanel.addEventListener('scroll', function () {
// 判断是否在底部
const atBottom = logPanel.scrollTop + logPanel.clientHeight >= logPanel.scrollHeight - 2;
logAutoScroll = atBottom;
});
logPanel.addEventListener('mouseenter', () => { logAutoScroll = false; });
logPanel.addEventListener('mouseleave', () => {
// 如果离开时已在底部,则恢复自动滚动
const atBottom = logPanel.scrollTop + logPanel.clientHeight >= logPanel.scrollHeight - 2;
logAutoScroll = atBottom;
});
}
}, 500);
function addBattleLog(lines) {
const ul = document.getElementById('battleLog');
if (!ul) return;
for (const line of lines) {
logList.push(line);
}
// 限制日志条数
while (logList.length > 200) logList.shift();
ul.innerHTML = logList.map(l => `<li style='margin-bottom:2px;'>${l}</li>`).join('');
// 自动滚动到底部(仅在自动滚动开启时)
const logPanel = document.getElementById('battleLog');
if (logPanel && logAutoScroll) {
logPanel.scrollTop = logPanel.scrollHeight;
}
}
// 更新物品变动统计
function updateInventory(newInventory) {
for (const [itemId, itemData] of Object.entries(newInventory)) {
const oldCount = inventory[itemId] || 0;
const newCount = itemData.count;
inventory[itemId] = oldCount + newCount;
}
updateInventoryPanel();
}
// 更新物品变动面板
function updateInventoryPanel() {
const tbody = document.querySelector('#inventoryTableBody');
if (!tbody) return;
tbody.innerHTML = '';
const sortedItems = Object.entries(inventory)
.filter(([itemId, total]) => total > 0)
.sort((a, b) => b[1] - a[1]);
for (const [itemId, total] of sortedItems) {
const row = document.createElement('tr');
row.innerHTML = `
<td>${itemIdNameMap[itemId] || itemId}</td>
<td style="text-align:right;">${total}</td>
`;
tbody.appendChild(row);
}
}
})();