// ==UserScript==
// @name Infinite Challenge
// @namespace http://tampermonkey.net/
// @version 1.0
// @description 自动挑战选定的副本,智能检测战斗结束并自动返回,次数为零后自动停止。
// @author Lunaris
// @match https://aring.cc/awakening-of-war-soul-ol/
// @icon https://aring.cc/awakening-of-war-soul-ol/favicon.ico
// @grant none
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// 副本配置
const dungeons = {
'装备挑战': {
selector: '.daily-challenge .legend',
needConfirm: false,
autoReturn: true,
buttonText: '开始'
},
'金币挑战': {
selector: '.daily-challenge .gold',
needConfirm: false,
autoReturn: true,
buttonText: '开始'
},
'钻石挑战': {
selector: '.daily-challenge .diamond',
needConfirm: false,
autoReturn: true,
buttonText: '开始'
},
'不朽之塔': {
selector: '.eternal-tower .antiquity',
needConfirm: true,
autoReturn: false,
buttonText: '进入'
},
'符石尖塔': {
selector: '.rune-tower .awaken',
needConfirm: true,
autoReturn: false,
buttonText: '进入',
filter: (el) => el.textContent.trim() === '符石尖塔'
},
'符石神殿': {
selector: '.rune-tower .awaken',
needConfirm: true,
autoReturn: false,
buttonText: '开始战斗',
filter: (el) => el.textContent.trim() === '符石神殿'
},
'无尽之塔': {
selector: '.endless-tower .darkGold',
needConfirm: true,
autoReturn: false,
buttonText: '进入'
}
};
let isRunning = false;
let selectedDungeon = null;
let battleState = 'idle';
let lastHp = null;
let battleCheckCount = 0;
let statusDisplay = null;
let isMinimized = false;
// 创建控制面板
function createControlPanel() {
const panel = document.createElement('div');
panel.id = 'dungeon-auto-panel';
panel.style.cssText = `
position: fixed;
top: 60px;
right: 10px;
background: rgba(0, 0, 0, 0.9);
color: white;
border-radius: 6px;
font-family: Arial, sans-serif;
font-size: 12px;
z-index: 10000;
width: 150px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
border: 1px solid #333;
cursor: move;
`;
// 标题栏
const header = document.createElement('div');
header.id = 'dungeon-header';
header.style.cssText = `
background: linear-gradient(90deg, #2c3e50, #34495e);
padding: 6px 8px;
border-radius: 5px 5px 0 0;
font-weight: bold;
font-size: 11px;
color: #4CAF50;
display: flex;
justify-content: space-between;
align-items: center;
cursor: move;
`;
header.innerHTML = `
<span id="header-text">副本自动挑战</span>
<div>
<button id="minimize-btn" style="
background: none;
border: none;
color: #fff;
cursor: pointer;
font-size: 14px;
padding: 0 4px;
margin-left: 4px;
" title="最小化">−</button>
<button id="close-btn" style="
background: none;
border: none;
color: #fff;
cursor: pointer;
font-size: 14px;
padding: 0 4px;
margin-left: 4px;
" title="关闭">×</button>
</div>
`;
// 内容区域
const content = document.createElement('div');
content.id = 'dungeon-content';
content.style.cssText = `
padding: 10px;
font-size: 11px;
line-height: 1.4;
`;
content.innerHTML = `
<div style="margin-bottom: 8px;">
<select id="dungeon-select" style="width: 100%; padding: 4px; background: #2c3e50; color: white; border: 1px solid #444; border-radius: 3px; font-size: 11px;">
<option value="">请选择副本</option>
${Object.keys(dungeons).map(name => `<option value="${name}">${name}</option>`).join('')}
</select>
</div>
<div id="dungeon-info" style="margin-bottom: 8px; color: #17A2B8; font-size: 10px;"></div>
<div style="margin-bottom: 8px;">
<button id="dungeon-start" style="width: 100%; padding: 6px; background: #28A745; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 11px;">
开始挑战
</button>
<button id="dungeon-stop" style="width: 100%; padding: 6px; background: #DC3545; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 11px; display: none;">
停止挑战
</button>
</div>
<div id="dungeon-status" style="color: #6C757D; font-size: 10px; line-height: 1.3;"></div>
`;
panel.appendChild(header);
panel.appendChild(content);
document.body.appendChild(panel);
// 添加拖拽功能
let startX, startY, panelX, panelY, isDragging = false;
header.addEventListener('mousedown', (e) => {
if (e.target.id === 'minimize-btn' || e.target.id === 'close-btn') return;
isDragging = true;
startX = e.clientX;
startY = e.clientY;
const rect = panel.getBoundingClientRect();
panelX = rect.left;
panelY = rect.top;
document.addEventListener('mousemove', handleDrag);
document.addEventListener('mouseup', stopDrag);
e.preventDefault();
});
function handleDrag(e) {
if (!isDragging) return;
const deltaX = e.clientX - startX;
const deltaY = e.clientY - startY;
const newX = Math.max(0, Math.min(window.innerWidth - panel.offsetWidth, panelX + deltaX));
const newY = Math.max(0, Math.min(window.innerHeight - panel.offsetHeight, panelY + deltaY));
panel.style.left = newX + 'px';
panel.style.top = newY + 'px';
panel.style.right = 'auto';
}
function stopDrag() {
isDragging = false;
document.removeEventListener('mousemove', handleDrag);
document.removeEventListener('mouseup', stopDrag);
}
// 最小化功能
const minimizeBtn = header.querySelector('#minimize-btn');
minimizeBtn.addEventListener('click', (e) => {
e.stopPropagation();
toggleMinimize();
});
// 关闭功能
const closeBtn = header.querySelector('#close-btn');
closeBtn.addEventListener('click', (e) => {
e.stopPropagation();
if (isRunning) {
if (confirm('挑战正在进行中,确定要关闭吗?')) {
stopAuto();
panel.style.display = 'none';
}
} else {
panel.style.display = 'none';
}
});
// 绑定事件
content.querySelector('#dungeon-select').addEventListener('change', updateDungeonInfo);
content.querySelector('#dungeon-start').addEventListener('click', startAuto);
content.querySelector('#dungeon-stop').addEventListener('click', stopAuto);
return panel;
}
// 切换最小化状态
function toggleMinimize() {
const content = statusDisplay.querySelector('#dungeon-content');
const minimizeBtn = statusDisplay.querySelector('#minimize-btn');
const headerText = statusDisplay.querySelector('#header-text');
isMinimized = !isMinimized;
if (isMinimized) {
content.style.display = 'none';
statusDisplay.style.width = 'auto';
statusDisplay.style.minWidth = '120px';
minimizeBtn.textContent = '+';
minimizeBtn.title = '展开';
// 更新最小化标题
if (isRunning) {
headerText.textContent = `挑战中...`;
} else {
headerText.textContent = '副本挑战';
}
} else {
content.style.display = 'block';
statusDisplay.style.width = '120px';
statusDisplay.style.minWidth = '120px';
minimizeBtn.textContent = '−';
minimizeBtn.title = '最小化';
headerText.textContent = '副本自动挑战';
}
}
// 更新副本信息
function updateDungeonInfo() {
const select = document.getElementById('dungeon-select');
const info = document.getElementById('dungeon-info');
selectedDungeon = select.value;
if (!selectedDungeon) {
info.textContent = '';
return;
}
const remainingTimes = getRemainingTimes(selectedDungeon);
if (remainingTimes !== null) {
info.textContent = `剩余次数: ${remainingTimes}`;
} else {
info.textContent = '无法获取次数信息';
}
}
// 获取剩余次数
function getRemainingTimes(dungeonName) {
const config = dungeons[dungeonName];
const elements = document.querySelectorAll(config.selector);
let targetElement = null;
if (config.filter) {
for (let el of elements) {
if (config.filter(el)) {
targetElement = el;
break;
}
}
} else {
targetElement = elements[0];
}
if (!targetElement) return null;
const container = targetElement.closest('.border-wrap');
if (!container) return null;
const text = container.textContent;
const match = text.match(/(?:剩余)?次数[::]\s*(\d+)/);
return match ? parseInt(match[1]) : null;
}
// 查找并点击按钮
function clickDungeonButton(dungeonName) {
const config = dungeons[dungeonName];
const elements = document.querySelectorAll(config.selector);
let targetElement = null;
if (config.filter) {
for (let el of elements) {
if (config.filter(el)) {
targetElement = el;
break;
}
}
} else {
targetElement = elements[0];
}
if (!targetElement) return false;
const container = targetElement.closest('.border-wrap');
if (!container) return false;
const buttons = container.querySelectorAll('button');
for (let btn of buttons) {
if (btn.textContent.includes(config.buttonText)) {
btn.click();
return true;
}
}
return false;
}
// 点击确定按钮
function clickConfirmButton() {
const buttons = document.querySelectorAll('button.el-button--primary.is-plain');
for (let btn of buttons) {
if (btn.textContent.includes('确定')) {
btn.click();
return true;
}
}
return false;
}
// 点击返回按钮
function clickReturnButton() {
const buttons = document.querySelectorAll('button.el-button--success.is-plain.main');
for (let btn of buttons) {
if (btn.textContent.includes('返回') && btn.style.display !== 'none') {
btn.click();
return true;
}
}
return false;
}
// 获取战斗数据
function getBattleData() {
const personFight = document.querySelector('.person-fight');
const teamFight = document.querySelector('.team-fight');
let battleData = null;
// 优先检查个人战斗,然后检查团队战斗
const fightElement = personFight || teamFight;
if (fightElement) {
const hpElement = fightElement.querySelector('.el-progress-bar__innerText span');
const timeElement = fightElement.querySelector('.fight-over-timer');
if (hpElement && timeElement) {
const hpText = hpElement.textContent.trim().replace('%', '').replace(/\s/g, '');
const timeText = timeElement.textContent.trim();
// 打印调试信息
console.log('[副本挑战] 血量文本:', hpElement.textContent, '=> 解析后:', hpText);
console.log('[副本挑战] 时间文本:', timeText);
battleData = {
hp: hpText,
time: timeText
};
} else {
console.log('[副本挑战] 未找到血量或时间元素', {
hpElement: !!hpElement,
timeElement: !!timeElement,
fightType: personFight ? 'person' : 'team'
});
}
} else {
console.log('[副本挑战] 未找到战斗界面');
}
return battleData;
}
// 检测战斗是否活跃
function isBattleActive(battleData) {
if (!battleData || !battleData.hp || !battleData.time) {
console.log('[副本挑战] 战斗不活跃 - 数据缺失:', battleData);
return false;
}
// 如果时间为00:00或血量为0%,认为战斗未活跃
const timeSeconds = parseTimeToSeconds(battleData.time);
const hpPercent = parseFloat(battleData.hp);
console.log('[副本挑战] 战斗状态检查:', {
time: battleData.time,
timeSeconds,
hp: battleData.hp,
hpPercent,
isActive: timeSeconds > 0 && hpPercent > 0
});
if (timeSeconds === 0 || hpPercent === 0 || isNaN(hpPercent)) {
return false;
}
return true;
}
// 解析时间字符串为秒数
function parseTimeToSeconds(timeStr) {
const parts = timeStr.split(' : ');
if (parts.length === 2) {
return parseInt(parts[0]) * 60 + parseInt(parts[1]);
}
return 0;
}
// 检查战斗是否结束
function isBattleFinished() {
const battleData = getBattleData();
if (!battleData) {
console.log('[副本挑战] 战斗已结束 - 无战斗数据');
return true;
}
const hpPercent = parseFloat(battleData.hp);
// 血量为0表示战斗结束
if (hpPercent === 0) {
console.log('[副本挑战] 战斗已结束 - 血量为0');
return true;
}
// 检查是否有"挑战成功"或"挑战失败"文本
const fightInfos = document.querySelectorAll('.fight-res-info');
for (let info of fightInfos) {
const text = info.textContent;
if (text.includes('挑战成功') || text.includes('挑战失败')) {
console.log('[副本挑战] 战斗已结束 - 发现结束文本:', text.includes('挑战成功') ? '挑战成功' : '挑战失败');
return true;
}
}
console.log('[副本挑战] 战斗进行中 - 血量:', hpPercent.toFixed(1) + '%');
return false;
}
// 开始自动挑战
function startAuto() {
if (!selectedDungeon) {
alert('请先选择副本!');
return;
}
const remainingTimes = getRemainingTimes(selectedDungeon);
if (remainingTimes === null) {
alert('无法获取副本信息,请确认页面已加载完成!');
return;
}
if (remainingTimes <= 0) {
alert('该副本剩余次数为0!');
return;
}
isRunning = true;
battleState = 'idle';
lastHp = null;
battleCheckCount = 0;
document.getElementById('dungeon-start').style.display = 'none';
document.getElementById('dungeon-stop').style.display = 'block';
document.getElementById('dungeon-select').disabled = true;
updateStatus('开始挑战...');
runAutoChallenge();
}
// 停止自动挑战
function stopAuto() {
isRunning = false;
battleState = 'idle';
document.getElementById('dungeon-start').style.display = 'block';
document.getElementById('dungeon-stop').style.display = 'none';
document.getElementById('dungeon-select').disabled = false;
updateStatus('已停止');
}
// 更新状态
function updateStatus(msg) {
const statusEl = document.getElementById('dungeon-status');
if (statusEl) {
statusEl.innerHTML = msg;
}
}
// 执行自动挑战
function runAutoChallenge() {
if (!isRunning) return;
const remainingTimes = getRemainingTimes(selectedDungeon);
updateDungeonInfo();
if (remainingTimes === null || remainingTimes <= 0) {
updateStatus('<div style="color: #28A745;">挑战完成!</div>');
stopAuto();
return;
}
const config = dungeons[selectedDungeon];
const battleData = getBattleData();
// 状态机处理
if (battleState === 'idle') {
// 空闲状态,开始新的挑战
updateStatus(`<div>正在挑战...</div><div style="color: #FFC107;">剩余次数: ${remainingTimes}</div>`);
if (clickDungeonButton(selectedDungeon)) {
battleState = config.needConfirm ? 'confirming' : 'fighting';
battleCheckCount = 0;
setTimeout(runAutoChallenge, 500);
} else {
setTimeout(runAutoChallenge, 1000);
}
}
else if (battleState === 'confirming') {
// 等待确认
if (clickConfirmButton()) {
battleState = 'fighting';
battleCheckCount = 0;
updateStatus('<div style="color: #17A2B8;">战斗中...</div>');
}
setTimeout(runAutoChallenge, 500);
}
else if (battleState === 'fighting') {
// 战斗中,监测战斗状态
if (battleData && isBattleActive(battleData)) {
const hpPercent = parseFloat(battleData.hp);
// 更新最后血量
lastHp = hpPercent;
// 检查战斗是否结束
if (isBattleFinished()) {
console.log('[副本挑战] 检测到战斗结束,等待返回');
updateStatus('<div style="color: #28A745;">战斗结束,准备返回...</div>');
if (config.autoReturn) {
// 自动返回的副本,等待更长时间确保返回完成
battleState = 'idle';
setTimeout(runAutoChallenge, 3000);
} else {
// 需要手动返回的副本
battleState = 'waiting_return';
setTimeout(runAutoChallenge, 1500);
}
} else {
// 战斗还在继续
updateStatus(`<div style="color: #17A2B8;">战斗中...</div><div style="color: #DC3545;">怪物血量: ${hpPercent.toFixed(1)}%</div>`);
setTimeout(runAutoChallenge, 1000);
}
} else {
// 战斗界面消失或不活跃
console.log('[副本挑战] 战斗界面消失,检查次数:', battleCheckCount);
battleCheckCount++;
if (battleCheckCount > 5) {
// 确认战斗已结束,重置状态
console.log('[副本挑战] 确认战斗已结束,返回idle状态');
battleState = 'idle';
battleCheckCount = 0;
setTimeout(runAutoChallenge, 2000);
} else {
setTimeout(runAutoChallenge, 500);
}
}
}
else if (battleState === 'waiting_return') {
// 等待返回
if (clickReturnButton()) {
updateStatus('<div style="color: #28A745;">已返回,准备下一次挑战...</div>');
battleState = 'idle';
setTimeout(runAutoChallenge, 2000);
} else {
// 检查是否战斗界面已消失(可能已自动返回)
if (!battleData || !isBattleActive(battleData)) {
battleCheckCount++;
if (battleCheckCount > 3) {
battleState = 'idle';
setTimeout(runAutoChallenge, 1000);
} else {
setTimeout(runAutoChallenge, 500);
}
} else {
battleCheckCount = 0;
setTimeout(runAutoChallenge, 500);
}
}
}
}
// 初始化
console.log('副本自动挑战脚本已启动');
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
setTimeout(() => {
statusDisplay = createControlPanel();
}, 1000);
});
} else {
setTimeout(() => {
statusDisplay = createControlPanel();
}, 1000);
}
})();