您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
Adds a hero selection dropdown at the top of all World of Dungeons pages. Not all skins are supported.
// ==UserScript== // @name WoD 角色选择下拉框 // @namespace com.dobydigital.userscripts.wod // @description Adds a hero selection dropdown at the top of all World of Dungeons pages. Not all skins are supported. // @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/hero-switcher-dropdown.user.js // @include http*://*.world-of-dungeons.*/wod/spiel/* // @include http*://*.world-of-dungeons.* // @icon http://info.world-of-dungeons.net/wod/css/WOD.gif // @require https://gcore.jsdelivr.net/jquery/1.10.2/jquery.min.js // @modifier Christophero // @version 2023.06.25.1 // ==/UserScript== (function () { ("use strict"); const targetElement = $("td.gadget_table_cell.merged"); if (!targetElement.length) { console.error( `Hero Selector Dropdown Userscript: Unable to find target element for dropdown.`, targetElement ); return; } const SESSION_HERO_ID_KEY = "session_hero_id"; const GOTO_KEY = "goto"; const HERO_LIST_STORAGE_KEY = "com.dobydigital.userscripts.wod.heroswitcherdropdown.herolist"; const USERSCRIPT_CONTAINER_ID = "xaerodegreaz_userscript_hero_select_container"; const DROPDOWN_ID = "xaerodegreaz_userscript_hero_select"; const storage = window.localStorage; const heroList = load(HERO_LIST_STORAGE_KEY); (function Main() { try { if (!heroList) { refreshHeroList(); } else { displayHeroSelector(heroList); } } catch (e) { console.error("Hero Selector Dropdown Userscript: Uncaught exception", e); } })(); function load(key) { try { const raw = storage.getItem(HERO_LIST_STORAGE_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 refreshHeroList() { $(`div[id=${USERSCRIPT_CONTAINER_ID}]`).remove(); fetch("/wod/spiel/settings/heroes.php") .then(function (res) { return res.text(); }) .then(function (text) { const jq = $(text); const inputs = jq.find("input[name=FIGUR]"); if (!inputs.length) { console.error( 'Hero Selector Dropdown Userscript: Unable to find hero list inputs on "heroes" page.', inputs ); return; } const heroes = inputs .map(function () { const characterId = $(this).val(); const characterName = $(this).parent().find("a").text(); return { characterId, characterName }; }) .toArray(); save(HERO_LIST_STORAGE_KEY, heroes); displayHeroSelector(heroes); }); } function displayHeroSelector(heroes) { //# We could process the query parameters ahead of time, but, for a small amount of efficiency, I think it's best to defer so it can be done asynchronously. const rawVars = getUrlVars(); //# We want to capture all query parameters so they can be passed when changing users. We don't want the session_hero_id, because we will be replacing that. const gotoFlag = rawVars[GOTO_KEY]; const urlVars = rawVars .filter((key) => key !== SESSION_HERO_ID_KEY) .filter((key) => key != GOTO_KEY); let currentSessionHeroId = rawVars[SESSION_HERO_ID_KEY]; //# Location without any query parameters let basePath = window.location.href.split("?")[0]; if (window.location.pathname == "/") { basePath += "wod/spiel/news/news.php"; } const remainingQueryParameters = urlVars .map((x) => `${x}=${rawVars[x]}`) .join("&"); const querystring = urlVars.length > 0 ? `&${remainingQueryParameters}` : ""; //# Begin generating new DOM elements. const newDiv = $( `<div id="${USERSCRIPT_CONTAINER_ID}" class="gadget"><label for="${DROPDOWN_ID}">切换英雄: </label></div>` ); if (!currentSessionHeroId) { currentSessionHeroId = $('input[name="session_hero_id"]').val(); } const index = heroes.findIndex( (hero) => hero.characterId == currentSessionHeroId ); const addVal = gotoFlag == "prevactivehero" ? -1 : gotoFlag == "nextactivehero" ? 1 : 0; const realHeroId = heroes[(index + heroes.length + addVal) % heroes.length].characterId; const options = heroes .map((hero) => { const selected = hero.characterId === realHeroId ? "selected" : ""; return `<option ${selected} value="${basePath}?${SESSION_HERO_ID_KEY}=${hero.characterId}${querystring}">${hero.characterName}</option>`; }) .join(""); const select = $(`<select id="${DROPDOWN_ID}">${options}</select>`).change( function () { window.location.href = $(this).val(); } ); const refreshButton = $( '<button class="button" title="Refresh Hero List">🗘</button>' ).click(function () { refreshHeroList(); }); newDiv.append(select, refreshButton); targetElement.prepend(newDiv); } function getHiddenInputValuesForSubPages() { return { //# items.php - cellar, treasure vault, etc view: $("input[type=hidden][name=view]").val(), //# trade.php - market, auctions, etc show: $("input[type=hidden][name=show]").val(), }; } function getUrlVars() { const vars = []; const href = window.location.href; if (href.includes("?") && href.includes("&")) { const hashes = href .slice(window.location.href.indexOf("?") + 1) .split("&"); for (let i = 0; i < hashes.length; i++) { const hash = hashes[i].split("="); vars.push(hash[0]); vars[hash[0]] = hash[1]; } } //# After performing a search on any of the items pages (cellar, treasure vault), the 'view' query parameter goes missing, so switching to another character //# will redirect to the general storage. We want to be able to capture that view value from the form inputs, and set the 'view' query param //# if it isn't already present. //# We see the same behaviour on the market / auction pages, only the query param is 'show' instead of view. const subPages = getHiddenInputValuesForSubPages(); Object.keys(subPages).map((key) => { if (subPages[key] && !vars[key]) { vars.push(key); vars[key] = subPages[key]; } }); return vars; } })();