F-List Kinks Calculator

Generates an estimated compatibility score when comparing character kinks.

  1. // ==UserScript==
  2. // @name F-List Kinks Calculator
  3. // @namespace flistkinkscalculator
  4. // @description Generates an estimated compatibility score when comparing character kinks.
  5. // @version 1.1.2
  6. // @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==
  7. // @require https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js
  8. // @match *://www.f-list.net/c/*
  9. // @grant unsafeWindow
  10. // @grant GM.getValue
  11. // @grant GM.setValue
  12. // ==/UserScript==
  13.  
  14. /* globals $ exportFunction */
  15.  
  16. var FList = unsafeWindow.FList;
  17.  
  18. // Descriptions for your scores.
  19. var ratingNames = [
  20. {score: -1.00, confidence: 0.00, desc: "Uncertain"},
  21. {score: -1.00, confidence: 0.20, desc: "Are you fucking kidding me?"},
  22. {score: -0.30, confidence: 0.15, desc: "Hell No!"},
  23. {score: -0.15, confidence: 0.15, desc: "Nope"},
  24. {score: 0.00, confidence: 0.15, desc: "Terrible"},
  25. {score: 0.20, confidence: 0.20, desc: "Poor"},
  26. {score: 0.40, confidence: 0.20, desc: "Average"},
  27. {score: 0.60, confidence: 0.30, desc: "Good"},
  28. {score: 0.75, confidence: 0.40, desc: "Very Good"},
  29. {score: 0.85, confidence: 0.50, desc: "Excellent!"}
  30. ];
  31.  
  32. // The different scoring matrices available for selection.
  33. // 1st dimension is your character's kink. 2nd dimension is the other character.
  34. var scoringProfiles = [
  35. {
  36. name: "Unbiased",
  37. matrix: {
  38. fave: {fave: 4, yes: 3, maybe: 1, no:-2},
  39. yes: {fave: 3, yes: 3, maybe: 1, no:-1},
  40. maybe: {fave: 1, yes: 1, maybe: 1, no: 0},
  41. no: {fave:-2, yes:-1, maybe: 0, no: 0}
  42. }
  43. }, {
  44. name: "Self-Biased",
  45. matrix: {
  46. fave: {fave: 4, yes: 3, maybe: 2, no:-4},
  47. yes: {fave: 3, yes: 3, maybe: 1, no:-2},
  48. maybe: {fave: 1, yes: 1, maybe: 1, no: 0},
  49. no: {fave:-1, yes:-1, maybe: 0, no: 0}
  50. }
  51. }
  52. ];
  53.  
  54. // Giving/Receiving kink pairs
  55. var asymmetricalKinks = {
  56. 16 : 163, 163 : 16, // Rimming
  57. 157 : 137, 137 : 157, // Anal
  58. 158 : 141, 141 : 158, // Oral
  59. 340 : 229, 229 : 340, // Vaginal
  60. 514 : 512, 512 : 514, // Fellatio
  61. 515 : 513, 513 : 515, // Cunnilingus
  62. 422 : 423, 423 : 422 // Vore
  63. };
  64.  
  65. // Class names for fave, yes, maybe, no
  66. var quickCompareClasses = [
  67. 'Character_QuickCompareNo',
  68. 'Character_QuickCompareMaybe',
  69. 'Character_QuickCompareYes',
  70. 'Character_QuickCompareFave'
  71. ];
  72.  
  73.  
  74. var statusText = $('<span id="KinkCalculatorStatus" style="padding-left:0.5em">');
  75. var scoringProfileSelector = $('<select id="KinkCalculatorProfileSelector">').width('auto');
  76.  
  77. for (var p in scoringProfiles) {
  78. scoringProfileSelector.append($('<option>').val(p).append(scoringProfiles[p].name));
  79. }
  80.  
  81. // Insert our own controls.
  82. $('#Character_QuickCompareClear').after(statusText);
  83. $('#Character_QuickCompareSelect').width('auto').after(scoringProfileSelector).after(" Scoring profile: ");
  84.  
  85. // Insert our own function into the default Quick Compare function.
  86. var oldCompare = FList.Character_quickCompare;
  87.  
  88. var newCompare = function(clear) {
  89. console.log("Running a comparison...");
  90. oldCompare(clear);
  91. kinkCalculator(clear);
  92. };
  93.  
  94. exportFunction(newCompare, FList, {defineAs: "Character_quickCompare"});
  95.  
  96.  
  97. function kinkCalculator(clear) {
  98. statusText.empty();
  99.  
  100. $('.KinkCalculatorBreakdown, .KinkCalculatorAsym').remove();
  101.  
  102. if (clear) return;
  103.  
  104. statusText.text("Getting character kinks...");
  105.  
  106. var char1 = {
  107. id: $('#Character_QuickCompareSelect').val(),
  108. name: $('#Character_QuickCompareSelect').find(":selected").text()
  109. };
  110. var char2 = {
  111. id: $('#profile-character-id').val(),
  112. name: $('#profile-character-name').val()
  113. };
  114. var profile = scoringProfileSelector.val();
  115.  
  116. var dataSource = '/json/getFetishes.php';
  117. $.getJSON(dataSource, {character_id: char1.id, id_only: true}, function (a) {
  118. $.getJSON(dataSource, {character_id: char2.id, id_only: true}, function (b) {
  119. char1.kinks = a;
  120. char2.kinks = b;
  121. showScore(char1, char2, profile);
  122. });
  123. });
  124. }
  125.  
  126.  
  127. function calculateKinks(kinks1, kinks2, matrix) {
  128. var total = 0;
  129. var gross = 0;
  130. var positive = 0;
  131. var negative = 0;
  132. var neutral = 0;
  133. var misses = 0;
  134.  
  135. for (var fid1 in kinks1) {
  136. var fid2 = asymmetricalKinks[fid1] ? asymmetricalKinks[fid1] : fid1;
  137.  
  138. if (kinks2[fid2]) {
  139. var v = matrix[kinks1[fid1]][kinks2[fid2]];
  140. total += v;
  141. gross += Math.abs(v);
  142.  
  143. if (v > 0) positive++;
  144. else if (v < 0) negative++;
  145. else neutral++;
  146.  
  147. var fItem1 = $('#Character_ListedFetish' + fid1);
  148. var fItem2 = $('#Character_ListedFetish' + fid2);
  149.  
  150. if (fid1 !== fid2) {
  151. // Remap the highlighted kink element
  152. fItem2.addClass(fItem1.attr('class'))
  153. .attr('title', 'Kinks Calculator re-mapped to this kink using its giving/receiving rules')
  154. .append(' <b class="KinkCalculatorAsym">&#8651;</b>');
  155.  
  156. if (!fItem1.data('KinkCalculatorValue')) {
  157. fItem1.removeClass(quickCompareClasses.join(' ') + ' Character_QuickCompareActive');
  158. }
  159. }
  160.  
  161. fItem2.data('KinkCalculatorValue', v)
  162. .append(' <b class="KinkCalculatorBreakdown">' + (v < 0 ? '' : '+') + v + '</b>');
  163. }
  164. else {
  165. misses++;
  166. }
  167. }
  168.  
  169. var score = total / gross;
  170. var certainty = (3*positive + 2*negative + neutral) / (3*positive + 2*negative + neutral + misses);
  171.  
  172. var rating = getRating(score, certainty);
  173.  
  174. return {
  175. total: total,
  176. gross: gross,
  177. positive: positive,
  178. negative: negative,
  179. neutral: neutral,
  180. misses: misses,
  181. score: score,
  182. rating: rating,
  183. certainty: certainty
  184. };
  185. }
  186.  
  187. function getRating(score, confidence) {
  188. var rating = "?";
  189.  
  190. for (var r in ratingNames) {
  191. if (score >= ratingNames[r].score && confidence >= ratingNames[r].confidence) {
  192. rating = ratingNames[r].desc;
  193. }
  194. }
  195.  
  196. return rating;
  197. }
  198.  
  199. function showScore(char1, char2, profile) {
  200. statusText.text("Scoring...");
  201.  
  202. var result = calculateKinks(jsonKinksToMap(char1.kinks), jsonKinksToMap(char2.kinks), scoringProfiles[profile].matrix);
  203.  
  204. var altColorMode = GM.getValue('altColorMode', false);
  205.  
  206. var breakdownDetails =
  207. "Compare " + char1.name + " (" + char1.id + ") and " + char2.name+ " (" + char2.id + ")\n\n" +
  208. "Hits: " + result.positive + " positive, " + result.negative + " negative, " + result.neutral + " neutral\n" +
  209. "Misses: " + result.misses + "\n" +
  210. "Points (sum of all hit values): " + result.total + "\n" +
  211. "Gross (sum of absolute hit values): " + result.gross + "\n" +
  212. "Score (points / gross): " + toPercent(result.score, 1) + "\n" +
  213. "Confidence (based on hits / hits + misses): " + toPercent(result.certainty, 1);
  214.  
  215. statusText.empty().append(
  216. '<b>Score:</b> ',
  217. $('<a href="#">' + toPercent(result.score, 0) + '</a>').click(function(){alert(breakdownDetails); return false;}),
  218. ' &nbsp; ',
  219. '<b>Confidence:</b> ' + toPercent(result.certainty, 0) + ' &nbsp; ',
  220. '<b>Rating:</b> ' + result.rating + ' &nbsp; ',
  221. $('<label for="KinkCalculatorAltColorMode"> Color based on points</label>')
  222. .prepend($('<input type="checkbox" id="KinkCalculatorAltColorMode"/>')
  223. .attr('checked', altColorMode ? 'checked' : null)
  224. .change(toggleQuickCompareColors))
  225. );
  226.  
  227. toggleQuickCompareColors();
  228. }
  229.  
  230.  
  231. function toggleQuickCompareColors() {
  232. var altColorMode = $('#KinkCalculatorAltColorMode').is(':checked');
  233. GM.setValue('altColorMode', altColorMode);
  234.  
  235. var classesJoined = quickCompareClasses.join(' ');
  236.  
  237. $('.Character_QuickCompareActive').each(function(idx, el) {
  238. var e = $(el);
  239.  
  240. if (altColorMode) {
  241. e.data('OldClasses', e.attr('class'));
  242. e.removeClass(classesJoined);
  243. var v = parseInt(e.data('KinkCalculatorValue'));
  244. var c = v > 3 ? 3 : v > 0 ? 2 : v < 0 ? 0 : 1;
  245. e.addClass(quickCompareClasses[c]);
  246. }
  247. else {
  248. e.attr('class', e.data('OldClasses'));
  249. }
  250. });
  251. }
  252.  
  253.  
  254. function toPercent(x, dp) {
  255. return isNaN(x) ? "?" : (x * 100).toFixed(dp) + "%";
  256. }
  257.  
  258.  
  259. function jsonKinksToMap(json) {
  260. var map = {};
  261. $.each(json.fetishes, function (yif, f) {map[f.fid] = f.choice;});
  262. return map;
  263. }