Greasy Fork is available in English.
Allows rolling from forge steel character sheets into roll20.
Od
// ==UserScript==
// @name Forged20
// @namespace jackpoll4100
// @version 1.2
// @description Allows rolling from forge steel character sheets into roll20.
// @author jackpoll4100
// @match https://andyaiken.github.io/forgesteel*
// @match https://app.roll20.net/*
// @match https://*.discordsays.com/*
// @icon https://raw.githubusercontent.com/jackpoll4100/Forged20/refs/heads/main/DS%20logo.png
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_addValueChangeListener
// ==/UserScript==
(function() {
'use strict';
if (!window.location.href.includes('forgesteel')){
window.forgesteelEnabled = false;
function forgesteelToggle(){
window.forgesteelEnabled = !window.forgesteelEnabled;
};
let forgesteelSettingsTemplate =
`<div id="forgesteelSettings" style="display: flex; flex-direction: row; justify-content: space-between;">
<input type="checkbox" id="forgesteelEnabled" title="Enables rolling from your Forge Steel character sheet in another tab.">
<input id="autoCheckLabel" style="margin: 5px 5px 5px 5px; width: 90%" disabled value="Enable rolls from Forge Steel" type="text" title="Enables rolling from your Forge Steel character sheet in another tab.">
</div>`;
function GM_onMessage(label, callback){
GM_addValueChangeListener(label, function(){
callback.apply(undefined, arguments[2]);
});
}
function execMacro(macro){
console.log('Forge Steel - Executing Macro: ', macro);
if (!window.forgesteelEnabled){
console.log('cancelling macro execution, forgesteel connection not enabled.');
return;
}
document.querySelectorAll('[title="Text Chat Input"]')[0].value = macro;
document.getElementById('chatSendBtn').click();
}
GM_onMessage('forgesteel-pipe', function(message) {
console.log('forgesteel message received: ', message);
if (message.includes('template')){
let cleanedString = message.split('---')[1];
execMacro(cleanedString);
}
});
function appendForgeSteelSettings(){
let uiContainer = document.createElement('div');
uiContainer.innerHTML = forgesteelSettingsTemplate;
document.getElementById('textchat-input').appendChild(uiContainer);
document.getElementById('forgesteelEnabled').addEventListener('click', forgesteelToggle);
}
function timer (){
if (document.getElementById('chatSendBtn')){
appendForgeSteelSettings();
}
else{
setTimeout(timer, 500);
}
}
setTimeout(timer, 0);
console.log('forgesteel listener registered');
}
else {
function GM_sendMessage(label){
GM_setValue(label, Array.from(arguments).slice(1));
}
console.log('sending open message');
GM_sendMessage('forgesteel-pipe', 'forgesteel opened');
let classMap = {
rollSelector: '.total > div.ant-statistic-content > span > span',
rollTitles: ['.roll-modal div.ant-statistic-title', '.modal-content .ability-panel > div.header-text-panel > div > div.header-text'],
rollButton: '.die-roll-panel > .ant-btn',
effectsSelector: '.ant-drawer .power-roll-row .effect',
criticalSuccess: '.ant-alert-success',
tierAlert: '.ant-alert-warning .ant-alert-message'
};
function fetchCharacter(callback)
{
let request = window.indexedDB.open('localforage');
request.onsuccess = async function(event) {
let db = event.target.result;
let store = db.transaction(['keyvaluepairs'],'readwrite').objectStore('keyvaluepairs');
store.get('forgesteel-heroes').onsuccess = function (event) {
console.log('Found the following characters: ', event.target.result);
const urlTokens = window.location.href.split('/');
let stopLooking = false;
for (const character of event.target.result)
{
if (character.id == urlTokens[urlTokens.length - 1] && !stopLooking)
{
stopLooking = true;
callback(character);
}
}
};
};
}
function rollWatcher(){
fetchCharacter((character) => {
const roll = document.querySelector(classMap.rollSelector)?.innerHTML;
let rollTitle = '';
classMap.rollTitles.forEach((selector)=>{
rollTitle = document.querySelector(selector)?.innerHTML ? document.querySelector(selector).innerHTML : rollTitle;
});
const tierAlert = document.querySelector(classMap.tierAlert);
let modifierText = '';
let rollTier = roll < 12 ? 1 : roll < 17 ? 2 : 3;
if (tierAlert?.innerHTML?.includes('down') && rollTier > 1){
rollTier --;
modifierText = '(Tier was decreased by a Double Bane)';
}
else if (tierAlert?.innerHTML?.includes('up') && rollTier < 3){
rollTier ++;
modifierText = '(Tier was increased by a Double Edge)';
}
const effects = document.querySelectorAll(classMap.effectsSelector);
const constructedEffect = effects.length === 3 ? `{{effect=${ effects[rollTier - 1].innerHTML } ${ modifierText }}}` : '';
const constructedMessage = `&{template:default} {{name=${ character?.name ? `${ character.name }` : '' }}} ${ rollTitle ? `{{type=${ rollTitle }}}` : '' } {{result=${ roll } ${ document.querySelector(classMap.criticalSuccess) ? '(Critical Success)' : ''}}} ${ constructedEffect }`;
console.log('Sending message to roll20: ', constructedMessage);
GM_sendMessage('forgesteel-pipe', `${ Math.random() }---` + constructedMessage);
});
}
const bodyTarget = document.querySelector('body');
const config = { attributes: false, childList: true, subtree: true };
const listenerSetup = ()=>{
const foundElement = document.querySelector(classMap.rollButton);
if (foundElement && !foundElement.getAttribute('listener-applied')){
foundElement.setAttribute('listener-applied', true);
foundElement.addEventListener('click', ()=>{ setTimeout(rollWatcher, 100); });
}
};
const observer = new MutationObserver(listenerSetup);
observer.observe(bodyTarget, config);
}
})();