R00t Success Chance

Calculates success chance for r00t field actions.

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name           R00t Success Chance
// @description    Calculates success chance for r00t field actions.
// @namespace      http://userscripts.org/users/dtkarlsson
// @include        http*://*animecubed.com/billy/bvs/villagefields*
// @include        http*://*animecubedgaming.com/billy/bvs/villagefields*
// @licence        MIT; http://www.opensource.org/licenses/mit-license.php
// @copyright      2009, Daniel Karlsson
// @version        0.12.0
// @history        0.12.0 New domain - animecubedgaming.com - Channel28
// @history        0.11.0 Now https compatible (Updated by Channel28)
// @history        0.10.0 Added rough estimate of max difficulty
// @history        0.9.1 Text was supposed to be white, now it is
// @grant          none
// ==/UserScript==

var jutsuTypes = {
	gen: "Genjutsu",
	nin: "Ninjutsu",
	tai: "Taijutsu",
	dou: "Doujutsu"
}
var basicJutsuTypes = {
	gen: "Genjutsu",
	nin: "Ninjutsu",
	tai: "Taijutsu"
}

function Ability()
{
	this.lvl = 0;
	this.str = 0;
	this.rng = 0;
	this.suc = 0;
}

function Ninja()
{
	this.gen = new Ability();
	this.nin = new Ability();
	this.tai = new Ability();
	this.dou = new Ability();
}

function Challenge()
{
	this.dif = 0;
	this.suc = 0;
}

function Mission()
{
	this.gen = new Challenge();
	this.nin = new Challenge();
	this.tai = new Challenge();
	this.dou = new Challenge();
	this.order = [];
	this.crank = 0;
}

// Math stuff below

function binomialCoefficient(n, k)
{
	// n!/[k!(n-k)!]
	if (k > n || k < 0)
		return 0;
	k = Math.max(k, n - k);
	var i = 1;
	var j = k + 1;
	var c = 1;
	// i = 1 ... n-k => (n-k)!
	// j = k+1 ... n => n!/k!
	while (j <= n)
		c *= j++ / i++;
	return c;
}

function binomdist(k, n, p, cumulative)
{
	// Cumulative distribution, k or less successes in n trials
	if (cumulative) {
		var sum = 0;
		for (var i = 0; i <= k; i++)
			sum += binomdist(i, n, p, false);
		return sum;
	}
	// Exactly k successes in n trials with probability p
	return binomialCoefficient(n, k) * Math.pow(p, k) * Math.pow(1 - p, n - k);
}

function successChance(challenge, ability, crank)
{
	if (!crank)
		crank = 0;

	// Successful rolls required
	var req = (challenge.suc + crank)- ability.suc;
	if (req > ability.lvl)
		return 0;
	else if (req <= 0)
		return 1;

	if ((challenge.dif + crank) > ability.rng)
		return 0;

	// Success probability per roll
	var prob = (ability.str + ability.rng - (challenge.dif + crank) + 1) / ability.rng;

	if (prob >= 1)
		return 1;

	return Math.min(0.9999, 1 - binomdist(req - 1, ability.lvl, prob, true));
}

function expectedSuccesses(challenge, ability, crank)
{
	if (!crank)
		crank = 0;

	if ((challenge.dif + crank) > ability.rng)
		return 0;

	// Success probability per roll
	var prob = (ability.str + ability.rng - (challenge.dif + crank) + 1) / ability.rng;
	
	return ability.suc + Math.min(1, prob) * ability.lvl;
}

function percent(n)
{
	if (n == 1.0)
		return "100%";
	else if (n == 0.0)
		return "0%";
	var p = Math.round(n * 1000) / 10;
	if (p > 99.9)
		return ">99.9%";
	else if (p < 0.1)
		return "<0.1%";
	return p + "%";
}

// Parsing

function parseAbilities()
{
	var ninja = new Ninja();
	var tables = document.evaluate("//table[count(descendant::table)=4]//table",
		document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
	
	for (var i = 0; i < tables.snapshotLength; i++) {
		var ab = new Ability();
		var type = "";
		
		var lines = tables.snapshotItem(i).textContent.split(/\n/);
		for (var l in lines) {
			var m;
			
			m = lines[l].match(/Suc: (\d+)( \(\+(\d+)\))?/);
			if (m) {
				ab.suc += parseInt(m[1]);
				if (m[3])
					ab.suc += parseInt(m[3]);
				continue;
			}
			
			m = lines[l].match(/Str: (\d+)( \(\+(\d+)\))?/);
			if (m) {
				ab.str += parseInt(m[1]);
				if (m[3])
					ab.str += parseInt(m[3]);
				continue;
			}
			
			m = lines[l].match(/Rng: (\d+)( \(\+(\d+)\))?/);
			if (m) {
				ab.rng += parseInt(m[1]);
				if (m[3])
					ab.rng += parseInt(m[3]);
				continue;
			}
			
			m = lines[l].match(/Lvl: (\d+)( \(\+(\d+)\))?/);
			if (m) {
				ab.lvl += parseInt(m[1]);
				if (m[3])
					ab.lvl += parseInt(m[3]);
				continue;
			}
			
			m = lines[l].match(/(\w{3}): (\d+)/);
			if (m) {
				type = m[1].toLowerCase();
				ab.lvl += parseInt(m[2]);
				continue;
			}
		}
		ninja[type] = ab;
	}

	return ninja;
}

function parseMission()
{
	var tables = document.evaluate("//table[count(descendant::table)=0]",
		document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
	
	var node = null;
	var mission = new Mission();
	for (var i = 0; i < tables.snapshotLength; i++) {
		var txt = tables.snapshotItem(i).textContent;
		if (/virus attack/i.test(txt)) {
			var lines = txt.split(/\n/);
			// Gen  D: 15  S: 15
			for (var l in lines) {
				var line = lines[l].replace(/\s+/g, " ");
				var m = line.match(/(\w{3}) D: (\d+) S: (\d+)/);
				if (m) {
					var type = m[1].toLowerCase();
					mission[type].dif = parseInt(m[2]);
					mission[type].suc = parseInt(m[3]);
					node = tables.snapshotItem(i);
				}
			}
			break;
		}
	}
	
	if (node)
		return {mission: mission, node: node};
}

var ninja = parseAbilities();
var missionNode = parseMission();

if (missionNode) {
	var mission = missionNode.mission;
	var node = missionNode.node;
	
	var p = 1;
	for (var t in jutsuTypes) {
		p *= successChance(mission[t], ninja[t]);
	}
	
	var tr = document.createElement("tr");
	var td = document.createElement("td");
	td.textContent = "Success chance: " + percent(p);
	td.style.color = "white";
	tr.appendChild(td);
	node.firstChild.appendChild(tr);
}

// Rough estimate of average field difficulty
function fieldChallenge(diff)
{
	diff = Math.min(230, diff);
	diff = Math.max(0, diff);
	var c = new Challenge();
	c.dif = Math.round(0.13 * diff + 15);
	c.suc = Math.round(0.11 * diff + 15);
	return c;
}

// Calculate max difficulty a ninja can reliably handle
var diff, p;
for (diff = 230; diff >= 0; diff -= 5) {
	p = 1;
	for (var t in basicJutsuTypes)
		p = Math.min(p, successChance(fieldChallenge(diff), ninja[t]));
	if (p > 0.95)
		break;
}
var form = document.getElementsByName("field")[0];
var div = document.createElement("div");
div.textContent = "Recommended difficulty (estimate): " + diff + " (" + percent(p) + ")";
form.parentNode.insertBefore(div, form);