AQ Ruffle Enhance

A set of enhancements for serious AdventureQuest play!

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         AQ Ruffle Enhance
// @namespace    http://tampermonkey.net/
// @version      1.1.0
// @description  A set of enhancements for serious AdventureQuest play!
// @author       You
// @match        https://*.battleon.com/game/web*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=battleon.com
// @grant        none
// @license      MIT
// ==/UserScript==

const gameSwf = document.getElementsByClassName("base")[0].src;
const baseSwf = window.location.origin + '/game/flash/';
const MAX_PLAYERS = 5;
const MENU_BAR_HEIGHT = 20;

const serverOptions = [
    { value: 'aq', name: 'aq' },
    { value: 'guardian', name: 'guardian' },
    { value: 'new', name: 'new' }
];

const renderOptions = [
    { value: 'webgpu', name: "WebGPU" },
    { value: 'wgpu-webgl', name: "WGPU" },
    { value: 'webgl', name: "WebGL" },
    { value: 'canvas', name: 'Canvas2D' }
];

const links = [
    { url: 'https://account.battleon.com/', name: 'Manage Account' },
    { url: 'https://aq-char-info.firebaseapp.com/', name: 'Char Info' },
    { url: 'https://battleon.com/', name: 'Home' },
    { url: 'https://adventurequestwiki.fandom.com/wiki/AdventureQuestWiki_Wiki', name: 'Wiki' },
    { url: 'https://forums2.battleon.com/f/tt.asp?forumid=1', name: 'Forums' },
    { url: 'https://discord.com/invite/FQsFCGV', name: 'Discord' },
];

const defaultPlayerConfig = {
    url: gameSwf,
    base: baseSwf,
    preferredRenderer: "webgl",
    warnOnUnsupportedContent: false,
};

const createElement = (tag, options = {}) => Object.assign(document.createElement(tag), options);

const renderDropdown = (options, selectOptions = {}) => {
    const select = createElement('select', selectOptions);
    options.forEach((option) => {
        const selectOption = createElement('option', {
            value: option.value,
            text: option.name,
            selected: defaultPlayerConfig.preferredRenderer === option.value
        });
        select.appendChild(selectOption);
    });

    return select;
};

const renderOptionGroup = (dropdown, button) => {
    const optionGroup = createElement('span');
    optionGroup.appendChild(dropdown);
    optionGroup.appendChild(button);

    return optionGroup;
};

const resizeWindows = () => {
    const container = document.getElementById('container');
    container.style.width = window.innerWidth;
    const playerContainers = Array.from(document.getElementsByClassName('player-container'));

    if (playerContainers.length === 0) {
        addWindow();
        resizeWindows();
        return;
    }

    // Thanks to nivp for the window resize algorithm!
    const availableHeight = window.innerHeight - MENU_BAR_HEIGHT;
    let max_width = 0;
    let max_height = 0;

    // maximize cell size through column search
    for (let i = 1; i <= playerContainers.length; i++) {
        let temp_height = availableHeight / Math.ceil(playerContainers.length / i);
        let temp_width = temp_height * 4 / 3;
        if (temp_width * i > (window.innerWidth * i) / (i + 1) && temp_width * i <= window.innerWidth) {
            max_width = temp_width;
            max_height = temp_height;
        }
    }

    // maximize cell size through row search
    for (let i = 1; i <= playerContainers.length; i++) {
        let temp_width = window.innerWidth / Math.ceil(playerContainers.length / i);
        let temp_height = temp_width * 3 / 4;
        if (
            temp_height * i > (availableHeight * i) / (i + 1) &&
            temp_height * i <= availableHeight
        ) {
            max_width = temp_width;
            max_height = temp_height;
        }
    }

    playerContainers.forEach((player) => {
        player.style.width = max_width;
        player.style.height = max_height;

        const rufflePlayer = player.getElementsByTagName('ruffle-player')[0];
        const playerMenuBar = player.getElementsByClassName('player-menu-bar')[0];

        rufflePlayer.style.height = max_height;
        rufflePlayer.style.width = max_height * 4 / 3;
        playerMenuBar.style.width = max_height * 4 / 3;
    });
};

const addWindow = () => {
    const container = document.getElementById('container');
    const player = window.RufflePlayer.newest().createPlayer();
    player.style = 'margin: 0 auto;';

    const playerContainer = createElement('div', {
        className: 'player-container',
        style: 'display: flex; flex-direction: column; align-items: center;'
    });
    const playerMenuBar = createElement('div', {
        className: 'player-menu-bar',
        style: `height: ${MENU_BAR_HEIGHT}px; background-color: grey; display: flex; justify-content: center;`
    });


    const killWindowButton = createElement('button', {
        innerText: 'x',
        onclick: () => {
            playerContainer.remove();
            document.getElementById('add-button').disabled = false;
            document.getElementById('add-all-button').disabled = false;
            resizeWindows();
        }
    });

    const refreshPlayerButton = createElement('button', {
        innerText: 'Refresh',
        onclick: () => player.reload()
    });

    const dropdown = renderDropdown(renderOptions, { className: 'player-dropdown' });
    const changeRendererButton = createElement('button', {
        innerText: 'Change Renderer',
        onclick: () => {
            const selectedValue = dropdown.value;
            const playerConfig = Object.assign({}, defaultPlayerConfig);
            playerConfig.preferredRenderer = selectedValue;
            player.load(playerConfig);
        }
    });

    const rendererGroup = renderOptionGroup(dropdown, changeRendererButton);

    playerMenuBar.appendChild(rendererGroup);
    playerMenuBar.appendChild(refreshPlayerButton);
    playerMenuBar.appendChild(killWindowButton);

    playerContainer.appendChild(playerMenuBar);
    playerContainer.appendChild(player);
    container.appendChild(playerContainer);

    player.load(defaultPlayerConfig);
};

