您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Calculates skill rolls, and adds a new table column on the skills page.
// ==UserScript== // @name [WoD] Display Skill Rolls // @namespace com.dobydigital.userscripts.wod // @version 2021.06.27.8 // @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.net/wod/spiel/hero/skills* // @icon http://info.world-of-dungeons.net/wod/css/WOD.gif // @require https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js // @grant GM.xmlHttpRequest // ==/UserScript== (async function () { 'use strict'; const attributeShortNames = { Strength: 'st', Constitution: 'co', Intelligence: 'in', Dexterity: 'dx', Charisma: 'ch', Agility: 'ag', Perception: 'pe', Willpower: 'wi' } const attributesToShortName = ( heroAttributes ) => { const obj = {}; Object.keys( heroAttributes ).forEach( key => { obj[attributeShortNames[key]] = heroAttributes[key] } ); return obj; } 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 ) => { const jq = $( data ); const markers = jq.find( 'li' ).map( function () { const match = /^The (?<rollType>.+)roll is: (?<rollCalculation>.+?)( \((?<modifier>[+\-]\d*%?)\))?$/g.exec( $( this ).text() ); if ( !match ) { return; } //console.log( {match} ); // @ts-ignore const {rollType, rollCalculation, modifier} = match.groups; //console.log( {rollType, rollCalculation, modifier} ); return {rollType, rollCalculation, modifier}; } ).toArray(); //console.log( {markers} ); return markers; } function calculateSkillRoll( heroAttributes, skillName, skillLevel, rollCalculation, modifier ) { //console.log( {heroAttributes, skillName, skillLevel, rollCalculation, modifier} ); let replaced = rollCalculation.replaceAll( skillName, skillLevel ).trim(); Object.keys( heroAttributes ).forEach( key => { replaced = replaced.replaceAll( key, heroAttributes[key] ); } ) replaced = replaced.replaceAll( 'x', '*' ); //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'; 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 ); } } const main = async () => { 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 = attributesToShortName( heroAttributes ); const skillRollData = load( SKILL_ROLLS_STORAGE_KEY ) || {}; //console.log( {heroAttributes, shortAttributes} ); //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 ); } } ); } ); 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(); })();