Better Roll20 - CS

Just a quality of life feature set for your Roll20 Character Sheet

// ==UserScript==
// @name         Better Roll20 - CS
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  Just a quality of life feature set for your Roll20 Character Sheet
// @author       Mathy
// @match        https://www.tampermonkey.net/scripts.php?ext=dhdg&updated=true&version=5.2.2
// @match        https://app.roll20.net/editor/character/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=tampermonkey.net
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';
    document.querySelector('#dialog-window > div > ul > li.active > a').style.background = "red"

    console.log("Loading Better Roll 20 - CS...")

    const coinInputs = document.querySelectorAll(".equipment .money .coin input")
    coinInputs.forEach((input) => {
        input.addEventListener('keydown', autoCalculate);
    })

    const healthInput = document.querySelector("input[name=attr_hp]")
    healthInput.type = "text"
    healthInput.addEventListener('keydown', autoCalculate)


    function autoCalculate(e) {
        if (e.key === 'Enter') {
            const newValue = calculate(e.target.value)
            e.target.value = newValue
        }
    }

    window.calculator = calculate
    // math eval from: https://gist.github.com/tkrotoff/b0b1d39da340f5fc6c5e2a79a8b6cec0
    const minus0Hack = value => (Object.is(value, -0) ? "-0" : value)

    const operators = {
        "+": {
            func: (x, y) => `${minus0Hack(Number(x) + Number(y))}`,
            precedence: 1,
            associativity: "left",
            arity: 2
        },
        "-": {
            func: (x, y) => `${minus0Hack(Number(x) - Number(y))}`,
            precedence: 1,
            associativity: "left",
            arity: 2
        },
        "*": {
            func: (x, y) => `${minus0Hack(Number(x) * Number(y))}`,
            precedence: 2,
            associativity: "left",
            arity: 2
        },
        "/": {
            func: (x, y) => `${minus0Hack(Number(x) / Number(y))}`,
            precedence: 2,
            associativity: "left",
            arity: 2
        },
        "%": {
            func: (x, y) => `${minus0Hack(Number(x) % Number(y))}`,
            precedence: 2,
            associativity: "left",
            arity: 2
        },
        "^": {
            // Why Math.pow() instead of **?
            // -2 ** 2 => "SyntaxError: Unary operator used immediately before exponentiation expression..."
            // Math.pow(-2, 2) => -4
            // eslint-disable-next-line prefer-exponentiation-operator, no-restricted-properties
            func: (x, y) => `${minus0Hack(Math.pow(Number(x), Number(y)))}`,
            precedence: 3,
            associativity: "right",
            arity: 2
        }
    }
    const operatorsKeys = Object.keys(operators)

    const functions = {
        min: {
            func: (x, y) => `${minus0Hack(Math.min(Number(x), Number(y)))}`,
            arity: 2
        },
        max: {
            func: (x, y) => `${minus0Hack(Math.max(Number(x), Number(y)))}`,
            arity: 2
        },
        sin: { func: x => `${minus0Hack(Math.sin(Number(x)))}`, arity: 1 },
        cos: { func: x => `${minus0Hack(Math.cos(Number(x)))}`, arity: 1 },
        tan: { func: x => `${minus0Hack(Math.tan(Number(x)))}`, arity: 1 },
        log: { func: x => `${Math.log(Number(x))}`, arity: 1 } // No need for -0 hack
    }
    const functionsKeys = Object.keys(functions)

    const top2 = stack => stack[stack.length - 1]

    /**
 * Shunting yard algorithm: converts infix expression to postfix expression (reverse Polish notation)
 *
 * Example: ['1', '+', '2'] => ['1', '2', '+']
 *
 * https://en.wikipedia.org/wiki/Shunting_yard_algorithm
 * https://github.com/poteat/shunting-yard-typescript
 * https://blog.kallisti.net.nz/2008/02/extension-to-the-shunting-yard-algorithm-to-allow-variable-numbers-of-arguments-to-functions/
 */
    function shuntingYard(tokens) {
        const output = new Array()
        const operatorStack = new Array()

        for (const token of tokens) {
            if (functions[token] !== undefined) {
                operatorStack.push(token)
            } else if (token === ",") {
                while (operatorStack.length > 0 && top2(operatorStack) !== "(") {
                    output.push(operatorStack.pop())
                }
                if (operatorStack.length === 0) {
                    throw new Error("Misplaced ','")
                }
            } else if (operators[token] !== undefined) {
                const o1 = token
                while (
                    operatorStack.length > 0 &&
                    top2(operatorStack) !== undefined &&
                    top2(operatorStack) !== "(" &&
                    (operators[top2(operatorStack)].precedence > operators[o1].precedence ||
                     (operators[o1].precedence ===
                      operators[top2(operatorStack)].precedence &&
                      operators[o1].associativity === "left"))
                ) {
                    output.push(operatorStack.pop()) // o2
                }
                operatorStack.push(o1)
            } else if (token === "(") {
                operatorStack.push(token)
            } else if (token === ")") {
                while (operatorStack.length > 0 && top2(operatorStack) !== "(") {
                    output.push(operatorStack.pop())
                }
                if (operatorStack.length > 0 && top2(operatorStack) === "(") {
                    operatorStack.pop()
                } else {
                    throw new Error("Parentheses mismatch")
                }
                if (functions[top2(operatorStack)] !== undefined) {
                    output.push(operatorStack.pop())
                }
            } else {
                output.push(token)
            }
        }

        // Remaining items
        while (operatorStack.length > 0) {
            const operator = top2(operatorStack)
            if (operator === "(") {
                throw new Error("Parentheses mismatch")
            } else {
                output.push(operatorStack.pop())
            }
        }

        return output
    }

    /**
 * Evaluates reverse Polish notation (RPN) (postfix expression).
 *
 * Example: ['1', '2', '+'] => 3
 *
 * https://en.wikipedia.org/wiki/Reverse_Polish_notation
 * https://github.com/poteat/shunting-yard-typescript
 */
    function evalReversePolishNotation(tokens) {
        const stack = new Array()

        const ops = { ...operators, ...functions }

        for (const token of tokens) {
            const op = ops[token]

            if (op !== undefined) {
                const parameters = []
                for (let i = 0; i < op.arity; i++) {
                    parameters.push(stack.pop())
                }
                stack.push(op.func(...parameters.reverse()))
            } else {
                stack.push(token)
            }
        }

        if (stack.length > 1) {
            throw new Error("Insufficient operators")
        }

        return Number(stack[0])
    }

    /**
 * Breaks a mathematical expression into tokens.
 *
 * Example: "1 + 2" => [1, '+', 2]
 *
 * https://gist.github.com/tchayen/44c28e8d4230b3b05e9f
 */
    function tokenize(expression) {
        // "1  +" => "1 +"
        const expr = expression.replace(/\s+/g, " ")

        const tokens = []

        let acc = ""
        let currentNumber = ""

        for (let i = 0; i < expr.length; i++) {
            const c = expr.charAt(i)
            const prev_c = expr.charAt(i - 1) // '' if index out of range
            const next_c = expr.charAt(i + 1) // '' if index out of range

            const lastToken = top2(tokens)

            const numberParsingStarted = currentNumber !== ""

            if (
                // 1
                /\d/.test(c) ||
                // Unary operator: +1 or -1
                ((c === "+" || c === "-") &&
                 !numberParsingStarted &&
                 (lastToken === undefined ||
                  lastToken === "," ||
                  lastToken === "(" ||
                  operatorsKeys.includes(lastToken)) &&
                 /\d/.test(next_c))
            ) {
                currentNumber += c
            } else if (c === ".") {
                if (numberParsingStarted && currentNumber.includes(".")) {
                    throw new Error(`Double '.' in number: '${currentNumber}${c}'`)
                } else {
                    currentNumber += c
                }
            } else if (c === " ") {
                if (/\d/.test(prev_c) && /\d/.test(next_c)) {
                    throw new Error(`Space in number: '${currentNumber}${c}${next_c}'`)
                }
            } else if (functionsKeys.includes(acc + c)) {
                acc += c
                if (!functionsKeys.includes(acc + next_c)) {
                    tokens.push(acc)
                    acc = ""
                }
            } else if (
                operatorsKeys.includes(c) ||
                c === "(" ||
                c === ")" ||
                c === ","
            ) {
                if (
                    operatorsKeys.includes(c) &&
                    !numberParsingStarted &&
                    operatorsKeys.includes(lastToken)
                ) {
                    throw new Error(`Consecutive operators: '${lastToken}${c}'`)
                }
                if (numberParsingStarted) {
                    tokens.push(currentNumber)
                }
                tokens.push(c)
                currentNumber = ""
            } else {
                acc += c
            }
        }

        if (acc !== "") {
            throw new Error(`Invalid characters: '${acc}'`)
        }

        // Add last number to the tokens
        if (currentNumber !== "") {
            tokens.push(currentNumber)
        }

        // ['+', '1'] => ['0', '+', '1']
        // ['-', '1'] => ['0', '-', '1']
        if (tokens[0] === "+" || tokens[0] === "-") {
            tokens.unshift("0")
        }

        return tokens
    }

    function calculate(expression) {
        const tokens = tokenize(expression)
        const rpn = shuntingYard(tokens)
        return evalReversePolishNotation(rpn)
    }

})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址