// ==UserScript==
// @name 工匠放置小工具之1:事件提醒
// @namespace http://tampermonkey.net/
// @version 1.07
// @description 工匠提醒 + 下一个事件
// @author Stella
// @match https://idleartisan.com/*
// @grant GM_getValue
// @grant GM_setValue
// @license CC-BY-NC-SA-4.0
// ==/UserScript==
(function() {
'use strict';
const COOLDOWN = 30 * 1000; // 30秒全局冷却
let cooldownUntil = 0;
// 默认配置
const defaultConfig = {
CB: true, // Crafting Bonus
Siege: true, // Siege
TS: true, // Trade Ship
M: true, // Merchant
GLOBAL: true, // 全局开关
IDLE: false // 摸鱼模式
};
const config = {
CB: GM_getValue('CB', defaultConfig.CB),
Siege: GM_getValue('Siege', defaultConfig.Siege),
TS: GM_getValue('TS', defaultConfig.TS),
M: GM_getValue('M', defaultConfig.M),
GLOBAL: GM_getValue('GLOBAL', defaultConfig.GLOBAL),
IDLE: GM_getValue('IDLE', defaultConfig.IDLE)
};
// ========== 中英文事件映射表 ==========
const eventDict = {
"Mining Bonus": "采矿加成",
"Woodcutting Bonus": "伐木加成",
"Thief": "盗贼",
"Battling Bonus": "战斗加成",
"Crafting Bonus": "制作加成",
"Merchant": "商人",
"Purchasing Agent": "采购代理",
"Tax Season": "税收季节",
"Distant war drums": "遥远的战鼓",
"Goblin Siege": "哥布林围攻",
"Boss Fight": "Boss对抗",
"Ancient Treant": "远古树人",
"Runic Golem": "符文魔像",
"Trade ship": "贸易船"
};
// 事件顺序(英文作为基准)
const eventOrder = [
"Mining Bonus",
"Woodcutting Bonus",
"Thief",
"Battling Bonus",
"Crafting Bonus",
"Merchant",
"Purchasing Agent",
"Tax Season",
"Distant war drums",
"Goblin Siege",
"Boss Fight",
"Ancient Treant",
"Runic Golem",
"Trade ship"
];
// 判断当前语言模式 (en / zh)
function getLangMode(currentText) {
const asciiOnly = /^[\x00-\x7F]*$/; // 纯ASCII认为是英文
return asciiOnly.test(currentText) ? "en" : "zh";
}
// 根据语言返回正确事件名
function getEventName(enName, lang) {
if (lang === "en") return enName;
return eventDict[enName] || enName;
}
// ========== UI面板 ==========
const panel = document.createElement('div');
panel.style.position = 'fixed';
panel.style.bottom = '20px';
panel.style.right = '20px';
panel.style.background = 'rgba(0,0,0,0.8)';
panel.style.color = 'white';
panel.style.padding = '12px';
panel.style.borderRadius = '10px';
panel.style.zIndex = 9999;
panel.style.fontFamily = 'sans-serif';
panel.style.fontSize = '14px';
panel.style.boxShadow = '0 4px 12px rgba(0,0,0,0.5)';
function createSwitch(labelText, key) {
const container = document.createElement('div');
container.style.marginBottom = '8px';
container.style.display = 'flex';
container.style.alignItems = 'center';
container.style.justifyContent = 'space-between';
const label = document.createElement('span');
label.textContent = labelText;
const switchContainer = document.createElement('div');
switchContainer.style.width = '50px';
switchContainer.style.height = '24px';
switchContainer.style.background = config[key] ? '#4caf50' : '#ccc';
switchContainer.style.borderRadius = '12px';
switchContainer.style.position = 'relative';
switchContainer.style.cursor = 'pointer';
switchContainer.style.transition = 'background 0.3s';
const knob = document.createElement('div');
knob.style.width = '20px';
knob.style.height = '20px';
knob.style.background = '#fff';
knob.style.borderRadius = '50%';
knob.style.position = 'absolute';
knob.style.top = '2px';
knob.style.left = config[key] ? '28px' : '2px';
knob.style.transition = 'left 0.3s';
switchContainer.appendChild(knob);
switchContainer.addEventListener('click', () => {
config[key] = !config[key];
GM_setValue(key, config[key]);
switchContainer.style.background = config[key] ? '#4caf50' : '#ccc';
knob.style.left = config[key] ? '28px' : '2px';
});
container.appendChild(label);
container.appendChild(switchContainer);
panel.appendChild(container);
}
// 开关顺序
createSwitch('制作提醒', 'CB');
createSwitch('围攻提醒', 'Siege');
createSwitch('商船提醒', 'TS');
createSwitch('商人提醒', 'M');
panel.appendChild(document.createElement('hr')); // 分隔线
createSwitch('全局开关', 'GLOBAL');
createSwitch('摸鱼模式', 'IDLE');
document.body.appendChild(panel);
// 请求通知权限
if (Notification.permission !== "granted") {
Notification.requestPermission();
}
// 定时检测提醒
setInterval(() => {
if (!config.GLOBAL) return;
const now = Date.now();
if (now < cooldownUntil) return;
const title = document.title.trim();
let notified = false;
// 检测语言模式
const langMode = getLangMode(title);
const notify = (msg) => {
if (config.IDLE) msg = langMode === "en" ? "Windows Update Reminder" : "Windows 更新提醒";
new Notification(config.IDLE ? (langMode === "en" ? "Windows Update" : "Windows 更新") : "Idle Artisan", {
body: msg,
icon: "https://idleartisan.com/favicon.ico"
});
cooldownUntil = now + COOLDOWN;
notified = true;
};
if (config.CB && (title.includes("CB") || title.includes("制作") || title.includes("Crafting"))) {
notify(langMode === "en" ? "Crafting Bonus!" : "制作加成来了!");
} else if (config.Siege && (title.includes("Siege") || title.includes("围攻"))) {
notify(langMode === "en" ? "Prepare for Siege!" : "准备 BOSS 战斗!");
} else if (config.TS && (title.includes("TS") || title.includes("商船") || title.includes("Trade ship"))) {
notify(langMode === "en" ? "Trade Ship arrived!" : "商船来了!");
} else if (config.M && (title === "Idle Artisan - M" || title.includes("商人") || title.includes("Merchant"))) {
notify(langMode === "en" ? "Merchant arrived!" : "商人来了!");
}
}, 10000);
// ========= 新增功能:下一个事件显示 =========
const nextEventLabel = document.createElement('div');
nextEventLabel.style.marginLeft = "15px";
nextEventLabel.style.color = "#ff4d4d"; // 或 "#ff0000"
nextEventLabel.style.fontWeight = "bold";
nextEventLabel.style.fontSize = "14px";
nextEventLabel.textContent = "Next Event: ...";
const eventWrapper = document.getElementById("event-wrapper");
if (eventWrapper && eventWrapper.parentNode) {
eventWrapper.parentNode.insertBefore(nextEventLabel, eventWrapper.nextSibling);
}
// 定时更新下一个事件(2秒一次)
setInterval(() => {
const currentNameElem = document.getElementById("event-name");
if (!currentNameElem) return;
const currentEventRaw = currentNameElem.textContent.trim();
const langMode = getLangMode(currentEventRaw);
// 找到英文事件基准
let currentEn = Object.keys(eventDict).find(en => en === currentEventRaw || eventDict[en] === currentEventRaw);
if (!currentEn) return;
const idx = eventOrder.findIndex(e => e === currentEn);
if (idx >= 0) {
const nextEventEn = eventOrder[(idx + 1) % eventOrder.length];
const nextEventName = getEventName(nextEventEn, langMode);
nextEventLabel.textContent = langMode === "en" ? "Next Event: " + nextEventName : "下一个事件: " + nextEventName;
} else {
nextEventLabel.textContent = langMode === "en" ? "Next Event: Unknown" : "下一个事件: 未知";
}
}, 2000);
})();