F-List Kinks Calculator

Generates an estimated compatibility score when comparing character kinks.

当前为 2014-11-17 提交的版本,查看 最新版本

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

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

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

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

您需要先安装一款用户脚本管理器扩展,例如 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;
}