// ==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();
})();