您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Calculates skill rolls, and adds a new table column on the skills page.
// ==UserScript== // @name WoD 显示技能骰[中文] // @namespace com.dobydigital.userscripts.wod // @description Calculates skill rolls, and adds a new table column on the skills page. // @author XaeroDegreaz // @home https://github.com/XaeroDegreaz/world-of-dungeons-userscripts // @supportUrl https://github.com/XaeroDegreaz/world-of-dungeons-userscripts/issues // @source https://raw.githubusercontent.com/XaeroDegreaz/world-of-dungeons-userscripts/main/src/display-skill-rolls.user.js // @match *://*.world-of-dungeons.org/wod/spiel/hero/skills* // @icon http://info.world-of-dungeons.org/wod/css/WOD.gif // @require https://code.jquery.com/jquery-3.3.1.min.js // @grant GM.xmlHttpRequest // @modifier Christophero // @version 2023.01.08.1 // ==/UserScript== (async function () { "use strict"; const loadHeroAttributes = async () => { return await new Promise((resolve) => { GM.xmlHttpRequest({ url: "/wod/spiel/hero/attributes.php", synchronous: false, onload: (data) => { resolve(parseHeroAttributes(data)); }, }); }); }; const parseHeroAttributes = (data) => { const jq = $(data.responseText); const attributesTable = jq.find("table[class=content_table]").first(); if (!attributesTable.length) { console.error("NOPE.", attributesTable); return; } const attributeRows = $(attributesTable).find("tr[class^=row]"); const rawRows = attributeRows .map(function () { const cells = $(this).find("> td"); const attributeName = cells.first().text().trim(); const valueCell = cells .find(":nth-child(2)") .contents() .filter(function () { return this.nodeType == 3; }) .text() .trim(); const effectiveValueCell = cells .find(":nth-child(2) > span[class=effective_value]") .text() .trim() .replace(/\D/g, ""); return { attributeName, valueCell, effectiveValueCell }; }) .toArray(); const retVal = {}; rawRows.forEach((x) => { retVal[x.attributeName] = x.effectiveValueCell.length > 0 ? Number(x.effectiveValueCell) : Number(x.valueCell); }); return retVal; }; const parseAttackRolls = (data) => { //console.log("ABC",data); const jq = $(data); const markers = jq .find("li") .map(function () { const match = /^(?<rollType>.+)公式为:(?<rollCalculation>.+?)( \((?<modifier>[+\-]\d*%?)\))?$/g.exec( $(this).text() ); if (!match) { return; } //console.log( "match",{match} ); // @ts-ignore const { rollType, rollCalculation, modifier } = match.groups; //console.log( "meeee",{rollType, rollCalculation, modifier} ); return { rollType, rollCalculation, modifier }; }) .toArray(); //console.log( {markers} ); return markers; }; function calculateSkillRoll( heroAttributes, skillName, skillLevel, rollCalculation, modifier ) { //console.log(1,{heroAttributes, skillName, skillLevel, rollCalculation, modifier} ); let replaced = rollCalculation.replaceAll(skillName, skillLevel).trim(); //console.log(replaced); Object.keys(heroAttributes).forEach((key) => { replaced = replaced.replaceAll(key, heroAttributes[key]); }); replaced = replaced.replaceAll("×", "*"); replaced = replaced.replaceAll("÷", "/"); replaced = replaced.replaceAll("2", "2"); replaced = replaced.replaceAll("3", "3"); replaced = replaced.replaceAll("+", "+"); console.log({ replaced }); const roll = eval(replaced); const modifierAsNumber = Number(modifier?.replaceAll(/\D/g, "")); const modifierAsFraction = modifierAsNumber / 100; const rollWithModifier = modifier ? modifier.endsWith("%") ? modifier.startsWith("+") ? roll * (1 + modifierAsFraction) : roll * (1 - modifierAsFraction) : modifier.startsWith("+") ? roll + modifierAsNumber : roll - modifierAsNumber : roll; //console.log( {rollWithModifier} ); return Math.floor(rollWithModifier); } const storage = window.localStorage; const SKILL_ROLLS_STORAGE_KEY = "com.dobydigital.userscripts.wod.displayskillrolls.skillrollslist"; //console.log(123123,storage); function load(key) { try { const raw = storage?.getItem(key); return raw ? JSON.parse(raw) : undefined; } catch (e) { console.error( `Hero Selector Dropdown Userscript: Unable to load key:${key}`, e ); return undefined; } } function save(key, value) { try { storage?.setItem(key, JSON.stringify(value)); } catch (e) { console.error( `Hero Selector Dropdown Userscript: Unable to save key:${key}`, e ); } } function remove(key) { try { storage?.removeItem(key); } catch (e) { console.error( `Hero Selector Dropdown Userscript: Unable to remove key:${key}`, e ); } } const main = async () => { // add button to remove skill roll data $('<button class="button clickable">清除技能骰缓存</button>') .insertBefore('input[name="hide_all"]') .click(function () { remove(SKILL_ROLLS_STORAGE_KEY); alert("技能骰缓存已清除"); }); const contentTable = $("table[class=content_table]"); const body = $(contentTable).find("> tbody"); const header = $(body).find("> tr[class=header]"); $(header).append("<th>Base Rolls</th>"); const skillRows = $(body).find("tr[class^=row]"); $(skillRows).each(async function () { const a = $(this).find("a"); //# Re-align the skill name cell so the text doesn't look inconsistent when injecting attack rolls $(a).parent().attr("valign", "center"); $(this).append('<td class="roll_placeholder">-</td>'); }); const heroAttributes = await loadHeroAttributes(); const shortAttributes = heroAttributes; const skillRollData = load(SKILL_ROLLS_STORAGE_KEY) || {}; //console.log( {heroAttributes, shortAttributes, skillRollData} ); //console.log( {header} ); // # begin parsing rows $(skillRows).each(async function () { const row = $(this); $(row) .find("input[type=image]") .click(async function () { await renderRollData($(row), skillRollData, shortAttributes); }); await renderRollData($(row), skillRollData, shortAttributes); }); }; const renderRollData = async (row, skillRollData, shortAttributes) => { //console.log( "rendering") const a = $(row).find("a"); const skill = $(a).text(); const link = $(a).attr("href"); //console.log( {skill, link} ); const baseLevel = $(row).find("div[id^=skill_rang_]").text().trim(); const effectiveLevel = $(row) .find("span[id^=skill_eff_rang_]") .text() .replace(/\D/g, "") .trim(); const skillLevel = effectiveLevel.length > 0 ? Number(effectiveLevel) : Number(baseLevel); if (!skillLevel) { return; } if (!skillRollData?.[skill]) { const skillData = await new Promise((resolve) => { GM.xmlHttpRequest({ url: link, synchronous: false, onload: (data) => { resolve(data.responseText); }, }); }); //console.log('qqqq',skillData); skillRollData[skill] = parseAttackRolls(skillData); save(SKILL_ROLLS_STORAGE_KEY, skillRollData); } //console.log( "data", skillRollData[skill] ); if (skillRollData[skill].length === 0) { return; } const formatted = skillRollData[skill].map((x) => { return { rollType: x.rollType, rollValue: calculateSkillRoll( shortAttributes, skill, skillLevel, x.rollCalculation, x.modifier ), rollCalculation: x.rollCalculation, modifier: x.modifier, }; }); //console.log( {formatted} ); $(row) .find("td[class=roll_placeholder]") .replaceWith( `<td class="roll_placeholder"><table width="100%"><tbody>${formatted .map((x) => { const modifierString = x.modifier ? `<b>(${x.modifier})</b>` : ""; return `<tr onmouseover="return wodToolTip(this, '<b>${x.rollType}</b>: ${x.rollCalculation} ${modifierString}');"> <td align="left"> ${x.rollType} </td> <td align="right"> ${x.rollValue} </td> <td align="right"> <img alt="" border="0" src="/wod/css//skins/skin-8/images/icons/inf.gif"> </td> </tr>`; }) .join("")}</tbody></table></td>` ); }; await main(); })();