From 4c28ce82648d53ba1befacb15753f7423e8e4a9c Mon Sep 17 00:00:00 2001 From: "Mr. Stallion" Date: Sat, 6 Mar 2021 14:59:28 -0600 Subject: [PATCH] Kink scoring --- chat/CharacterSearch.vue | 2 + learn/matcher-types.ts | 91 +++++++++++++++++++++++++++- learn/matcher.ts | 124 +++++++++++++++++++++++++++++++++++++-- 3 files changed, 210 insertions(+), 7 deletions(-) diff --git a/chat/CharacterSearch.vue b/chat/CharacterSearch.vue index a90c7f8..eabe63d 100644 --- a/chat/CharacterSearch.vue +++ b/chat/CharacterSearch.vue @@ -197,6 +197,8 @@ @Hook('mounted') mounted(): void { core.connection.onMessage('ERR', (data) => { + this.state = 'search'; + switch(data.number) { case 18: this.error = l('characterSearch.error.noResults'); diff --git a/learn/matcher-types.ts b/learn/matcher-types.ts index 8c59d87..f7b1af4 100644 --- a/learn/matcher-types.ts +++ b/learn/matcher-types.ts @@ -10,7 +10,9 @@ export enum TagId { ApparentAge = 64, RelationshipStatus = 42, Species = 9, - LanguagePreference = 49 + LanguagePreference = 49, + + Kinks = 99999 } export enum Gender { @@ -481,3 +483,90 @@ export interface SpeciesMappingCache { [key: number]: SpeciesMappingCacheRecord[]; } + +export const kinkMatchScoreMap = { + favorite: { + favorite: 0.55, + yes: 0.25, + maybe: -0.5, + no: -1.0 + }, + + yes: { + favorite: 0.20, + yes: 0.20, + maybe: -0.25, + no: -0.5 + }, + + maybe: { + favorite: -0.5, + yes: -0.25, + maybe: 0, + no: 0 + }, + + no: { + favorite: -1.0, + yes: -0.5, + maybe: -0.1, + no: 0 + } +}; + + +export const kinkComparisonExclusions = { + 585: true, // amputees + 587: true, // anthro characters + 588: true, // body hair + 589: true, // canon characters + 458: true, // chubby + 605: true, // disabilities + 590: true, // facial hair / beards + 531: true, // femboys + 592: true, // femininity + 109: true, // older characters + 600: true, // shorter characters + 601: true, // skinny characters + 584: true, // slime / goo characters + 616: true, // superheroes / villains + 603: true, // taller characters + 532: true, // tomboys + 720: true, // toons + 354: true, // twinks + 183: true, // very fat characters + 85: true, // very lithe characters + 84: true, // very muscular characters + 197: true // younger characters +}; + +export const kinkComparisonExclusionGroups = { + 29: true, // gender preferences + 30: true // species preferences +}; + + +export const kinkComparisonSwaps: Record = { + 137: 157, // anal sex giving -> receiving + 157: 137, // anal sex receiving -> giving + 163: 16, // rimming giving -> receiving + 16: 163, // rimming receiving -> giving + 229: 340, // vaginal sex giving -> receiving + 340: 229, // vaginal sex receiving -> giving + 513: 515, // cunnilingus giving -> receiving + 515: 513, // cunnilingus receiving -> giving + 141: 158, // oral sex giving -> receiving + 158: 141 // oral sex receiving -> giving +}; + + +export interface KinkBucketScore { + score: number; + count: number; + weighted: number; +} + +export interface MatchResultKinkScores { + [key: string]: KinkBucketScore; +} + diff --git a/learn/matcher.ts b/learn/matcher.ts index 9d4575c..5661ab3 100644 --- a/learn/matcher.ts +++ b/learn/matcher.ts @@ -1,20 +1,21 @@ /* eslint-disable no-null-keyword, max-file-line-count */ import * as _ from 'lodash'; -import { Character, CharacterInfotag } from '../interfaces'; +import { Character, CharacterInfotag, KinkChoice } from '../interfaces'; import log from 'electron-log'; //tslint:disable-line:match-default-export-name // tslint:disable-next-line ban-ts-ignore // @ts-ignore import anyAscii from 'any-ascii'; +import {Store} from '../site/character_page/data_store'; import { BodyType, fchatGenderMap, FurryPreference, Gender, genderKinkMapping, - Kink, - kinkMapping, + Kink, KinkBucketScore, kinkComparisonExclusionGroups, kinkComparisonExclusions, kinkComparisonSwaps, + kinkMapping, kinkMatchScoreMap, KinkPreference, likelyHuman, mammalSpecies, nonAnthroSpecies, Orientation, Species, SpeciesMap, speciesMapping, SpeciesMappingCache, @@ -25,6 +26,7 @@ import { export interface MatchReport { + _isVue: true; you: MatchResult; them: MatchResult; youMultiSpecies: boolean; @@ -47,6 +49,7 @@ export interface MatchResultScores { [TagId.Age]: Score; [TagId.FurryPreference]: Score; [TagId.Species]: Score; + [TagId.Kinks]: Score; } export interface MatchScoreDetails { @@ -177,7 +180,7 @@ export class Matcher { readonly yourAnalysis: CharacterAnalysis; readonly theirAnalysis: CharacterAnalysis; - static readonly UNICORN_LEVEL = 5.5; + static readonly UNICORN_LEVEL = 6.0; constructor(you: Character, them: Character, yourAnalysis?: CharacterAnalysis, theirAnalysis?: CharacterAnalysis) { @@ -199,6 +202,7 @@ export class Matcher { const themYouMatch = themYou.match(); const report: MatchReport = { + _isVue: true, you: youThemMatch, them: themYouMatch, youMultiSpecies: false, @@ -216,6 +220,8 @@ export class Matcher { report.details.totalScoreDimensions = Matcher.countScoresTotal(report); report.details.dimensionsAtScoreLevel = Matcher.countScoresAtLevel(report, report.score) || 0; + // log.debug('report.generate', report); + return report; } @@ -238,6 +244,7 @@ export class Matcher { const themYouMatch = themYou.match(); const report: MatchReport = { + _isVue: true, you: youThemMatch, them: themYouMatch, youMultiSpecies: (yourCharacterAnalyses.length > 1), @@ -275,7 +282,11 @@ export class Matcher { log.debug( 'report.identify.best', - {buildTime: Date.now() - reportStartTime, variations: yourCharacterAnalyses.length * theirCharacterAnalyses.length} + { + buildTime: Date.now() - reportStartTime, + variations: yourCharacterAnalyses.length * theirCharacterAnalyses.length, + report: bestReport! + } ); return bestReport!; @@ -382,7 +393,8 @@ export class Matcher { [TagId.Age]: this.resolveAgeScore(), [TagId.FurryPreference]: this.resolveFurryPairingsScore(), [TagId.Species]: this.resolveSpeciesScore(), - [TagId.SubDomRole]: this.resolveSubDomScore() + [TagId.SubDomRole]: this.resolveSubDomScore(), + [TagId.Kinks]: this.resolveKinkScore() }, info: { @@ -557,6 +569,32 @@ export class Matcher { return this.formatScoring(score, theyAreAnthro ? 'furry pairings' : theyAreHuman ? 'human pairings' : ''); } + + private resolveKinkScore(): Score { + const kinkScore = this.resolveKinkBucketScore('all'); + + log.debug('report.score.kink', this.them.name, this.you.name, kinkScore.count, kinkScore.score, kinkScore.weighted); + + if (kinkScore.weighted === 0) { + return new Score(Scoring.NEUTRAL); + } + + if (kinkScore.weighted < 0) { + if (Math.abs(kinkScore.weighted) < 0.45) { + return new Score(Scoring.WEAK_MISMATCH, 'Challenging kinks'); + } + + return new Score(Scoring.MISMATCH, 'Mismatching kinks'); + } + + if (Math.abs(kinkScore.weighted) < 0.45) { + return new Score(Scoring.WEAK_MATCH, 'Good kinks'); + } + + return new Score(Scoring.MATCH, 'Great kinks'); + } + + static furryLikeabilityScore(c: Character): Scoring { const furryPreference = Matcher.getTagValueList(TagId.FurryPreference, c); @@ -747,6 +785,78 @@ export class Matcher { } + private resolveKinkBucketScore(bucket: 'all' | 'favorite' | 'yes' | 'maybe' | 'no' | 'positive' | 'negative'): KinkBucketScore { + const yourKinks = this.getAllStandardKinks(this.you); + const theirKinks = this.getAllStandardKinks(this.them); + + const result: any = _.reduce( + yourKinks, + (accum, yourKinkValue: any, yourKinkId: any) => { + const theirKinkId = (yourKinkId in kinkComparisonSwaps) ? kinkComparisonSwaps[yourKinkId] : yourKinkId; + + if ( + (!(theirKinkId in theirKinks)) + || (yourKinkId in kinkComparisonExclusions) + || ((Store.shared.kinks[yourKinkId]) && (Store.shared.kinks[yourKinkId].kink_group in kinkComparisonExclusionGroups)) + ) { + return accum; + } + + const theirKinkValue = theirKinks[theirKinkId] as any; + + if ( + (yourKinkValue === bucket) + || (bucket === 'all') + || ((bucket === 'negative') && ((yourKinkValue === 'no') || (yourKinkValue === 'maybe'))) + || ((bucket === 'positive') && ((yourKinkValue === 'favorite') || (yourKinkValue === 'yes'))) + ) { + return { + score: accum.score + this.getKinkMatchScore(yourKinkValue, theirKinkValue), + count: accum.count + 1 + }; + } + + return accum; + }, + { score: 0, count: 0 } + ); + + result.weighted = (result.count === 0) + ? 0 + : ( + (Math.log(result.count) / Math.log(5)) // log 5 base + * (result.score / result.count) + ); + + return result; + } + + + private getAllStandardKinks(c: Character): { [key: number]: KinkChoice } { + const kinks = _.pickBy(c.kinks, _.isString); + + _.each( + c.customs, + (custom: any) => { + if (!custom) { + return; + } + + const children = (custom.children) ? custom.children : {}; + + _.each(children, (child) => kinks[child] = custom.choice); + } + ); + + return kinks as any; + } + + + private getKinkMatchScore(aValue: string, bValue: string): number { + return _.get(kinkMatchScoreMap, `${aValue}.${bValue}`, 0); + } + + static getTagValue(tagId: number, c: Character): CharacterInfotag | undefined { return c.infotags[tagId]; } @@ -1026,6 +1136,8 @@ export class Matcher { aboveLevelScore = (1 - (aboveLevelMul * 0.5)) * Math.pow(dimensionsAboveScoreLevel, matchRatio); } + // const kinkScore = match.you.kinkScore.weighted; + log.debug( 'report.score.search', {