F-List Kinks Calculator

Generates an estimated compatibility score when comparing character kinks.

目前為 2014-11-17 提交的版本,檢視 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name        F-List Kinks Calculator
// @namespace   flistkinkscalculator
// @description Generates an estimated compatibility score when comparing character kinks.
// @version     1.1.0
// @icon        data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABh0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzT7MfTgAAA4dJREFUWIXFl19oHFUUxn/nbswsbRBDS2jzog9BH2JdhFokVJqA0AcfpI2CoKAvYqlUaqFQUGnBCi2KihYJNCp5FamCCg0V3aBg81LQog/+oVk27UOyNG0C7Y7ZuceHZGbu7E5mdtMUD/twvjsz53733O+cu1dUlf/TugBEBABVPQicCgJbcF+y1obPSSMcfu/6xpjEO43AXvG6u14FZkSkEsYRVUVEUNUh4J2rc4veC0c+fLx5knjetIxJEjlQVbHWsnVLr3/uzOGLwBTwOVCJCAB7gBOjx74YtkEDMYkEbIiJCDZo8PDAdt5+ZaQMjAAk8jRf+Z3bS9c3fHKAxr91bi9dZ6pcTox3tbypirt9J18s4ft+xxN6nsebE7/GAwWT+l4rAWneURgYGOiYQLVaTcQxq8KwNlibgLUBgmBMM4X1mRtntZDQLALhQ+PI+NMLs8Ds+gi45RCWesgkjYC1AUiS+bX5G+uaHJJxNOo1ORkQkk1k7OjeyD/w7mTHODRrQg1kZEBtgIhp6WLxisy6cZiLTA1YGyCS3LtqtRoHFOkYx2Ta0ED4sODU7Hvn/or8QsF0jENrRCLM00BTBj54bSTyX//ox1x8ZF/cM97/6u/IN5EI86oASZaPY83jneDQzWxEcRWsoQEjuThBwMGmnS0I+4B7vn/8zRVnFZKLXUv7n5AjwtYMnDrwROQfG/spF7vmxhHTpgjvlgbCerB5IhS5Mw1MT087eHvkS7tl2Mz87GSyseTh3270OziO3XYj6unp2bDj2DXP8yjc42VnAGC2OsMfl5Jiiv8J68qvaR9FzGqzl5ZKCG3zfX30PbAjuxEBmKUKmybH2Qz0eR43fR8feH5iAmMMjUYjdQKRtSc3xqyegnU23RzMJvDD12d5VsYpeR67tm1jslIBYHR0NDU4rByxzcdsmtXrdS788mc2Ad/3eRA4NDhIwRge6+2lfPnymqsLv6nVarkElpeXW8YSBB7d8RAvHTrOy8Ui1lpUlYXFRRSYm5vLDF4sFiNfNXk5Aeju7qa/v5/h3f+kEpgBFk8fP8wbJ8+gqswvLPDJk89w8Psvsdby1unx3BXm2fDunTy3by+s3I6A+GY0BHwG3A9c/E5kDyBPqZar58/fe2n//keevnXr5ztmsGJTInIiqqy0y+a3YGulkgKMwdB8qZSvsA4tuuimEaiVSkHKN3eFwH8EALqp+4E4kAAAAABJRU5ErkJggg==
// @require     https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js
// @match       *://www.f-list.net/c/*
// @grant       unsafeWindow
// @grant       GM_getValue
// @grant       GM_setValue
// ==/UserScript==

var FList = unsafeWindow.FList;

// Descriptions for your scores.
var ratingNames = [
	{score: -1.00, confidence: 0.00, desc: "Uncertain"},
	{score: -1.00, confidence: 0.20, desc: "Are you fucking kidding me?"},
	{score: -0.30, confidence: 0.15, desc: "Hell No!"},
	{score: -0.15, confidence: 0.15, desc: "Nope"},
	{score:  0.00, confidence: 0.15, desc: "Terrible"},
	{score:  0.20, confidence: 0.20, desc: "Poor"},
	{score:  0.40, confidence: 0.20, desc: "Average"},
	{score:  0.60, confidence: 0.30, desc: "Good"},
	{score:  0.75, confidence: 0.40, desc: "Very Good"},
	{score:  0.85, confidence: 0.50, desc: "Excellent!"}
];

