您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Disables villages with which you have a peace treaty in spy/bingo/attack menus.
// ==UserScript== // @name BvS Peace Treaty Helper // @namespace ns://yesterday.BvS.local/ // @description Disables villages with which you have a peace treaty in spy/bingo/attack menus. // @include http://*animecubed.com/billy/bvs/village.html // @include http://*animecubed.com/billy/bvs/bingo.html // @include http://*animecubed.com/billy/bvs/villageattack.html // @include http://*animecubed.com/billy/bvs/villagespy.html // @include http://*animecubed.com/billy/bvs/villagespyreport.html // @version 1.2.1 // @history 1.2.1 [2014-09-14] Cleaner, more generic compatibility hack. // @history 1.2.0 [2014-09-13] Scriptish 0.1.12 compatibility hack. // @history 1.1.0 [2014-09-12] Changed list formatting to use "Pax List". // @history 1.0.5 [2014-09-12] Enabled multiball. // @history 1.0.4 [2014-09-12] Changed list formatting to double-parenthesis. // @history 1.0.3 [2014-09-12] Items are now removed from the list entirely, rather than merely being disabled. // @history 1.0.2 [2014-09-11] Added spy report modification. // @history 1.0.1 [2014-09-09] Changed list formatting from bracketed (impossible) to parenthesized. // @history 1.0.0 [2014-09-08] Initial version. // @license WTFPL (http://wtfpl.org/) // @grant GM_setValue // @grant GM_getValue // ==/UserScript== ///////////////////////// // Options ///////////////////////// // If the announcement parser fails badly for one or more of your alts, // you can override it on a per-alt basis. // For example, if you have three alts named "Main", "Alt 1", and "Alt 2": // // const overrideList = { // "Main": ["VillageOne", "Village 2", "Village3"], // override: peace with just these three villages // "Alt 1": null, // don't override at all (this can be omitted) // "Alt 2": [], // override: peace with no villages // }; // const overrideList = null; // Set this to "true" to see the results of the announcement parser in the console. const logParseToConsole = false; ///////////////////////// // Main ///////////////////////// (function(outerGMGet,outerGMSet) { try { // Multiball! Handle multiple alts. const playerName = document.evaluate( "//input[@name='player']", document, null, XPathResult.ANY_UNORDERED_NODE_TYPE, null ).singleNodeValue.value; // Get values on a per-alt basis. Non-Greasemonkey script engines typically // can't handle raw array values, so we also manually JSONify our data. function GM_setValue(key, val) { return outerGMSet(playerName + ";" + key, JSON.stringify(val)); } function GM_getValue(key, def) { key = playerName + ";" + key; const val = outerGMGet(key, def); if (val) try { return JSON.parse(val); } catch (e) { console.log({err: "GM_getValue JSON parse failure!", key: key, val: val}); } return def; } function parseAnnouncement() { // If the user has manually specified a list of villages, use that instead of // checking the village announcement. if (overrideList && overrideList[playerName]) return overrideList[playerName]; // The kage may specify one or more lists of peaceable villages as a parenthesized, // comma-separated list in the village announcement, prefixed with "Pax List:", // e.g.: // // Pax List: (Village1, Village2, Vil Lage3, Etc) // // Spacing and formatting is mostly ignored, but capitalization (except for the // initial "[Pp]ax [Ll]ist:") is not. Do not use quotation marks. const announcement = document.getElementById("annul"); const match = announcement.textContent.match(/[Pp]ax [Ll]ist\:\s*\((\s*[\w ]+\s*,)*[\w ]+\)/g); if (match) { return match.map(function(L){return L.match(/\((.*)\)/)[1].split(",");}) .reduce(function(a,b){return a.concat(b)}) .map(function(s){return s.trim();}); } // Otherwise, we have to guess. In so doing we just pull ALL the potential village // names out of the announcement text. // // Spaces are potentially part of a village name, which is more helpful than harmful: // it means we're not typically going to have to worry about (e.g.) "That Village" // being triggered by a sentence like "That won't help." Instead, the detected strings // will be "That won" and "t help". // // There are still things you can name a village to sneak your way onto this list: // "Invading" or "Sannin", for example, will often work. Of course, players can always // just turn off the script... const textblocksSnapshot = document.evaluate( '//*[@id="annul"]//text()', document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); const textsArray = (function(){ var a = []; for (var i = 0; i < textblocksSnapshot.snapshotLength; ++i) { const node = textblocksSnapshot.snapshotItem(i); a = a.concat(node.textContent.split(/[^\w ]/).map(function(e){return e.trim();})); } // filter out "and " if the kage uses the Oxford comma a = a.map(function(s){ if (s.match(/^and /)) return s.slice(4); return s; }); // filter out "and " if the kage _doesn't_ use the Oxford comma, or in two-item lists // (this prevents villages with the five-character string " and " in their names from // being detected, but at time of writing no such villages exist) a = a.map(function(s){ return s.split(/ and /g); }) .reduce(function(A,B){ B.forEach(function(v){A.push(v);}); return A;},[]); // filter out null strings and strings too long to be a village name a = a.filter(function(e){return e.length > 0 && e.length <= 10}); // filter out duplicate strings (keep only one instance) a = Object.getOwnPropertyNames(a.reduce(function(o,k){o[k]++;return o;},({}))).sort(); return a; })(); // As a special case, we also check to see if there's a village set for invasion // and, if it's there, remove it from the list. const invadeMatch = document.body.textContent.match( /Planning to Invade: (.*) Village/); if (invadeMatch && invadeMatch[1]) { const invadeIndex = textsArray.indexOf(invadeMatch[1]); if (invadeIndex != -1) textsArray.splice(invadeIndex, 1); } return textsArray; } function disableInputsInForm(theForm, verbotenList) { const inputNodes = Array.prototype.filter.call( theForm.elements, function(n){return n.type != "hidden"} ); const removedVillages = []; inputNodes.forEach(function(input) { if (input.type == "hidden") return; if (verbotenList.indexOf(input.value) == -1) return; // collect all the text used to label this input const labels = (function(n) { var a = []; while (true) { a.push(n); n = n.nextSibling; if (!n) break; if (n.tagName && ({input:1})[n.tagName.toLowerCase()]) break; }; return a; })(input); labels.forEach(function(n){ n.parentNode.removeChild(n); }); // replace real spaces in village name with nbsp removedVillages.push(input.value.replace(/ /g, ' ')); }); // add note concerning removed villages if (removedVillages.length > 0) { const container = theForm.parentNode; // outer div: border const div = document.createElement("div"); const dStyle = div.style; const cStyle = getComputedStyle(container); // copy border style, width ["Left","Right","Bottom"].forEach(function(a){ ["Color","Style","Width"].forEach(function(b){ const k = "border" + a + b; dStyle[k] = cStyle[k]; }); }); dStyle.width = cStyle.width; // inner div: text styling, text div.innerHTML = "<div style='color: #040; font: bold italic x-small sans-serif;\ margin: 0px 5px; text-align: right'>TEXT</span>"; // add actual village list (note: NBSP in "Not shown") div.firstChild.firstChild.textContent = ( "Not shown: " + removedVillages.sort().join(", ") ); // insert after container container.parentNode.insertBefore(div, container.nextSibling); } } function disableFromSpyReport(verbotenList) { const nameTexts = (function(){ const a = []; const ss = document.evaluate("//tr/td[2]/a/text()", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); for (var i = 0; i < ss.snapshotLength; ++i) a.push(ss.snapshotItem(i)); return a; })(); nameTexts.forEach(function(t){ if (verbotenList.indexOf(t.textContent.trim()) == -1) return; const td = t.parentNode.parentNode.parentNode; td.style.textDecoration = "line-through"; td.style.color = "#888" t.parentNode.style.color = "#888"; }); }; // main function (function(){ "use strict"; const pageURL = document.location.href; if (pageURL.endsWith("/village.html")) { // Get names from announcement const candidateNames = parseAnnouncement(); if (logParseToConsole) console.log(candidateNames); GM_setValue("peaceList", candidateNames); return; } const verbotenList = GM_getValue("peaceList", []); if (pageURL.endsWith("/bingo.html")) { disableInputsInForm(document.forms.lookinto, verbotenList); return; } if (pageURL.endsWith("/villageattack.html")) { disableInputsInForm(document.forms.lookinto, verbotenList); return; } if (pageURL.endsWith("/villagespy.html")) { disableInputsInForm(document.forms.lookinto, verbotenList); disableInputsInForm(document.forms.multili, verbotenList); return; } if (pageURL.endsWith("/villagespyreport.html")) { disableFromSpyReport(verbotenList); return; } })(); } catch (e) { console.log(e); } })(GM_getValue, GM_setValue);