// ==UserScript==
// @name 智龙迷城战友网增强
// @namespace http://www.mapaler.com/
// @version 2.5.5
// @description 地下城增加技能图标
// @author Mapaler <mapaler@163.com>
// @copyright 2019+, Mapaler <mapaler@163.com>
// @icon https://pad.skyozora.com/images/egg.ico
// @match *://pad.skyozora.com/*
// @require https://unpkg.com/opencc-js@1.0.5/dist/umd/full.js
// @resource jquery https://cdn.bootcdn.net/ajax/libs/jquery/1.8.3/jquery.min.js
// @resource icons https://www.gitlink.org.cn/repo/mapaler/fix-pad_skyozora_com/raw/branch/v2.5.0/icons-symbol.svg
// @grant GM_getResourceText
// @grant GM_registerMenuCommand
// @grant GM_getValue
// @grant GM_setValue
// @grant unsafeWindow
// @run-at document-start
// ==/UserScript==
(async function() {
'use strict';
const svgNS = "http://www.w3.org/2000/svg"; //svg用的命名空间
let mobileMode = /\bmobile\b/i.test(navigator.userAgent);
let T2S = GM_getValue("traditional-to-simplified") ?? true; //繁转简
let ConciseMode = GM_getValue("dungeon-style-concise") ?? true; //简洁模式
let OpenAllDetails = GM_getValue("open-all-details") ?? false; //自动展开所有详情
let storeAvatar = GM_getValue("local-store-card-avatar") ?? true; //数据库里储存怪物头像
//监听head的加载,代码来源于 EhTagSyringe
const headLoaded = new Promise(function (resolve, reject) {
if(document.head && document.head.nodeName == "HEAD") {
console.debug("已经有 head");
resolve(document.head);
}else{
console.debug("开始监听 head");
//监听DOM变化
let observer = new MutationObserver(function(mutations) {
for (const mutation of mutations) {
//监听到HEAD 结束
if(mutation.target.nodeName === "HEAD") {
console.debug("已监听到 head");
observer.disconnect();
resolve(mutation.target);
break;
}
}
});
observer.observe(document, {childList: true, subtree: true, attributes: true});
}
});
//head加载后添加国内的JQ源
headLoaded.then(head=>{
let jqElement = document.querySelector('script[src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"]');
if (jqElement) {
jqElement.src = "https://cdn.bootcdn.net/ajax/libs/jquery/1.8.3/jquery.min.js";
console.debug('替换外链 jQuery 路径');
} else {
console.debug('直接内嵌 jQuery');
[
'jquery',
].forEach(resName=>{
let scriptText = GM_getResourceText(resName);
if (!scriptText) return;
const script = document.createElement("script");
script.id = resName;
script.type = "text/javascript";
script.innerHTML = scriptText;
head.appendChild(script);
});
}
});
//大数字缩短长度
Number.prototype.bigNumberToString = /^(zh|ja|ko)\b/i.test(navigator.language) ?
(function() { //中、日、韩习惯
const negative = this < 0;
let numTemp = negative ? Math.abs(this) : this.valueOf();
if (!numTemp) return "0";
const grouping = 1e4;
const unit = ['','萬','億','兆','京','垓'];
const numParts = [];
do {
numParts.push(numTemp % grouping);
numTemp = Math.floor(numTemp / grouping);
} while (numTemp > 0 && numParts.length < (unit.length - 1))
if (numTemp > 0) {
numParts.push(numTemp);
}
let numPartsStr = numParts.map((num, idx) => {
if (num > 0) {
return (num < 1e3 ? "零" : "") + num + unit[idx];
} else
return "零";
});
numPartsStr.reverse(); //反向
let outStr = numPartsStr.join("");
outStr = outStr.replace(/(^零+|零+$)/g, ''); //去除开头的零
outStr = outStr.replace(/零{2,}/g, '零'); //去除多个连续的零
return (negative ? "-" : "") + outStr;
}) :
(function() { //英语习惯
const negative = this < 0;
let numTemp = negative ? Math.abs(this) : this.valueOf();
if (!numTemp) return "0";
const grouping = 1e3;
const unit = ['', 'K', 'M', 'G', 'T', 'P'];
const numParts = [];
do {
numParts.push(numTemp % grouping);
numTemp = Math.floor(numTemp / grouping);
} while (numTemp > 0 && numParts.length < (unit.length - 1))
if (numTemp > 0) {
numParts.push(numTemp);
}
let numPartsStr = numParts.map((num, idx) => {
if (num > 0) {
return num + unit[idx];
} else
return "";
});
let outStr = numPartsStr.filter(Boolean).reverse().join(" ");
return (negative ? "-" : "") + outStr;
});
const bootstrap = function(){
if (!mobileMode) {
document.styleSheets[0].deleteRule(1);
document.styleSheets[0].deleteRule(0);
}
//插入总svg
const svgText = GM_getResourceText("icons"); //将svg文本读取出来
if (svgText) {
const parser = new DOMParser();
const iconsSvg = parser.parseFromString(svgText, "image/svg+xml"); //转换成svg文档
const svgDoc = iconsSvg.documentElement;
svgDoc.setAttribute("class","hide");
const symbols = Array.from(svgDoc.querySelectorAll('symbol'));
const [maxWidth, maxHeight] = symbols.reduce(([maxWidth, maxHeight], symble)=>{
const img = symble.querySelector('image');
return [
Math.max(maxWidth, img.width.baseVal.value),
Math.max(maxHeight, img.height.baseVal.value)
];
},[0,0]);
symbols.forEach(symble=>{
const img = symble.querySelector('image');
symble.setAttribute('viewBox', [(maxWidth - img.width.baseVal.value) / -2, (maxHeight - img.height.baseVal.value) / -2, maxWidth, maxHeight].join(" "));
});
document.body.insertAdjacentElement("afterbegin", svgDoc); //插入body
}
const styleDom = document.head.appendChild(document.createElement("style"));
styleDom.textContent = `
* {
font-family: "Microsoft Yahei", "Microsoft JhengHei", "Source Han Sans", Arial, Helvetica, sans-serif, "Malgun Gothic", "맑은 고딕", "Gulim", AppleGothic !important;
color: white;
user-select: auto !important;
}
#container .item2 {
display: none;
}
body {
background:#222 ;
}
.hide {
display: none;
postion: absolute;
}
.svg-icon {
width: 2em;
height: 2em;
vertical-align: text-bottom;
}
.svg-icon text {
font-family: "FOT-Kurokane Std EB", "Arial Black" !important;
font-size: 1.1em;
font-weight: bold;
text-shadow: 0 0 1px black,1px 1px 1px black,-1px -1px 1px black;
text-anchor: middle;
/* 文本水平居中 */
dominant-baseline: middle;
/* 文本垂直居中 */
}
.tooltip[href*="pets/"][data-id]::after
{
display: inline-block;
vertical-align: middle;
content: "No."attr(data-id)"\\000A"attr(data-name);
white-space: pre;
line-height: 1em;
}
.tooltip[href$="pets/"]::after,
.tooltip:not([data-id])::after
{
content: unset;
}
tr[align="center"] .tooltip[href*="pets/"]::after
{
display: block;
max-width: 70px;
white-space: pre-wrap;
}
.paddf-link:link {
text-decoration: underline;
}
.enhance-board {
border-collapse: collapse;
background-color: #532;
}
.enhance-board tr>td
{
width: 12px;
height: 12px;
}
.enhance-board tr:nth-of-type(2n+1)>td:nth-of-type(2n+1),
.enhance-board tr:nth-of-type(2n)>td:nth-of-type(2n)
{
background-color: rgba(0,0,0,0.4);
}
.enhance-board .orb-true::before
{
display: block;
content: "";
background-color: white;
width: 12px;
height: 12px;
border-radius: 50%;
}
details summary{
cursor: pointer;
}
.skill-detail summary{
font-size:14px;
color: #99E8FF;
}
.board-position summary{
width: max-content;
padding: 0 5px;
border-radius: 5px;
background-color: #F252A1;
}
`;
// 将和制汉字转换为简体中文(中国大陆)
const converterJP2CN = OpenCC.Converter({ from: 'jp', to: 'cn' });
// 将和制汉字转换为繁体中文(中国香港)
const converterJP2HK = OpenCC.Converter({ from: 'jp', to: 'hk' });
// 将繁体中文(香港)转换为简体中文(中国大陆)
const converterHK2CN = OpenCC.Converter({ from: 'hk', to: 'cn' });
// 将类型的假名转换为繁体中文(中国香港)
const converterKANA2CN = OpenCC.CustomConverter([
['のみ', '限定'],
['タイプ', '類型'],
['キャラ', '角色'],
['マシン', '機械'],
['ドラゴン', '龍'],
['バランス', '平衡'],
['リーダー', '隊長'],
['助っ人', '輔助'],
['ハンター', '獵人'],
['ラッシュ', 'Rush '],
['コロシアム', 'Coliseum '],
['ボス', 'BOSS '],
['ノーコン', '無法續關'],
['チーム', '隊伍'],
['ヘラ', '赫拉赫拉'],
['ゼウス', '宙斯'],
['アテナ', '雅典娜'],
['からくり', '機關'],
['ノア', '諾亞'],
['の', '的'],
['と', '與'],
]);
//本地数据库储存头像;
if (storeAvatar) redirectLocalCardAvatar();
//所有带名字的头像添加名字
const cardAvatars = [...document.body.querySelectorAll('.tooltip[href^="pets/"]')];
cardAvatars.forEach(avatar=>{
let titleReg = /(\d+)\s*\-\s*(.+)/i.exec(avatar.title);
if (titleReg) {
avatar.dataset.id = titleReg[1];
avatar.dataset.name = T2S ? converterHK2CN(titleReg[2]) : titleReg[2];
}
})
const StageInfo = document.querySelector('#StageInfo');
//地下城页面
if (/^\/stage\b/.test(location.pathname) && StageInfo)
{
//====去除禁止复制内容的限制====
let unbidFunctionStr = "$(`#${this.id}`).unbind(); this.removeAttribute('oncopy'); this.removeAttribute('oncut'); this.removeAttribute('onpaste');";
StageInfo.setAttribute("oncopy", unbidFunctionStr);
StageInfo.setAttribute("oncut", unbidFunctionStr);
StageInfo.setAttribute("onpaste", unbidFunctionStr);
const pcPage = document.querySelector("#wrapper");
if (ConciseMode) { //添加精简模式的CSS
const styleConcise = document.createElement("style");
styleConcise.textContent = pcPage
? `.ats-skyscraper-wrapper,
.fb-share-button,
.twitter-tweet-button,
.twitter-share-button,
body > :not(#wrapper),
#wrapper > :not(table),
#wrapper > table:not(:nth-of-type(3)),
#wrapper > table:nth-of-type(3) > tbody > tr > td:nth-of-type(n+2),
#wrapper > table:nth-of-type(3) > tbody > tr > td:first-of-type > #fb-root,
#wrapper #StageInfo ~ *
{
display: none !important;
}
#wrapper {
width: 100%;
padding: 0;
}
#wrapper > table:nth-of-type(3) {
width: auto;
}`
: `body > :not(.content),
.content>p:empty,
.content>center,
#StageInfo>br,
#StageInfo>div:empty
{
display: none !important;
}
`
;
document.head.appendChild(styleConcise);
}
let pageTitle = document.title;
const JpHTMLConverter = T2S ? converterJP2CN : converterJP2HK;
pageTitle = pageTitle.replace(/^(.+)\s*-\s*(.+)\s*-\s*Puzzle & Dragons 戰友系統及資訊網/,
(match, p1, p2) => `${converterKANA2CN(JpHTMLConverter(p2))} - ${converterKANA2CN(JpHTMLConverter(p1))}` );
document.title = pageTitle;
const stageTitle = StageInfo.querySelector(":scope>h2");
if (stageTitle)
{
//和制汉字到繁体
const stage1 = stageTitle.querySelector("a");
stage1.textContent = converterKANA2CN(stage1.textContent);
const stage2 = Array.from(stageTitle.childNodes).find(node=>node.nodeName == "#text");
stage2.nodeValue = converterKANA2CN(stage2.nodeValue);
//和制汉字到中文
stageTitle.lang = 'jp';
const HTMLConvertHandler = OpenCC.HTMLConverter(JpHTMLConverter, stageTitle, 'jp', T2S ? 'zh-CN' : 'zh-HK');
HTMLConvertHandler.convert();
}
//提供固定队伍可跳转到PADDashFormation
const stageTeam = StageInfo.querySelector(":scope>div");
if (stageTeam?.textContent?.includes("本地下城採用系統預設隊伍"))
{
const cardAvatars = Array.from(stageTeam.querySelectorAll(':scope>a[href^="pets/"]'));
const cardIds = cardAvatars.map(avatar=>{
let hrefReg = /pets\/(\d+)/i.exec(avatar.href);
return hrefReg?.[1] || 0;
});
console.log(cardAvatars, cardIds);
const formationOutObj = {f: [[cardIds.map(id=>id>0?[id,99]:null), []]], v: 4};
const PADDFurl = new URL("https://mapaler.github.io/PADDashFormation/solo.html");
PADDFurl.searchParams.set("d", JSON.stringify(formationOutObj));
const PADDFlink = document.createElement("a");
PADDFlink.className = "paddf-link";
PADDFlink.href = PADDFurl;
PADDFlink.target = "_blank";
PADDFlink.textContent = "PADDashFormation 组队链接";
stageTeam.appendChild(PADDFlink);
}
const stageDetail = StageInfo.querySelector(":scope>table:nth-of-type(2)");
if (stageDetail)
{
//HP和防御条
const centerRows = stageDetail.tBodies[0].querySelectorAll(":scope>tr[align=\"center\"]:not(:first-child)");
for (let tr of centerRows)
{
let tds = tr.querySelectorAll(":scope>td:not([rowspan])");
if (tds.length>5)
{
domBigNumToString(tds[0]); //血量
domBigNumToString(tds[3]); //攻击
domBigNumToString(tds[5]); //防御
}
}
//先制数字
const leftRows = stageDetail.tBodies[0].querySelectorAll(":scope>tr[align=\"left\"]");
for (let tr of leftRows)
{
let skillNames = tr.querySelectorAll(":scope .skill");
for (let skillName of skillNames) {
if (skillName.nextSibling) {
domBigNumToString(skillName.nextSibling);
domAddIcon(skillName.nextSibling); //技能加图标
}
}
//伤害数字
let skillDamages = tr.querySelectorAll(":scope .skill_demage");
for (let skillDamage of skillDamages)
{
domBigNumToString(skillDamage);
}
}
}
//强化版面位置的显示
const boardsNode = Array.from(StageInfo.querySelectorAll('[onclick^="open_menu"]+[id^="skill"]'));
boardsNode.forEach(node=>{
node.classList.add("boards");
const boardDataCells = node.querySelectorAll(":scope table tr:nth-of-type(2)>td");
boardDataCells.forEach(cell=>{
const data = cell.textContent.split('\n').map(row=>Array.from(row).map(o=>Boolean(o=='●')));
const boardTable = createBoard(data);
cell.innerHTML = '';
cell.appendChild(boardTable);
})
if (node.nextElementSibling.nodeName == "BR") {
node.nextElementSibling.remove();
}
});
function createBoard(data) {
const table = document.createElement("table");
table.className = `enhance-board`;
for (let ri=0; ri<data.length; ri++)
{
const orbsRow = data[ri];
const row = table.insertRow();
for (let ci=0; ci<orbsRow.length; ci++)
{
const cell = row.insertCell();
cell.className = `orb-${orbsRow[ci]}`;
}
}
return table;
}
//直接打开所有隐藏内容
const hiddenSkillsSwitch = Array.from(document.body.querySelectorAll("[onclick^=open_]"));
hiddenSkillsSwitch.forEach(i=>i.remove()); //删除所有开关
const hiddenSkills = Array.from(document.querySelectorAll('[id^="skill"]')).filter(i=>i.style.display);
hiddenSkills.forEach(i=>{
const boardPos = /skill(\d+_){4}\d+/.test(i.id);
const detail = document.createElement("details");
detail.className = boardPos ? "board-position" : "skill-detail";
detail.open = OpenAllDetails ? true : boardPos;
detail.id = i.id;
const summary = detail.appendChild(document.createElement("summary"));
summary.textContent = boardPos ? "生成位置" : "敌人技能资料" ;
detail.append(...i.childNodes);
i.parentNode.insertBefore(detail, i);
i.remove();
});
}
//新闻页面,主要是针对于8人本页面
if (/^\/news\//.test(location.pathname))
{
const contentTables = Array.from(document.body.querySelectorAll(".content>table"));
for (let table of contentTables)
{
const rows = Array.from(table.rows).slice(1);
for (let tr of rows)
{
domBigNumToString(tr.cells[1]);
}
}
}
//====转简体====
if (T2S) {
document.title = converterHK2CN(document.title);
// 设置转换起点为根节点,即转换整个页面
const rootNode = document.documentElement;
document.body.lang = 'zh-HK';
// 将所有 zh-HK 标签转为 zh-CN 标签
const HTMLConvertHandler = OpenCC.HTMLConverter(converterHK2CN, rootNode, 'zh-HK', 'zh-CN');
HTMLConvertHandler.convert(); // 开始转换 -> 汉语
}
}
function domBigNumToString(dom)
{
if (!(dom instanceof Node)) return;
const regOriginal = /\b-?\d+(?:,\d{3})*\b/g;
if (dom.nodeType === Node.TEXT_NODE) {
textNodeConvertNumber(dom);
} else {
let nodes = Array.from(dom.childNodes);
nodes = nodes.filter(node=>node.nodeType === Node.TEXT_NODE);
for (let textNode of nodes)
{
textNodeConvertNumber(textNode);
}
}
//在纯文本node内转换数字
function textNodeConvertNumber(textNode) {
textNode.nodeValue = textNode.nodeValue.trim()
.replace(new RegExp(regOriginal), match=>{
return parseInt(match.replaceAll(",",""), 10).bigNumberToString();
});
}
}
SVGSVGElement.prototype.appendSymbleIcon = function(id) {
const use = document.createElementNS(svgNS,'use');
use.setAttribute("href",`#i-${id}`);
this.appendChild(use);
return use;
}
function domAddIcon(dom)
{
//创建svg图标引用的svg
function svgIcon(id) {
const svg = document.createElementNS(svgNS,'svg');
svg.setAttribute("class","svg-icon");
svg.appendSymbleIcon(id);
return svg;
}
function attrIndex(str) {
switch (str) {
case '火': return 0;
case '水': return 1;
case '木': return 2;
case '光': return 3;
case '暗': return 4;
}
}
function typeIndex(str) {
switch (str) {
case '進化用': return 0;
case '平衡': return 1;
case '體力': return 2;
case '回復': return 3;
case '龍': return 4;
case '神': return 5;
case '攻擊': return 6;
case '惡魔': return 7;
case '機械': return 8;
case '特別保護': return 9; //已经没有这个type了
case '能力覺醒用': return 12;
case '強化合成用': return 14;
case '販賣用': return 15;
}
}
//用于查找下一个文本节点
function nextTextNode(node) {
const nextNode = node.nextSibling;
if (nextNode == null ||
nextNode.nodeType === Node.TEXT_NODE && nextNode.length > 0) {
return nextNode;
} else {
return nextTextNode(nextNode);
}
}
if (dom.nodeType === Node.TEXT_NODE) {
let res;
if (res = /異常狀態(如毒、威嚇、破防)無效化/.exec(dom.nodeValue)) {
dom.parentElement.insertBefore(svgIcon('abnormal-state-shield'), dom);
}
if (res = /HP在上限\d+%或以上的話,受到致命傷害時,將會以(\d+)(點|%)HP生還/.exec(dom.nodeValue)) {
const superResolve = res[2] == '%';
const svg = svgIcon(superResolve ? 'super-resolve' : 'resolve');
if (superResolve) {
const text = document.createElementNS(svgNS,'text');
text.textContent = res[1];
text.setAttribute("x", "50%");
text.setAttribute("y", "50%");
text.setAttribute("fill", "white");
svg.appendChild(text);
}
dom.parentElement.insertBefore(svg, dom);
}
if (res = /單一傷害值.+點以上的傷害(吸收|無效)/.exec(dom.nodeValue)) {
const svg = svgIcon('shield');
const frontIcon = svg.appendSymbleIcon(`damage-${res[1]=='吸收'?'absorb':'void'}`);
dom.parentElement.insertBefore(svg, dom);
}
if (res = /將\s*(\d+)COMBO\s*或以下時所造成的傷害全部吸收/.exec(dom.nodeValue)) {
const svg = svgIcon('combo-absorb');
svg.appendSymbleIcon('recover');
const text = document.createElementNS(svgNS,'text');
text.textContent = res[1];
text.setAttribute("x", "50%");
text.setAttribute("y", "50%");
text.setAttribute("fill", "#F7C");
svg.appendChild(text);
dom.parentElement.insertBefore(svg, dom);
}
if (res = /受到的(.*)屬性傷害減少(\d+)%/.exec(dom.nodeValue)) {
const all = !Boolean(res[1]);
const attrs = res[1].split("、");
for (let i = 0; i < attrs.length; i++) {
const attr = attrIndex(attrs[i]);
const svg = svgIcon('shield');
if (all) {
const text = document.createElementNS(svgNS,'text');
text.textContent = res[2];
text.setAttribute("x", "50%");
text.setAttribute("y", "50%");
text.setAttribute("fill", "white");
svg.appendChild(text);
} else {
const frontIcon = svg.appendSymbleIcon(`attr-${attr}`);
frontIcon.setAttribute('transform', 'scale(0.85) translate(2, 0)');
}
dom.parentElement.insertBefore(svg, dom);
}
}
if (res = /由(.+)寵物造成的傷害減少(\d+)%/.exec(dom.nodeValue)) {
const types = res[1].split(/[、和]/);
for (let i = 0; i < types.length; i++) {
const type = typeIndex(types[i].replace(/類$/,''));
const svg = svgIcon('shield');
const frontIcon = svg.appendSymbleIcon(`type-${type}`);
frontIcon.setAttribute('transform', 'scale(0.7) translate(5, 3)');
dom.parentElement.insertBefore(svg, dom);
}
}
if (res = /將(?:受到的)?(?:隨機|其中)?([一\d]種)?(.*)屬性傷害轉換成自己的生命值/.exec(dom.nodeValue)) {
if (Boolean(res[1])) { //如果随机
const svg = svgIcon(`attr-any`);
svg.appendSymbleIcon('recover');
dom.parentElement.insertBefore(svg, dom);
return;
}
let attrsStr = res[2];
const hasMultiGroup = /「.+」/.test(attrsStr);
//multiGroupTypeA 「火水」、「水木」
//multiGroupTypeB 「火/水」、「水/木」
//multiGroupTypeC 「火水/水木」
const multiGroupTypeC = /^「([^「」]+)」$/.exec(attrsStr);
if (multiGroupTypeC) attrsStr = multiGroupTypeC[1];
const normalSplit = attrsStr.includes('、'); //是否是顿号的普通分割
let attrs = attrsStr.split(normalSplit ? "、" : "/" ); //用顿号或者/分割第一次
attrs = attrs.map(attrStr=>{
const multiGroupTypeAB = /「(.+?)」/mg.exec(attrStr);
if (multiGroupTypeAB) {
attrStr = multiGroupTypeAB[1];
}
if (attrStr.length > 1) { //如果不止一个属性
return (attrStr.includes('/') ? attrStr.split('/') : Array.from(attrStr)).map(attrIndex); //子组分割
} else {
return attrIndex(attrStr);
}
});
if (!hasMultiGroup) attrs = [attrs]; //如果并没有多组,则嵌入到一个单元素数组
const fragment = document.createDocumentFragment();
for (let i=0;i<attrs.length;i++) {
let attrArr = attrs[i];
attrArr.forEach(attr=>{
const svg = svgIcon(`attr-${attr}`);
svg.appendSymbleIcon('recover');
fragment.append(svg);
});
if (i<(attrs.length-1)) {
fragment.append('/');
}
}
dom.parentElement.insertBefore(fragment, dom);
}
if (res = /技能的?冷卻時間(增加|縮短)/.exec(dom.nodeValue)) {
const zuo = res[1]=='增加';
const svg = svgIcon('skill-boost');
const text = document.createElementNS(svgNS,'text');
text.textContent = zuo?'-':'+';
text.setAttribute("x", "80%");
text.setAttribute("y", zuo?"80%":"20%");
text.setAttribute("fill", zuo?"lightblue":"yellow");
text.setAttribute("style", "font-size: 2em;");
svg.appendChild(text);
dom.parentElement.insertBefore(svg, dom);
}
if (res = /覺醒技能無效化/.exec(dom.nodeValue)) {
const svg = svgIcon('awoken-bind');
dom.parentElement.insertBefore(svg, dom);
}
if (res = /無法發動任何主動技能/.exec(dom.nodeValue)) {
const svg = svgIcon('skill-bind');
dom.parentElement.insertBefore(svg, dom);
}
if (res = /封鎖.+寵物/.exec(dom.nodeValue)) {
const svg = svgIcon('member-bind');
dom.parentElement.insertBefore(svg, dom);
}
if (res = /結算時(增加|減少)(\d+)COMBO/.exec(dom.nodeValue)) {
const decrease = res[1]=='減少';
const svg = svgIcon(`combo-${decrease?'decrease':'increase'}`);
const text = document.createElementNS(svgNS,'text');
text.textContent = res[2];
text.setAttribute("x", "70%");
text.setAttribute("y", "40%");
text.setAttribute("fill", "white");
svg.appendChild(text);
dom.parentElement.insertBefore(svg, dom);
}
if (res = /(寵物的)?攻擊力變成原來的(\d+)%/.exec(dom.nodeValue)) {
const member = Boolean(res[1]);
const decrease = Number(res[2])<100;
let svg = svgIcon(member ? 'assist-bind' : 'attr-any');
const frontIcon = svg.appendSymbleIcon(`member-atk-${decrease?'decrease':'increase'}`);
frontIcon.setAttribute('transform', `scale(0.75) translate(${member ? 4 : 10}, ${member ? 4 : (decrease? 10 : 0)})`);
dom.parentElement.insertBefore(svg, dom);
}
if (res = /回復力變成(?:原來的)?(\d+)%?/.exec(dom.nodeValue)) {
const decrease = Number(res[1])<100;
const svg = svgIcon('attr-5');
const frontIcon = svg.appendSymbleIcon(`member-atk-${decrease?'decrease':'increase'}`);
frontIcon.setAttribute('transform', `scale(0.75) translate(10, ${decrease? 10 : 0 })`);
dom.parentElement.insertBefore(svg, dom);
}
if (res = /輔助寵物無效化/.exec(dom.nodeValue)) {
const svg = svgIcon('assist-bind');
const frontIcon = svg.appendSymbleIcon(`bind`);
//frontIcon.setAttribute('transform', 'translate(0, 5)');
dom.parentElement.insertBefore(svg, dom);
}
if (res = /寶珠鎖定/.exec(dom.nodeValue)) {
const svg = svgIcon('lock');
dom.parentElement.insertBefore(svg, dom);
}
if (res = /鎖定.*掉落/.exec(dom.nodeValue)) {
const svg = svgIcon('lock');
svg.appendSymbleIcon(`fall-down`);
dom.parentElement.insertBefore(svg, dom);
}
if (res = /攻擊力提升至/.exec(dom.nodeValue)) {
const svg = svgIcon('angry');
dom.parentElement.insertBefore(svg, dom);
}
if (res = /(遮擋寶珠的雲|雲遮擋寶珠)/.exec(dom.nodeValue)) {
const svg = svgIcon('cloud');
dom.parentElement.insertBefore(svg, dom);
}
if (res = /無法移動(.+)的寶珠/.exec(dom.nodeValue)) {
const svg = svgIcon('immobility');
dom.parentElement.insertBefore(svg, dom);
}
if (res = /(掉落|變成)超暗闇/.exec(dom.nodeValue)) {
const svg = svgIcon('super-dark');
if (res[1] == '掉落') {
svg.appendSymbleIcon(`fall-down`);
}
dom.parentElement.insertBefore(svg, dom);
}
if (res = /寶珠每隔([\d\.]+)秒不斷轉換/.exec(dom.nodeValue)) {
const svg = svgIcon('roulette');
dom.parentElement.insertBefore(svg, dom);
}
if (res = /造成玩家目前HP(\d+)%的傷害/.exec(dom.nodeValue)) {
const svg1 = svgIcon('gravity');
dom.parentElement.insertBefore(svg1, dom);
const damage = Number(res[1]);
if (damage < 100) return; //小于100的重力不需要盾
const fragment = document.createDocumentFragment();
fragment.append(`(需要`);
const svg2 = svgIcon('shield');
svg2.appendSymbleIcon(`text-defense`);
fragment.append(svg2);
fragment.append(`盾>${Math.round((1-100/Number(res[1]))*10000)/100}%)`);
dom.parentElement.insertBefore(fragment, dom.nextSibling);
}
if (res = /掉落(弱體|强)化寶珠/.exec(dom.nodeValue)) {
const decline = res[1]=='弱體';
const svg = svgIcon(`orb-${decline?'decline':'enhance'}`);
svg.appendSymbleIcon(`fall-down`);
dom.parentElement.insertBefore(svg, dom);
}
if (res = /天降的寶珠不會產生COMBO/.exec(dom.nodeValue)) {
const svg = svgIcon('no-fall-dowm');
dom.parentElement.insertBefore(svg, dom);
}
if (res = /寶珠起手的位置/.exec(dom.nodeValue)) {
const svg = svgIcon('fix-start-position');
dom.parentElement.insertBefore(svg, dom);
}
if (res = /攻擊目標被鎖定/.exec(dom.nodeValue)) {
const svg = svgIcon('fix-target');
dom.parentElement.insertBefore(svg, dom);
}
if (res = /寶珠移動時間變成原來的(\d+)%/.exec(dom.nodeValue)) {
const decrease = Number(res[1])<100;
const svg = svgIcon(`move-time-${decrease?'decrease':'increase'}`);
dom.parentElement.insertBefore(svg, dom);
}
if (res = /寶珠移動時間(減少|增加)(\d+)秒/.exec(dom.nodeValue)) {
const decrease = res[1] == '減少';
const svg = svgIcon(`move-time-${decrease?'decrease':'increase'}`);
dom.parentElement.insertBefore(svg, dom);
}
if (res = /玩家的HP上限/.exec(dom.nodeValue)) {
const svg = svgIcon('change-max-hp');
dom.parentElement.insertBefore(svg, dom);
}
if (res = /隊員換成隊長/.exec(dom.nodeValue)) {
const svg = svgIcon('change-leader-position');
dom.parentElement.insertBefore(svg, dom);
}
if (res = /隊長會(?:隨機)?變成/.exec(dom.nodeValue)) {
const svg = svgIcon('change-leader-card');
dom.parentElement.insertBefore(svg, dom);
}
if (res = /寶珠盤面變成【\d+×\d+】/.exec(dom.nodeValue)) {
const svg = svgIcon('board-change-size');
dom.parentElement.insertBefore(svg, dom);
}
if (res = /掉落COMBO寶珠/.exec(dom.nodeValue)) {
const svg = svgIcon('orb-combo');
svg.appendSymbleIcon(`fall-down`);
dom.parentElement.insertBefore(svg, dom);
}
if (res = /隨機轉換自身的屬性|將自身的屬性轉換成(.)/.exec(dom.nodeValue)) {
const svg = svgIcon('enemy-attr');
const attr = res[1] ? attrIndex(res[1]) : 'any';
const frontIcon = svg.appendSymbleIcon(`attr-${attr}`);
frontIcon.setAttribute('transform', 'scale(0.85) translate(1, 1)');
dom.parentElement.insertBefore(svg, dom);
}
if (res = /回復玩家(\d+)%HP/.exec(dom.nodeValue)) {
const svg = svgIcon('heal');
dom.parentElement.insertBefore(svg, dom);
}
// if (res = /消除玩家所有BUFF技能效果/.exec(dom.nodeValue)) {
// const svg = svgIcon('bind');
// dom.parentElement.insertBefore(svg, dom);
// }
if (res = /變成不會受到任何傷害的狀態/.exec(dom.nodeValue)) {
const svg = svgIcon('invincible');
dom.parentElement.insertBefore(svg, dom);
}
if (res = /回到可以承受傷害的狀態/.exec(dom.nodeValue)) {
const svg = svgIcon('invincible');
const frontIcon = svg.appendSymbleIcon(`bind`);
frontIcon.setAttribute('transform', 'translate(0, 5)');
dom.parentElement.insertBefore(svg, dom);
}
if (res = /(掉落|出現)荊棘/.exec(dom.nodeValue)) {
const svg = svgIcon('thorn');
if (res[1] == '掉落') svg.appendSymbleIcon(`fall-down`);
dom.parentElement.insertBefore(svg, dom);
}
if (res = /\d+回合內,$/.exec(dom.nodeValue) && dom.nextElementSibling.nodeName == 'IMG') {
const nextText = nextTextNode(dom);
if (/無法被消除/.exec(nextText.nodeValue)) {
const svg = svgIcon('bind');
dom.parentElement.insertBefore(svg, dom);
}
if (/掉落機率提升/.exec(nextText.nodeValue)) {
const svg = svgIcon('fall-down');
dom.parentElement.insertBefore(svg, dom);
}
}
}
}
GM_registerMenuCommand(`${T2S?"关闭":"开启"}-繁体转简体`, function(){
alert(`${T2S?"关闭":"开启"}繁体转简体后,刷新页面生效。`);
GM_setValue("traditional-to-simplified", !T2S);
});
GM_registerMenuCommand(`${ConciseMode?"关闭":"开启"}-地下城简洁模式`, function(){
alert(`${ConciseMode?"关闭":"开启"}地下城简洁模式后,刷新页面生效。`);
GM_setValue("dungeon-style-concise", !ConciseMode);
});
GM_registerMenuCommand(`${OpenAllDetails?"关闭":"开启"}-自动展开所有详情`, function(){
alert(`${OpenAllDetails?"关闭":"开启"}自动展开所有详情后,刷新页面生效。`);
GM_setValue("open-all-details", !OpenAllDetails);
});
GM_registerMenuCommand(`${storeAvatar?"关闭":"开启"}-在本地数据库储存怪物头像`, function(){
alert(`${storeAvatar?"关闭":"开启"}本地数据库储存怪物头像后,刷新页面生效。`);
GM_setValue("local-store-card-avatar", !storeAvatar);
});
//加载document后执行启动器
if (/loaded|complete/.test(document.readyState)){
bootstrap();
}else{
document.addEventListener('DOMContentLoaded',bootstrap,false);
}
async function redirectLocalCardAvatar(){
function loadDatabase(dbName, dbVersion) {
return new Promise(function(resolve, reject) {
const DBOpenRequest = indexedDB.open(dbName, dbVersion);
DBOpenRequest.onsuccess = function(event) {
resolve(event.target.result); //DBOpenRequest.result;
console.debug(GM_info.script.name, "数据库已可使用");
};
DBOpenRequest.onerror = function(event) {
// 错误处理
console.error(GM_info.script.name, "数据库无法启用,删除可能存在的异常数据库。",event);
indexedDB.deleteDatabase(dbName); //直接把整个数据库删掉
console.error("也可能是隐私模式导致无法启用数据库,于是尝试不保存的情况下读取数据。");
reject(event);
};
DBOpenRequest.onupgradeneeded = function(event) {
let db = event.target.result;
let store;
store = db.createObjectStore("cards_avatar");
// 使用事务的 oncomplete 事件确保在插入数据前对象仓库已经创建完毕
store.transaction.oncomplete = function(event) {
console.log(GM_info.script.name, "数据库建立完毕");
};
};
});
}
const dbName = "pad-skyozora-enhance";
const tableName = "cards_avatar";
const dbVersion = 1;
let db = await loadDatabase(dbName, dbVersion);
function putImageBlob(blob, key) {
return new Promise(function(resolve, reject) {
const transaction = db.transaction([tableName], "readwrite");
const objectStore = transaction.objectStore(tableName);
const put = objectStore.put(blob, key);
put.onsuccess = function (event) {
const imgURL = URL.createObjectURL(blob);
resolve(imgURL);
};
put.onerror = function (event) {
reject(false);
};
});
}
function getImageBlobURL(key) {
return new Promise(function(resolve, reject) {
const transaction = db.transaction([tableName], "readwrite");
const objectStore = transaction.objectStore(tableName);
const get = objectStore.get(key);
get.onsuccess = function(event) {
const blob = event.target.result;
if (blob == undefined) {
resolve(false);
return;
}
const imgURL = URL.createObjectURL(blob);
resolve(imgURL);
};
get.onerror = function(event) {
reject(false);
};
});
}
function getId(src) {
const regres = src.match(/pets\/(\d+)\.png/i);
return parseInt(regres[1],10);
//return `card-avatar-${regres[1]}`;
}
const cardAvatars = Array.from(document.querySelectorAll('img:where([src^="images/pets"],[data-original^="images/pets"])'));
cardAvatars.forEach(async avatar=>{
const src = avatar.dataset.original ?? avatar.src;
const cardId = getId(src);
//console.log("configId",configId);
let avatarURL = await getImageBlobURL(cardId);
if (!avatarURL) {
console.debug('数据库中未获取到 No.%d 的头像数据,开始下载。', cardId);
let response = await fetch(src);
let blob = await response.blob();
avatarURL = await putImageBlob(blob, cardId);
console.debug(' No.%d 的头像数据下载成功。', cardId);
} else {
//console.debug('直接使用数据库中获取到的 No.%d 的头像数据。', cardId);
}
avatar.dataset.id = cardId;
if (avatar.dataset.original) avatar.dataset.original = avatarURL;
avatar.src = avatarURL;
});
}
})();