// The different scoring matrices available for selection.
// 1st dimension is your character's kink. 2nd dimension is the other character.
var scoringProfiles = [
	{
		name: "Unbiased",
		matrix: {
			fave:  {fave: 4, yes: 3, maybe: 1, no:-2},
			yes:   {fave: 3, yes: 3, maybe: 1, no:-1},
			maybe: {fave: 1, yes: 1, maybe: 1, no: 0},
			no:    {fave:-2, yes:-1, maybe: 0, no: 0}
		}
	}, {
		name: "Self-Biased",
		matrix: {
			fave:  {fave: 4, yes: 3, maybe: 2, no:-4},
			yes:   {fave: 3, yes: 3, maybe: 1, no:-2},
			maybe: {fave: 1, yes: 1, maybe: 1, no: 0},
			no:    {fave:-1, yes:-1, maybe: 0, no: 0}
		}
	}
];

// Giving/Receiving kink pairs
var asymmetricalKinks = {
	16  : 163, 163 : 16,  // Rimming
	157 : 137, 137 : 157, // Anal
	158 : 141, 141 : 158, // Oral
	340 : 229, 229 : 340, // Vaginal
	514 : 512, 512 : 514, // Fellatio
	515 : 513, 513 : 515, // Cunnilingus
	422 : 423, 423 : 422  // Vore
};

// Class names for fave, yes, maybe, no
var quickCompareClasses = [
	'Character_QuickCompareNo',
	'Character_QuickCompareMaybe',
	'Character_QuickCompareYes',
	'Character_QuickCompareFave'
];


var statusText = $('<span id="KinkCalculatorStatus" style="padding-left:0.5em">');
var scoringProfileSelector = $('<select id="KinkCalculatorProfileSelector">').width('auto');

for (var p in scoringProfiles) {
	scoringProfileSelector.append($('<option>').val(p).append(scoringProfiles[p].name));
}

// Insert our own controls.
$('#Character_QuickCompareClear').after(statusText);
$('#Character_QuickCompareSelect').width('auto').after(scoringProfileSelector).after(" Scoring profile: ");

// Insert our own function into the default Quick Compare function.
var oldCompare = FList.Character_quickCompare;

var newCompare = function(clear) {
	oldCompare(clear);
	kinkCalculator(clear);
};

exportFunction(newCompare, FList, {defineAs: "Character_quickCompare"});


function kinkCalculator(clear) {
	var dataSource = '/json/getFetishes.php';

	statusText.empty();
	$('.KinkCalculatorBreakdown, .KinkCalculatorAsym').remove();

	if (clear) return;

	statusText.html("Getting character kinks...");

	var char1 = {
		id:   $('#Character_QuickCompareSelect').val(),
		name: $('#Character_QuickCompareSelect').find(":selected").text()
	};
	var char2 = {
		id:   $('#profile-character-id').val(),
		name: $('#profile-character-name').val()
	};
	var profile = scoringProfileSelector.val();

	$.getJSON(dataSource, {character_id: char1.id, id_only: true}, function (a) {
		$.getJSON(dataSource, {character_id: char2.id, id_only: true}, function (b) {
			char1.kinks = a;
			char2.kinks = b;
			showScore(char1, char2, profile);
		});
	});
}