const addAllWindows = () => {
    const playerContainers = document.getElementsByTagName('ruffle-player');
    for (let i = playerContainers.length; i < MAX_PLAYERS; i++) {
        addWindow();
    }
};

const renderMenuBar = () => {
    const menuBar = createElement('div', {
        style: `height: ${MENU_BAR_HEIGHT}px; width: 100%; display: flex; justify-content: space-between;`
    });

    const addButton = createElement("button", {
        id: 'add-button',
        innerText: '+Add',
        onclick: () => {
            addWindow();
            const playerContainers = document.getElementsByTagName('ruffle-player');
            document.getElementById('add-button').disabled = playerContainers.length === MAX_PLAYERS;
            document.getElementById('add-all-button').disabled = playerContainers.length === MAX_PLAYERS;
            resizeWindows();
        }
    });

    const addAllButton = createElement("button", {
        id: 'add-all-button',
        innerText: `+Max (${MAX_PLAYERS})`,
        onclick: () => {
            addAllWindows();
            document.getElementById('add-button').disabled = true;
            addAllButton.disabled = true;
            resizeWindows();
        }
    });

    const killAllButton = createElement('button', {
        id: 'kill-all-button',
        innerText: 'Kill Windows',
        onclick: () => {
            const playerContainers = Array.from(document.getElementsByClassName('player-container'));
            playerContainers.forEach((playerContainer) => {
                playerContainer.remove();
            });
            document.getElementById('add-button').disabled = false;
            document.getElementById('add-all-button').disabled = false;
            addWindow();
            resizeWindows();
        }
    });

    const rendererDropdown = renderDropdown(renderOptions);
    const changeRendererButton = createElement('button', {
        innerText: 'Change Renderers',
        onclick: () => {
            const selectedValue = rendererDropdown.value;
            const players = Array.from(document.getElementsByTagName('ruffle-player'));
            players.forEach((player) => {
                const playerConfig = Object.assign({}, defaultPlayerConfig);
                playerConfig.preferredRenderer = selectedValue;
                defaultPlayerConfig.preferredRenderer = selectedValue;
                player.load(playerConfig);
            });

            const playerDropdowns = Array.from(document.getElementsByClassName('player-dropdown'));
            playerDropdowns.forEach((dropdown) => {
                dropdown.value = selectedValue
            });
        }
    });

    const rendererOptionsGroup = renderOptionGroup(rendererDropdown, changeRendererButton);

    const goToServer = createElement('button', {
        textContent: 'New Tab',
        onclick: () => {
            const select = document.getElementById('server-select');
            window.open(`https://${select.value}.battleon.com/game/web`);
        }
    });

    const dropdown = renderDropdown(serverOptions, {
        id: 'server-select'
    });

    const buttonGroup = createElement('div');
    buttonGroup.appendChild(addButton);
    buttonGroup.appendChild(addAllButton);
    buttonGroup.appendChild(killAllButton);

    const linksGroup = createElement('div');
    links.forEach((link, index) => {
        const newLink = createElement('a', {
            textContent: link.name,
            href: link.url,
            target: '_blank',
            rel: 'noopener',
            style: 'font-size: 16px;'
        });

        linksGroup.appendChild(newLink);
        if (index !== links.length - 1) {
            const sep = createElement('span', {
                textContent: '|',
                style: 'font-size: 16px; margin: 5px;'
            });

            linksGroup.appendChild(sep);
        }
    });

    const selectGroups = createElement('div');
    const serverOptionsGroup = renderOptionGroup(dropdown, goToServer);

    selectGroups.appendChild(rendererOptionsGroup);
    selectGroups.appendChild(serverOptionsGroup);

    menuBar.appendChild(buttonGroup);
    menuBar.appendChild(linksGroup);
    menuBar.appendChild(selectGroups);

    const playersGroup = createElement('div', {
        id: "container",
        style: 'display: flex; flex-wrap: wrap; justify-content: center;'
    });

    document.body.appendChild(playersGroup);
    playersGroup.appendChild(menuBar);
}

const checkForRuffle = () => {
    const checker = setInterval(() => {
        if (window.RufflePlayer && window.RufflePlayer.invoked) {
            clearInterval(checker);

            window.addEventListener('resize', () => {
                let timer;
                if (timer) {
                    clearTimeout(timer);
                }
                timer = setTimeout(() => {
                    resizeWindows();
                }, 250);
            });

            document.body.style.margin = 0;
            document.getElementById('main').remove();

            renderMenuBar();
            addWindow();
            resizeWindows();

            window.onbeforeunload = (e) => {
                return 'Are you sure you want to quit?'
            }
        }
    }, 100);

    setTimeout(() => {
        clearInterval(checker);
        if (!window.RufflePlayer && window.confirm('I could not detect the Ruffle Plugin. Would you like to attempt importing Ruffle?')) {
            const script = createElement('script', {
                type: 'text/javascript',
                src: 'https://unpkg.com/@ruffle-rs/ruffle'
            });
            document.getElementsByTagName('head')[0].appendChild(script);
            checkForRuffle();
        }
    }, 2000);
}

(function() {
    'use strict';
    checkForRuffle();
})();