function calculateKinks(kinks1, kinks2, matrix) {
	var total = 0;
	var gross = 0;
	var positive = 0;
	var negative = 0;
	var neutral = 0;
	var misses = 0;

	for (var fid1 in kinks1) {
		var fid2 = asymmetricalKinks[fid1] ? asymmetricalKinks[fid1] : fid1;
	
		if (kinks2[fid2]) {
			var v = matrix[kinks1[fid1]][kinks2[fid2]];
			total += v;
			gross += Math.abs(v);

			if (v > 0) positive++;
			else if (v < 0) negative++;
			else neutral++;

			var fItem1 = $('#Character_ListedFetish' + fid1);
			var fItem2 = $('#Character_ListedFetish' + fid2);
			
			if (fid1 !== fid2) {
				// Remap the highlighted kink element
				fItem2.addClass(fItem1.attr('class'))
					.attr('title', 'Kinks Calculator re-mapped to this kink using its giving/receiving rules')
					.append(' <b class="KinkCalculatorAsym">&#8651;</b>');

				if (!fItem1.data('KinkCalculatorValue')) {
					fItem1.removeClass(quickCompareClasses.join(' ') + ' Character_QuickCompareActive');
				}
			}

			fItem2.data('KinkCalculatorValue', v)
				.append(' <b class="KinkCalculatorBreakdown">' + (v < 0 ? '' : '+') + v + '</b>');
		}
		else {
			misses++;
		}
	}

	var score = total / gross;
	var certainty = (3*positive + 2*negative + neutral) / (3*positive + 2*negative + neutral + misses);

	var rating = getRating(score, certainty);

	return {
		total:     total,
		gross:     gross,
		positive:  positive,
		negative:  negative,
		neutral:   neutral,
		misses:    misses,
		score:     score,
		rating:    rating,
		certainty: certainty
	};
}

function getRating(score, confidence) {
	var rating = "?";
	
	for (var r in ratingNames) {
		if (score >= ratingNames[r].score && confidence >= ratingNames[r].confidence) {
			rating = ratingNames[r].desc;
		}
	}

	return rating;
}

function showScore(char1, char2, profile) {
	statusText.html("Scoring...");
	
	var result = calculateKinks(jsonKinksToMap(char1.kinks), jsonKinksToMap(char2.kinks), scoringProfiles[profile].matrix);

	var altColorMode = GM_getValue('altColorMode', false);

	var breakdownDetails =
		"Compare " + char1.name + " (" + char1.id + ") and " + char2.name+ " (" + char2.id + ")" + "\n\n" +
		"Hits: " + result.positive + " positive, " + result.negative + " negative, " + result.neutral + " neutral " + "\n" +
		"Misses: " + result.misses + "\n" +
		"Points (sum of all hit values): " + result.total + "\n" +
		"Gross (sum of absolute hit values): " + result.gross + "\n" +
		"Score (points / gross): " + toPercent(result.score, 1) + "\n" +
		"Confidence (based on hits / hits + misses): " + toPercent(result.certainty, 1);

	statusText.empty().append(
		'<b>Score:</b> ',
		$('<a href="#">' + toPercent(result.score, 0) + '</a>').click(function(){alert(breakdownDetails); return false;}),
		' &nbsp; ',
		'<b>Confidence:</b> ' + toPercent(result.certainty, 0) + ' &nbsp; ',
		'<b>Rating:</b> ' + result.rating + ' &nbsp; ',
		$('<label for="KinkCalculatorAltColorMode"> Color based on points</label>')
			.prepend($('<input type="checkbox" id="KinkCalculatorAltColorMode"/>')
				.attr('checked', altColorMode ? 'checked' : null)
				.change(toggleQuickCompareColors))
	);
	
	toggleQuickCompareColors();
}


function toggleQuickCompareColors() {
	var altColorMode = $('#KinkCalculatorAltColorMode').is(':checked');
	GM_setValue('altColorMode', altColorMode);

	var classesJoined = quickCompareClasses.join(' ');

	$('.Character_QuickCompareActive').each(function(idx, el) {
		var e = $(el);

		if (altColorMode) {
			e.data('OldClasses', e.attr('class'));
			e.removeClass(classesJoined);
			var v = parseInt(e.data('KinkCalculatorValue'));
			var c = v > 3 ? 3 : v > 0 ? 2 : v < 0 ? 0 : 1;
			e.addClass(quickCompareClasses[c]);
		}
		else {
			e.attr('class', e.data('OldClasses'));
			e.data('OldClasses', null);
		}
	});
}


function toPercent(x, dp) {
	return isNaN(x) ? "?" : (x * 100).toFixed(dp) + "%";
}


function jsonKinksToMap(json) {
	var map = {};
	$.each(json.fetishes, function (yif, f) {map[f.fid] = f.choice;});
	return map;
}