diff --git a/compile.bat b/compile.bat index 2a06b08270c083d45c949ee958095a248b32b52d..d91b67280d69782543c0faf24b4cc5683b099907 100644 --- a/compile.bat +++ b/compile.bat @@ -4,7 +4,7 @@ :: Set working directory pushd %~dp0 if not exist "bin\resources" mkdir bin\resources -CALL devTools/concatFiles.bat js/ "*.js" bin/fc.js +CALL devTools/concatFiles.bat js\ "*.js" bin\fc.js :: Run the appropriate compiler for the user's CPU architecture. if %PROCESSOR_ARCHITECTURE% == AMD64 ( CALL "%~dp0devTools\tweeGo\tweego_win64.exe" -o "%~dp0bin/FC_pregmod.html" --module=bin/fc.js --head devTools/head.html "%~dp0src" diff --git a/devTools/FC.d.ts b/devTools/FC.d.ts index abc179ce417d94675542045bb8b6d52c0cc05446..c1ffd2605ddeb79957033c4a4ddf9f1c835dd0b0 100644 --- a/devTools/FC.d.ts +++ b/devTools/FC.d.ts @@ -35,6 +35,32 @@ declare namespace App { noun: string; } } + + namespace SlaveSummary{ + class SmartPiercing{ + setting: { + off: string, + submissive: string, + lesbian: string, + oral: string, + humiliation: string, + anal: string, + boobs: string, + sadist: string, + masochist: string, + dom: string, + pregnancy: string, + vanilla: string, + all: string, + none: string, + monitoring: string, + men: string, + women: string, + "anti-men": string, + "anti-women": string, + } + } + } } namespace Debug {} @@ -254,8 +280,28 @@ declare namespace App { } namespace View { } namespace SlaveSummary { - type StringRenderer = (slave: App.Entity.SlaveState) => String; type AppendRenderer = (slave: App.Entity.SlaveState, parentNode: Node) => void; + + class AbbreviationState { + clothes: number; + devotion: number; + diet: number; + drugs: number; + genitalia: number; + health: number; + hormoneBalance: number; + mental: number; + nationality: number; + origins: number; + physicals: number; + race: number; + rules: number; + rulesets: number; + skills: number; + } + class State { + abbreviation: AbbreviationState; + } } } diff --git a/devTools/concatFiles.bat b/devTools/concatFiles.bat index cce8129af35c054ac8f951eef9818ff8ae4aa08d..3da9527a846693b3f9a9c87fea44c70c5b48e6f4 100644 --- a/devTools/concatFiles.bat +++ b/devTools/concatFiles.bat @@ -2,12 +2,13 @@ :: Concatenates files from dir %1 specified with wildcard %2 and outputs result to %3 :: TODO Proper temp file instead of bin\list.txt +IF EXIST %3 DEL %3 SET _LISTFILE="bin\list.txt" >%_LISTFILE% (FOR /R "%~1" %%F IN (%2) DO echo "%%F") sort /O %_LISTFILE% %_LISTFILE% ->%3 (FOR /F "usebackq delims=" %%F IN (`type "%_LISTFILE%"`) DO ( - echo /* %%F */ - type %%F +(FOR /F "usebackq delims=" %%F IN (`type "%_LISTFILE%"`) DO ( + echo /* %%F */ >> %3 + copy /b %3+%%F %3 1>NUL ) ) diff --git a/js/002-config/fc-js-init.js b/js/002-config/fc-js-init.js index 6ee8fcd98120639ab35fa353a5727ed02858bd4e..5eff87da0a2f291b10f185220944887ec5494106 100644 --- a/js/002-config/fc-js-init.js +++ b/js/002-config/fc-js-init.js @@ -17,7 +17,7 @@ App.Arcology.Cell = {}, App.Art = {}; App.Corporate = {}; App.Data = {}; -App.Data.HeroSlaves = { }; +App.Data.HeroSlaves = {}; App.Data.Weather = {}; App.Debug = {}; App.Desc = {}; diff --git a/js/003-data/gameVariableData.js b/js/003-data/gameVariableData.js index 27885b3ef3a8ee399e4eb6d95abd487c30f0c0ed..ff132f1b933430dfe5d1d1be0313437b6fc2b3b5 100644 --- a/js/003-data/gameVariableData.js +++ b/js/003-data/gameVariableData.js @@ -32,26 +32,33 @@ App.Data.defaultGameStateVariables = { cheater: 0, cash: 0, cashLastWeek: 0, + taintedSaveFile: 0, // UI content + UI: { + slaveSummary: { + abbreviation: { + clothes: 2, + devotion: 2, + diet: 2, + drugs: 2, + genitalia: 2, + health: 2, + mental: 2, + nationality: 2, + origins: 2, + physicals: 2, + race: 2, + rules: 2, + rulesets: 2, + skills: 2, + } + } + }, FSNamePref: 0, HGFormality: 1, HGSeverity: 0, - abbreviateClothes: 2, - abbreviateDevotion: 2, - abbreviateDiet: 2, - abbreviateDrugs: 2, - abbreviateGenitalia: 2, - abbreviateHealth: 2, - abbreviateMental: 2, - abbreviateNationality: 2, - abbreviateOrigins: 2, - abbreviatePhysicals: 2, - abbreviateRace: 2, - abbreviateRules: 2, - abbreviateRulesets: 2, abbreviateSidebar: 2, - abbreviateSkills: 2, adamPrinciple: 0, allowFamilyTitles: 0, allowMaleSlaveNames: false, @@ -1385,7 +1392,6 @@ App.Data.resetOnNGPlus = { compact: 1, Cash: 1, Upkeep: 1, SexSlaveCount: 1, roomPop: 1, Rep: 1, GSP: 1, Authority: 1, Security: 1, Crime: 1, confirmWeekEnd: 0, }, DefaultBirthDestination: "individually decided fates", - abbreviateHormoneBalance: 2, legendaryFacility: 0, heroSlavesPurchased : [], fcnn: [ diff --git a/js/003-data/slaveSummaryData.js b/js/003-data/slaveSummaryData.js new file mode 100644 index 0000000000000000000000000000000000000000..4e2a4f5ab0c4d580823b324aa459dc564a1af5cc --- /dev/null +++ b/js/003-data/slaveSummaryData.js @@ -0,0 +1,1404 @@ +// these ratings tables are consumed by App.UI.SlaveSummaryImpl.helpers. getNumericRating() +// this function takes a value for rating and iterates a table (in the declaration order) +// until it finds a key greater or equal to the value; the found record is returned from the function +// Briefly, each dictionary entry key -> value is read as "the highest rating that still suits -> value" or +// "up to, including" +// The idea behind this is too decrease number of comparisons and retain compatibility with simple dictionaries +// without sparse keys +App.Data.SlaveSummary = { + long: { + body: { + age: { + 17: "Underage.", + 18: "Eighteen.", + 19: "Nineteen.", + 24: "Early twenties.", + 29: "Late twenties.", + 34: "Early thirties.", + 39: "Late thirties.", + 999: "Forties." + }, + face: { // face value + 100 + 4: {desc: "Very ugly", style: "red"}, + 59: {desc: "Ugly", style: "red"}, + 89: {desc: "Unattractive", style: "red"}, + 110: {desc: "Average"}, + 140: {desc: "Attractive", style: "pink"}, + 195: {desc: "Beautiful", style: "pink"}, + 200: {desc: "Very beautiful", style: "pink"}, + }, + lips: { + 10: {desc: "Thin lips", style: "red"}, + 20: {desc: "Normal lips"}, + 40: {desc: "Pretty lips"}, + 70: {desc: "Big lips"}, + 95: {desc: "Huge lips"}, + 100: {desc: "Facepussy"} + }, + teeth: { + "crooked": {desc: "Crooked teeth.", style: "yellow"}, + "gapped": {desc: "Tooth gap.", style: "yellow"}, + "cosmetic braces": {desc: "Cosmetic braces."}, + "straightening braces": {desc: "Braces."}, + "removable": {desc: "Removable teeth."}, + "pointy": {desc: "Sharp fangs."}, + "baby": {desc: "Baby teeth."}, + "mixed": {desc: "Mixed teeth."} + }, + waist: { // waist value + 100 + 4: {desc: "Absurdly narrow waist", style: "pink"}, + 60: {desc: "Hourglass waist", style: "pink"}, + 89: {desc: "Feminine waist", style: "pink"}, + 110: {desc: "Average waist"}, + 140: {desc: "Unattractive waist", style: "red"}, + 195: {desc: "Ugly waist", style: "red"}, + 200: {desc: "Masculine waist", style: "red"} + }, + genitalia: { + dickBalls: { // indices [dick, balls] + 3: { + 3: null, + 4: "Big balls.", + 5: "Huge balls.", + 8: "Monstrous balls.", + 99: "Hyper balls." + }, + 4: { + 3: "Big dick.", + 99: "Big dick & balls." + }, + 5: { + 4: "Huge dick.", + 99: "Huge dick & balls." + }, + 8: { + 5: "Monster dong.", + 99: "Monster dick & balls." + }, + 99: { + 8: "Hyper dong.", + 99: "Hyper dick & balls." + } + }, + holes: { // indices [vagina, anus] + 2: { + 2: null, + 3: "Gaping anus.", + 4: "Permagaped anus." + }, + 3: { + 2: "Loose pussy.", + 3: "High mileage." + }, + 4: { + 3: "Cavernous pussy.", + 4: "Blown out holes." + } + } + }, + titsAss: { // indices: [boobs, butt, FSAssetExpansionist(0,1), weight+100, muscles+100] + 499: { + 2: { + 2: { // FSAssetExpansionist doesn't matter + 109: { + 130: {desc: "Girlish figure.", style: "pink"}, + }, + }, + } + }, + 799: { + 4: null, + 6: {desc: "Big ass.", style: "pink"}, + 8: {desc: "Huge ass.", style: "pink"}, + 9: {desc: "Titanic ass.", style: "pink"}, + 999: {desc: "Hyper ass.", style: "pink"}, + }, + 1999: { + 4: {desc: "Big tits.", style: "pink"}, + 999: {desc: "Big T&A.", style: "pink"} + }, + 3999: { + 6: {desc: "Huge tits.", style: "pink"}, + 999: {desc: "Huge T&A.", style: "pink"} + }, + 11999: { + 8: {desc: "Monstrous tits.", style: "pink"}, + 999: {desc: "Enormous T&A.", style: "pink"}, + }, + 100000: { + 9: {desc: "Immobilizing tits.", style: "pink"}, + 999: {desc: "Hyper T&A.", style: "pink"} + } + }, + weight: { // indices: [weigh + 100, FSHedonisticDecadence (0,1), hips + 2, ] + 4: {desc: "Emaciated", style: "red"}, + 69: { + 2: { // FSHedonisticDecadence doesn't matter + 0: {desc: "Model-thin"}, + 5: {desc: "Very thin", style: "red"} + } + }, + 89: {desc: "Thin"}, + 110: {desc: "Trim"}, + 130: {desc: "Plush"}, + 195: { + 0: {desc: "Overweight", style: "red"}, + 1: { + 3: {desc: "Overweight", style: "red"}, + 5: {desc: "Nicely chubby"} + } + }, + 230: { + 0: {desc: "Fat", style: "red"}, + 1: { + 4: {desc: "Fat", style: "red"}, + 5: {desc: "Pleasantly soft and shapely"} + } + }, + 260: { + 0: {desc: "Obese", style: "red"}, + 1: {desc: "Amazingly voluptuous"} + }, + 290: { + 0: {desc: "Super Obese", style: "red"}, + 1: {desc: "SSBBW"} + }, + 999: { + 0: {desc: "Dangerously Obese", style: "red"}, + 1: {desc: "Perfectly massive"} + } + }, + hipsAss: { + 0: {desc: "Disproportionately small butt.", style: "red"}, + 1: {desc: "Disproportionately big butt.", style: "red"} + }, + muscles: { // indices: [muscles + 100, FSPhysicalIdealist(0,1) ] + 4: {desc:"Frail", style:"red"}, + 69: { + 0: {desc: "Very weak"}, + 1: {desc: "Very weak", style: "red"} + }, + 94: { + 0: {desc: "Weak"}, + 1: {desc: "Weak", style: "red"} + }, + 105: {desc: "Soft"}, + 130: {desc: "Toned"}, + 150: {desc: "Fit"}, + 195: {desc: "Muscular"}, + 100: {desc: "Hugely muscular"} + } + }, + mental: { + devotion: { // devotion value + 100 + 4: {desc: "Very hateful", style: ["devotion", "hateful"]}, + 49: {desc: "Hateful", style: ["devotion", "hateful"]}, + 79: {desc: "Resistant", style: ["devotion", "resistant"]}, + 120: {desc: "Ambivalent", style: ["devotion", "ambivalent"]}, + 150: {desc: "Accepting", style: ["devotion", "accept"]}, + 195: {desc: "Devoted", style: ["devotion", "devoted"]}, + 200: {desc: "Worshipful", style: ["devotion", "worship"]} + }, + trust: { // first key: trust + 100, second key: devotion + 100 + 4: {desc: "Extremely terrified", style: ["trust", "extremely-terrified"]}, + 49: {desc: "Terrified", style: ["trust", "terrified"]}, + 79: {desc: "Frightened", style: ["trust", "frightened"]}, + 120: {desc: "Fearful", style: ["trust", "fearful"]}, + 150: { + 79: {desc: "Careful", style: ["defiant", "careful"]}, + 200: {desc: "Careful", style: ["trust", "careful"]}, + }, + 195: { + 79: {desc: "Bold", style: ["defiant", "bold"]}, + 200: {desc: "Trusting", style: ["trust", "trusting"]}, + }, + 200: { + 79: {desc: "Defiant", style: ["defiant", "full"]}, + 200: {desc: "Profoundly trusting", style: ["trust", "prof-trusting"]}, + } + }, + education: { // index: intelligenceImplant + 15 + 0: ", hindered", + 29: "", + 44: ", educated", + 99: ", well educated" + }, + intelligence: { // index: intelligence + 100 + 4: {desc: "Moronic", style: "orangered"}, + 49: {desc: "Very slow", style: "orangered"}, + 84: {desc: "Slow", style: "orangered"}, + 115: {desc: "Average intelligence"}, + 150: {desc: "Smart", style: "deepskyblue"}, + 195: {desc: "Very smart", style: "deepskyblue"}, + 230: {desc: "Brilliant", style: "deepskyblue"}, + 999: {desc: "Genius", style: "deepskyblue"}, + }, + behavioralFlaw: { + "arrogant": "Arrogant.", + "bitchy": "Bitchy.", + "odd": "Odd.", + "hates men": "Hates men.", + "hates women": "Hates women.", + "gluttonous": "Stress eater.", + "anorexic": "Anorexic", + "devout": "Devoutly religious.", + "liberated": "Mentally liberated.", + } + }, + fetish: { // indices [fetish, fetishStrength] + "submissive": { + 60: "Submissive tendencies", + 95: "Submissive", + 100: "Complete submissive" + }, + "cumslut": { + 60: "Prefers oral", + 95: "Oral fixation", + 100: "Cumslut" + }, + "humiliation": { + 60: "Interest in humiliation", + 95: "Exhibitionist", + 100: "Humiliation slut" + }, + "buttslut": { + 60: "Prefers anal", + 95: "Anal fixation", + 100: "Buttslut" + }, + "boobs": { + 60: "Loves boobs", + 95: "Breast fixation", + 100: "Boobslut" + }, + "sadist": { + 60: "Sadistic tendencies", + 95: "Sadist", + 100: "Complete sadist" + }, + "masochist": { + 60: "Masochistic tendencies", + 95: "Masochist", + 100: "Complete masochist" + }, + "dom": { + 60: "Dominant tendencies", + 95: "Dominant", + 100: "Complete dom" + }, + "pregnancy": { + 60: "Interest in impregnation", + 95: "Pregnancy kink", + 100: "Pregnancy fetish" + }, + "none": "Sexually vanilla" + }, + clothes: { + "Western clothing": "Chaps.", + "a Santa dress": "Santa dress.", + "a ball gown": "Ball gown.", + "a bimbo outfit": "Bimbo outfit.", + "a biyelgee costume": "Biyelgee costume.", + "a bra": "Nice bra.", + "a bunny outfit": "Bunny outfit.", + "a burkini": "Burkini.", + "a burqa": "Burqa.", + "a button-up shirt and panties": "Button-up shirt, panties.", + "a button-up shirt": "Nice button-up shirt.", + "a chattel habit": "Chattel habit.", + "a cheerleader outfit": "Cheerleader.", + "a comfortable bodysuit": "Bodysuit.", + "a courtesan dress": "Courtesan dress.", + "a cybersuit": "Cybersuit.", + "a dirndl": "Dirndl.", + "a fallen nuns habit": "Slutty habit.", + "a gothic lolita dress": "Gothic lolita dress.", + "a halter top dress": "Halter top dress.", + "a hanbok": "Hanbok.", + "a hijab and abaya": "Hijab and abaya.", + "a hijab and blouse": "Hijab and blouse.", + "a huipil": "Huipil.", + "a kimono": "Kimono.", + "a klan robe": "Klan robe.", + "a latex catsuit": "Nice latex.", + "a leotard": "Leotard.", + "a long qipao": "Long Qipao.", + "a maternity dress": "Maternity dress.", + "a military uniform": "Military uniform.", + "a mini dress": "Mini dress.", + "a monokini": "Monokini.", + "a mounty outfit": "Mounty outfit.", + "a nice maid outfit": "Nice maid.", + "a nice nurse outfit": "Nice nurse.", + "a nice pony outfit": "Nice pony outfit.", + "a niqab and abaya": "Niqab and abaya.", + "a one-piece swimsuit": "Swimsuit.", + "a penitent nuns habit": "Cilice.", + "a police uniform": "Police uniform.", + "a red army uniform": "Red Army uniform.", + "a scalemail bikini": "Scalemail bikini.", + "a schoolgirl outfit": "Schoolgirl outfit.", + "a schutzstaffel uniform": "Schutzstaffel uniform.", + "a skimpy loincloth": "Skimpy loincloth.", + "a slave gown": "Slave gown.", + "a slutty klan robe": "Slutty klan robe.", + "a slutty maid outfit": "Slutty maid.", + "a slutty nurse outfit": "Slutty nurse.", + "a slutty outfit": "Slutty outfit.", + "a slutty pony outfit": "Slutty pony outfit.", + "a slutty qipao": "Slutty qipao.", + "a slutty schutzstaffel uniform": "Slutty Schutzstaffel uniform.", + "a sports bra": "Sports bra.", + "a string bikini": "String bikini.", + "a striped bra": "Striped bra.", + "a succubus outfit": "Succubus outfit.", + "a sweater and cutoffs": "Jean shorts, sweater.", + "a sweater and panties": "Sweater, panties.", + "a sweater": "Nice sweater.", + "a t-shirt and jeans": "Blue jeans, t-shirt.", + "a t-shirt and panties": "Panties, t-shirt.", + "a t-shirt and thong": "Thong, t-shirt.", + "a t-shirt": "T-shirt.", + "a tank-top and panties": "Tank-top, panties.", + "a tank-top": "Nice tank-top.", + "a thong": "Nice thong.", + "a toga": "Toga.", + "a tube top and thong": "Tube top, thong.", + "a tube top": "Nice tube top.", + "an apron": "Apron.", + "an oversized t-shirt and boy shorts": "Over-sized t-shirt, boy shorts.", + "an oversized t-shirt": "Nice over-sized t-shirt.", + "attractive lingerie for a pregnant woman": "Preggo lingerie.", + "attractive lingerie": "Nice lingerie.", + "battlearmor": "Battlearmor.", + "battledress": "Battledress.", + "body oil": "Body oil.", + "boyshorts": "Boy shorts.", + "chains": "Chains.", + "clubslut netting": "Netting.", + "conservative clothing": "Conservative clothing.", + "cutoffs and a t-shirt": "Cutoffs, t-shirt.", + "cutoffs": "Jean shorts.", + "harem gauze": "Harem outfit.", + "jeans": "Tight blue jeans.", + "kitty lingerie": "Kitty lingerie.", + "leather pants and a tube top": "Leather pants, tube top.", + "leather pants and pasties": "Leather pants, pasties.", + "leather pants": "Nice leather pants.", + "lederhosen": "Lederhosen.", + "nice business attire": "Nice suit.", + "overalls": "Overalls.", + "panties and pasties": "Pasties, panties.", + "panties": "Nice panties.", + "pasties": "Pasties.", + "restrictive latex": "Bondage latex.", + "shibari ropes": "Shibari.", + "slutty business attire": "Slutty suit.", + "slutty jewelry": "Bangles.", + "spats and a tank top": "Spats, tank top.", + "sport shorts and a sports bra": "Shorts, bra.", + "sport shorts and a t-shirt": "Nice sport shorts, shirt.", + "sport shorts": "Shorts.", + "stretch pants and a crop-top": "Stretch pants, crop-top.", + "striped panties": "Striped panties.", + "striped underwear": "Striped underwear", + "uncomfortable straps": "Leather straps.", + + }, + accessory: { + collar: { + "ancient Egyptian": "Wesekh.", + "ball gag": "Ball gag.", + "bell collar": "Bell collar.", + "bit gag": "Bit gag.", + "bowtie": "Bowtie collar.", + "cruel retirement counter": "Cruel counter collar.", + "dildo gag": "Dildo gag.", + "heavy gold": "Gold collar.", + "leather with cowbell": "Cowbell collar.", + "massive dildo gag": "Throat-bulging dildo gag.", + "neck corset": "Neck corset.", + "nice retirement counter": "Nice counter collar.", + "porcelain mask": "Porcelain mask.", + "preg biometrics": "Pregnancy biometrics collar.", + "pretty jewelry": "Pretty collar.", + "satin choker": "Satin choker.", + "shock punishment": "Shock collar.", + "silk ribbon": "Silken ribbon.", + "stylish leather": "Stylish leather collar.", + "tight steel": "Steel collar.", + "uncomfortable leather": "Leather collar.", + }, + belly: { + "a corset": "Corset.", + "a huge empathy belly": "Huge fake belly.", + "a large empathy belly": "Large fake belly.", + "a medium empathy belly": "Medium fake belly.", + "a small empathy belly": "Small fake belly.", + "a support band": "Support band.", + "an extreme corset": "Extreme corsetage.", + "shapewear": "Shapewear.", + }, + vaginal: { + "bullet vibrator": "Attached bullet vibrator.", + "smart bullet vibrator": "Attached smart bullet vibrator.", + "dildo": "Vaginal dildo.", + "large dildo": "Large vaginal dildo.", + "huge dildo": "Huge vaginal dildo.", + "long dildo": "Long vaginal dildo.", + "long, large dildo": "Long and large vaginal dildo.", + "long, huge dildo": "Long and wide vaginal dildo.", + }, + buttplug: { + "plug": "Buttplug.", + "large plug": "Large buttplug.", + "huge plug": "Huge buttplug.", + "long plug": "Long buttplug.", + "long, large plug": "Large, long buttplug.", + "long, huge plug": "Enormous buttplug.", + }, + buttplugAttachment: { + "tail": "Attached tail.", + "cat tail": "Attached cat tail.", + "fox tail": "Attached fox tail.", + "cow tail": "Attached cow tail.", + } + }, + diet: { + "restricted": "Dieting.", + "fattening": "Gaining weight.", + "corrective": "Corrective.", + "XX": "Estrogen rich.", + "XY": "Testosterone rich.", + "XXY": "Futanari mix.", + "muscle building": "Pumping iron.", + "slimming": "Slimming down.", + "cum production": "Cum production.", + "cleansing": "Cleansing.", + "fertility": "Fertility.", + }, + specialDiet: { // index: dietCum + 3 * dietMilk + 0: null, + 1: "Cum Added.", + 2: "Cum Based.", + 3: "Milk Based.", + 4: "Milk & Cum Added.", + 5: "Cum Based with Milk.", + 6: "Milk Added.", + 7: "Milk Based with Cum.", + 8: "Cum and Milk Based." + }, + race: { + "white": "Caucasian", + "asian": "Asian", + "indo-aryan": "Indo-aryan", + "latina": "Latina", + "middle eastern": "Middle Eastern", + "black": "Black", + "pacific islander": "Pacific Islander", + "malay": "Malay", + "amerindian": "Amerindian", + "semitic": "Semitic", + "southern european": "Southern European", + "mixed race": "Mixed race", + }, + hormoneBalance: { // rating is hormoneBalance value + 500 + 100: "Overwhelmingly masculine", + 200: "Extremely masculine", + 300: "Heavily masculine", + 400: "Very masculine", + 479: "Masculine", + 520: "Neutral", + 599: "Feminine", + 699: "Very feminine", + 799: "Heavily feminine", + 899: "Extremely feminine", + 1000: "Overwhelmingly feminine" + }, + health: { + illness: { + 0: null, + 1: {desc: "Sick", style: "yellow"}, + 2: {desc: "Ill", style: ["red", "strong"]}, + 3: {desc: "Very ill", style: ["red", "strong"]}, + 4: {desc: "Terribly ill", style: ["red", "strong"]}, + 5: {desc: "Dangerously ill", style: ["red", "strong"]} + }, + tiredness: { + 30: {desc: "Energetic", style: "green"}, + 60: {desc: "Tired", style: "yellow"}, + 90: {desc: "Fatigued", style: "orange"}, + 100: {desc: "Exhausted", style: ["red", "strong"]} + }, + health: { // health + 100 + 10: {desc: "On the edge of death", style: ["red", "strong"]}, + 49: {desc: "Extremely unhealthy", style: ["red", "strong"]}, + 79: {desc: "Unhealthy", style: ["red", "strong"]}, + 120: {desc: "healthy", style: "yellow"}, + 150: {desc: "Very healthy", style: "green"}, + 190: {desc: "Extremely healthy", style: "green"}, + 200: {desc: "Unnaturally healthy", style: "green"} + }, + }, + prestige: { + 0: null, + 1: {desc: "Prestigious.", style: "green"}, + 2: {desc: "Very prestigious.", style: "green"}, + 3: {desc: "Extremely prestigious.", style: "green"}, + }, + pornPrestige: { + 0: null, + 1: {desc: "Porn amateur.", style: "green"}, + 2: {desc: "Porn slut.", style: "green"}, + 3: {desc: "Porn star.", style: "green"}, + }, + accent: { + 0: null, + 1: {desc: "Cute accent.", style: "pink"}, + 2: {desc: "Accent."}, + 3: {desc: "Bad accent.", style: "red"}, + 4: {desc: "No language skills.", style: "red"}, + }, + smartPiercing: { + setting: { + "off": "SP off.", + "submissive": "SP: submissive.", + "lesbian": "SP: lesbian", + "oral": "SP: oral.", + "humiliation": "SP: humiliation.", + "anal": "SP: anal.", + "boobs": "SP: breasts.", + "sadist": "SP: sadism.", + "masochist": "SP: masochism.", + "dom": "SP: dominance.", + "pregnancy": "SP: pregnancy.", + "vanilla": "SP: vanilla.", + "all": "SP: all.", + "none": "SP: none.", + "monitoring": "SP: monitoring", + "men": "SP: men", + "women": "SP: women", + "anti-men": "SP: anti men", + "anti-women": "SP: anti women", + } + }, + skills: { + entertainment: { + 10: null, + 29: {desc: "Basic entertainer", style: "aquamarine"}, + 59: {desc: "Skilled entertainer", style: "aquamarine"}, + 99: {desc: "Expert entertainer", style: "aquamarine"}, + 999: {desc: "Masterful entertainer", style: "aquamarine"} + }, + sex: { // indices: [sexSkills, hasVagina(0,1)] + 30: {desc: "Sexually unskilled", style: "aquamarine"}, + 90: {desc: "Sexually skilled", style: "aquamarine"}, + 120: {desc: "Sexual expert", style: "aquamarine"}, + 180: { + 0: {desc: "Masterful shemale", style: "aquamarine"}, + 1: {desc: "Sexual expert", style: "aquamarine"} + }, + 9999: {desc: "Sex master", style: "aquamarine"} + }, + whoring: { + 10: null, + 29: {desc: "Basic whore", style: "aquamarine"}, + 59: {desc: "Skilled whore", style: "aquamarine"}, + 99: {desc: "Expert whore", style: "aquamarine"}, + 999: {desc: "Masterful whore", style: "aquamarine"} + }, + mss: {desc: "Masterful Sex Slave.", style: "aquamarine"}, + fighter: {desc: "Trained fighter.", style: "aquamarine"}, + }, + sexDrive: { + XX: { + 5: {desc: "Disgusted by men", style: "red"}, + 15: {desc: "Turned off by men", style: "red"}, + 35: {desc: "Not attracted to men", style: "red"}, + 65: {desc: "Indifferent to men"}, + 85: {desc: "Attracted to men", style: "green"}, + 95: {desc: "Aroused by men", style: "green"}, + 999: {desc: "Passionate about men", style: "green"} + }, + XY: { + 5: {desc: "disgusted by women", style: "red"}, + 15: {desc: "turned off by women", style: "red"}, + 35: {desc: "not attracted to women", style: "red"}, + 65: {desc: "indifferent to women"}, + 85: {desc: "attracted to women", style: "green"}, + 95: {desc: "aroused by women", style: "green"}, + 999: {desc: "passionate about women", style: "green"} + }, + energy: { + 20: {desc: "No sex drive", style: "red"}, + 40: {desc: "Poor sex drive", style: "red"}, + 60: {desc: "Average sex drive", style: "yellow"}, + 80: {desc: "Good sex drive", style: "green"}, + 95: {desc: "Powerful sex drive", style: "green"}, + 999: {desc: "Nymphomaniac!", style: "green"} + }, + synergy: { + omni: {desc: "Omnisexual!", style: "green"}, + nymphomni: {desc: "Omnisexual nymphomaniac!", style: "green"} + } + } + }, + short: { + body: { + age: { + 18: "18", + 19: "19", + 24: "Ea20s", + 29: "Lt20s", + 34: "Ea30s", + 39: "Lt30s", + 999: "40s" + }, + face: { // face value + 100 + 4: {desc: "Face---", style: "red"}, + 59: {desc: "Face--", style: "red"}, + 89: {desc: "Face-", style: "red"}, + 110: {desc: "Face"}, + 140: {desc: "Face+", style: "pink"}, + 195: {desc: "Face++", style: "pink"}, + 200: {desc: "Face+++", style: "pink"}, + }, + lips: { + 10: {desc: "Lips-", style: "red"}, + 20: {desc: "Lips"}, + 40: {desc: "Lips+"}, + 70: {desc: "Lips++"}, + 95: {desc: "Lips+++"}, + 100: {desc: "Facepussy"} + }, + teeth: { + "crooked": {desc: "Cr Teeth", style: "yellow"}, + "gapped": {desc: "Gap", style: "yellow"}, + "cosmetic braces": {desc: "Cos Braces"}, + "straightening braces": {desc: "Braces"}, + "removable": {desc: "Rem Teeth"}, + "pointy": {desc: "Fangs"}, + "baby": {desc: "Baby"}, + "mixed": {desc: "Mixed"} + }, + waist: { // waist value + 100 + 4: {desc: "Wst+++", style: "pink"}, + 60: {desc: "Wst++", style: "pink"}, + 89: {desc: "Wst+", style: "pink"}, + 110: {desc: "Wst"}, + 140: {desc: "Wst-", style: "red"}, + 195: {desc: "Wst--", style: "red"}, + 200: {desc: "Wst---", style: "red"} + }, + genitalia:{ + dickBalls: { // indices [dick, balls] + 3: { + 3: null, + 4: "Balls", + 5: "Balls+", + 8: "Balls++", + 99: "Balls+++" + }, + 4: { + 3: "Dick", + 99: "Junk" + }, + 5: { + 4: "Dick+", + 99: "Junk+" + }, + 8: { + 5: "Dick++", + 99: "Junk++" + }, + 99: { + 8: "Dick+++", + 99: "Junk+++" + } + }, + holes: { // indices [vagina, anus] + 2: { + 2: null, + 3: "A+", + 4: "A++" + }, + 3: { + 2: "V+", + 3: "V+A+" + }, + 4: { + 3: "V++", + 4: "V++A++" + } + } + }, + titsAss: { // indices: [boobs, butt, FSAssetExpansionist(0,1), weight+100, muscles+100] + 499: { + 2: { + 2: { // FSAssetExpansionist doesn't matter + 109: { + 130: {desc: "Girlish", style: "pink"}, + }, + }, + } + }, + 799: { + 4: null, + 6: {desc: "Ass", style: "pink"}, + 8: {desc: "Ass+", style: "pink"}, + 9: {desc: "Ass++", style: "pink"}, + 999: {desc: "Ass+++", style: "pink"}, + }, + 1999: { + 4: {desc: "Boobs", style: "pink"}, + 999: {desc: "T&A", style: "pink"} + }, + 3999: { + 6: {desc: "Boobs+", style: "pink"}, + 999: {desc: "T&A+", style: "pink"} + }, + 11999: { + 8:{desc: "Boobs++", style: "pink"}, + 999: {desc: "T&A++", style: "pink"}, + }, + 100000: { + 9: {desc: "Boobs+++", style: "pink"}, + 999: {desc: "T&A+++", style: "pink"} + } + }, + weight: { // indices: [weigh + 100, FSHedonisticDecadence (0,1), hips + 2, ] + 4: {desc: "W---", style: ["red", "strong"]}, + 69: { + 99: { // FSHedonisticDecadence doesn't matter + 0: {desc: "W--", style: "strong"}, + 5: {desc: "W--", style: ["red", "strong"]} + } + }, + 89: {desc: "W-", style: "strong"}, + 110: {desc: "W", style: "strong"}, + 130: {desc: "W+", style: "strong"}, + 195: { + 0: {desc: "W++", style: ["red", "strong"]}, + 1: { + 3: {desc: "W++", style: ["red", "strong"]}, + 5: {desc: "W++", style: "strong"} + } + }, + 230: { + 0: {desc: "W+++", style: ["red", "strong"]}, + 1: { + 4: {desc: "W+++", style: ["red", "strong"]}, + 5: {desc: "W+++", style: "strong"} + } + }, + 260: { + 0: {desc: "W++++", style: ["red", "strong"]}, + 1: {desc: "W++++", style: "strong"} + }, + 290: { + 0: {desc: "W+++++", style: ["red", "strong"]}, + 1: {desc: "W+++++", style: "strong"} + }, + 999: { + 0: {desc: "W++++++", style: ["red", "strong"]}, + 1: {desc: "W++++++", style: "strong"} + } + }, + hipsAss: { + 0: {desc: "Disp-", style: "red"}, + 1: {desc: "Disp+", style: "red"} + }, + muscles: { // indices: [muscles + 100, FSPhysicalIdealist(0,1) ] + 4: {desc:"Weak++", style:"red"}, + 69: { + 0: {desc: "Soft+"}, + 1: {desc: "Weak+", style: "red"} + }, + 94: { + 0: {desc: "Soft"}, + 1: {desc: "Weak", style: "red"} + }, + 105: {desc: "Soft"}, + 130: {desc: "Toned"}, + 150: {desc: "Fit"}, + 195: {desc: "Musc+"}, + 100: {desc: "Musc++"} + } + }, + mental: { + devotion: { // devotion value + 100 + 4: {desc: "VHate", style: ["devotion", "hateful"]}, + 49: {desc: "Hate", style: ["devotion", "hateful"]}, + 79: {desc: "Res", style: ["devotion", "resistant"]}, + 120: {desc: "Ambiv", style: ["devotion", "ambivalent"]}, + 150: {desc: "Accept", style: ["devotion", "accept"]}, + 195: {desc: "Devo", style: ["devotion", "devoted"]}, + 200: {desc: "Wor", style: ["devotion", "worship"]} + }, + trust: { // first key: trust + 100, second key: devotion + 100 + 4: {desc: "ETerr", style: ["trust", "extremely-terrified"]}, + 49: {desc: "Terr", style: ["trust", "terrified"]}, + 79: {desc: "Fright", style: ["trust", "frightened"]}, + 120: {desc: "Fear", style: ["trust", "fearful"]}, + 150: { + 79: {desc: "Caref", style: ["defiant", "careful"]}, + 200: {desc: "Caref", style: ["trust", "careful"]}, + }, + 195: { + 79: {desc: "Bold", style: ["defiant", "bold"]}, + 200: {desc: "Trust", style: ["trust", "trusting"]}, + }, + 200: { + 79: {desc: "Defiant", style: ["defiant", "full"]}, + 200: {desc: "VTrust", style: ["trust", "prof-trusting"]}, + } + }, + education: { // index: intelligenceImplant + 15 + 0: "(e-)", + 29: "", + 44: "(e)", + 99: "(e+)" + }, + intelligence: { // index: intelligence + 100 + 4: {desc: "I---", style: "orangered"}, + 49: {desc: "I--", style: "orangered"}, + 84: {desc: "I-", style: "orangered"}, + 115: {desc: "I"}, + 150: {desc: "I+", style: "deepskyblue"}, + 195: {desc: "I++", style: "deepskyblue"}, + 230: {desc: "I+++", style: "deepskyblue"}, + 999: {desc: "I++++", style: "deepskyblue"}, + }, + behavioralFlaw: { + "arrogant": "Arrog", + "bitchy": "Bitchy", + "odd": "Odd", + "hates men": "Men-", + "hates women": "Women-", + "gluttonous": "Glut", + "anorexic": "Ano", + "devout": "Dev", + "liberated": "Lib", + }, + sexualFlaw: { + "hates oral": {desc: "Oral-", style: "red"}, + "hates anal": {desc: "Anal-", style: "red"}, + "hates penetration": {desc: "Fuck-", style: "red"}, + "shamefast": {desc: "Shame", style: "red"}, + "idealistic": {desc: "Ideal", style: "red"}, + "repressed": {desc: "Repre", style: "red"}, + "apathetic": {desc: "Apath", style: "red"}, + "crude": {desc: "Crude", style: "red"}, + "judgemental": {desc: "Judge", style: "red"}, + "cum addict": {desc: "CumAdd", style: "yellow"}, + "anal addict": {desc: "AnalAdd", style: "yellow"}, + "attention whore": {desc: "Attention", style: "yellow"}, + "breast growth": {desc: "BoobObsess", style: "yellow"}, + "abusive": {desc: "Abusive", style: "yellow"}, + "malicious": {desc: "Malice", style: "yellow"}, + "self hating": {desc: "SelfHatr", style: "yellow"}, + "neglectful": {desc: "SelfNeglect", style: "yellow"}, + "breeder": {desc: "BreedObsess", style: "yellow"}, + }, + behavioralQuirk: { + "confident": "Confid", + "cutting": "Cutting", + "funny": "Funny", + "fitness": "Fit", + "adores women": "Women+", + "adores men": "Men+", + "insecure": "Insec", + "sinful": "Sinf", + "advocate": "Advoc", + }, + sexualQuirk: { + "gagfuck queen": "Gagfuck", + "painal queen": "Painal", + "strugglefuck queen": "Struggle", + "tease": "Tease", + "romantic": "Romantic", + "perverted": "Perverted", + "caring": "Caring", + "unflinching": "Unflinch", + "size queen": "SizeQ", + } + }, + health: { + tiredness: { + 30: {desc: "Ene", style: "green"}, + 60: {desc: "Tir", style: "yellow"}, + 90: {desc: "Tir+", style: "orange"}, + 100: {desc: "Exh", style: ["red", "strong"]} + } + }, + accent: { + 1: {desc: "Acc", style: "pink"}, + 2: {desc: "Acc-"}, + 3: {desc: "Acc--"}, + 4: {desc: "Acc--", style: "red"}, + }, + fetish: { // indices [fetish, fetishStrength] + "submissive": { + 60: "Sub", + 95: "Sub+", + 100: "Sub++" + }, + "cumslut": { + 60: "Oral", + 95: "Oral+", + 100: "Oral++" + }, + "humiliation": { + 60: "Humil", + 95: "Humil+", + 100: "Humil++" + }, + "buttslut": { + 60: "Anal", + 95: "Anal+", + 100: "Anal++" + }, + "boobs": { + 60: "Boobs", + 95: "Boobs+", + 100: "Boobs++" + }, + "sadist": { + 60: "Sadist", + 95: "Sadist+", + 100: "Sadist++" + }, + "masochist": { + 60: "Pain", + 95: "Pain+", + 100: "Pain++" + }, + "dom": { + 60: "Dom", + 95: "Dom+", + 100: "Dom++" + }, + "pregnancy": { + 60: "Preg", + 95: "Preg+", + 100: "Preg++" + }, + "none" : "Vanilla" + }, + prestige: { + 0: null, + 1: {desc: "Prest", style: "green"}, + 2: {desc: "Prest+", style: "green"}, + 3: {desc: "Prest++", style: "green"}, + }, + pornPrestige: { + 0: null, + 1: {desc: "PPrest", style: "green"}, + 2: {desc: "PPrest+", style: "green"}, + 3: {desc: "PPrest++", style: "green"}, + }, + diet: { + "restricted": "Di:W-", + "fattening": "Di:W+", + "corrective": "Di:W=", + "XX": "Di:XX+", + "XY": "Di:XY+", + "XXY": "Di:XXY+", + "muscle building": "Di:M+", + "slimming": "Di:M-", + "cum production": "Di:C+", + "cleansing": "Di:H+", + "fertility": "Di:F+", + + }, + specialDiet: { // index: dietCum + 3 * dietMilk + 0: null, + 1: "Cum+", + 2: "Cum++", + 3: "Milk+", + 4: "Cum+ Milk+", + 5: "Cum++ Milk+", + 6: "Milk++", + 7: "Cum+ Milk++", + 8: "Cum++ Milk++" + }, + drugs: { + "breast injections": "Boobs+", + "intensive breast injections": "Boobs++", + "hyper breast injections": "Boobs+++", + "nipple enhancers": "Nipple+", + "butt injections": "Butt+", + "intensive butt injections": "Butt++", + "hyper butt injections": "Butt+++", + "lip injections": "Lip+", + "fertility drugs": "Fert+", + "super fertility drugs": "Fert++", + "penis enhancement": "Dick+", + "intensive penis enhancement": "Dick++", + "hyper penis enhancement": "Dick+++", + "testicle enhancement": "Balls+", + "intensive testicle enhancement": "Balls++", + "hyper testicle enhancement": "Balls+++", + "psychosuppressants": "Psych-", + "psychostimulants": "Psych+", + "steroids": "Ster", + "female hormone injections": "HormXX++", + "male hormone injections": "HormXY++", + "hormone enhancers": "Horm+", + "hormone blockers": "Horm-", + "anti-aging cream": "Age-", + "appetite suppressors": "ApSup", + "penis atrophiers": "Dick-", + "testicle atrophiers": "Balls-", + "clitoris atrophiers": "Clit-", + "labia atrophiers": "Labia-", + "nipple atrophiers": "Nipple-", + "lip atrophiers": "Lip-", + "breast redistributors": "Breast-", + "butt redistributors": "Butt-", + "sag-B-gone": "AntiSag", + "growth stimulants": "GroStim", + "priapism agents": "Erection" + }, + nationality: { // NOTE this dictionary lacks "Zimbabwean" key, which is a spacial case + "Afghan": "Afg", + "Albanian": "Alb", + "Algerian": "Alg", + "American": "USA", + "Andorran": "And", + "Angolan": "Ang", + "Antiguan": "AB", + "Argentinian": "Arg", + "Armenian": "Arm", + "Aruban": "Aru", + "Australian": "Aus", + "Austrian": "Aut", + "Azerbaijani": "Aze", + "Bahamian": "Bah", + "Bahraini": "Bah", + "Bangladeshi": "Bgd", + "Barbadian": "Bar", + "Belarusian": "Ber", + "Belgian": "Bel", + "Belizean": "Blz", + "Beninese": "Ben", + "Bermudian": "Bmd", + "Bhutanese": "Bhu", + "Bissau-Guinean": "GB", + "Bolivian": "Bol", + "Bosnian": "Bos", + "Brazilian": "Bra", + "British": "UK", + "Bruneian": "Bru", + "Bulgarian": "Bul", + "Burkinabé": "BF", + "Burmese": "Bur", + "Burundian": "Bnd", + "Cambodian": "Kam", + "Cameroonian": "Cam", + "Canadian": "Can", + "Cape Verdean": "CV", + "Catalan": "Cat", + "Central African": "CAR", + "Chadian": "Cha", + "Chilean": "Chl", + "Chinese": "Chi", + "Colombian": "Col", + "Comorian": "Com", + "Congolese": "RC", + "a Cook Islander": "CI", + "Costa Rican": "CR", + "Croatian": "Cro", + "Cuban": "Cub", + "Curaçaoan": "Cur", + "Cypriot": "Cyp", + "Czech": "Cze", + "Danish": "Den", + "Djiboutian": "Dji", + "Dominican": "DR", + "Dominiquais": "Dom", + "Dutch": "Nld", + "East Timorese": "ET", + "Ecuadorian": "Ecu", + "Egyptian": "Egy", + "Emirati": "UAE", + "Equatoguinean": "EG", + "Eritrean": "Eri", + "Estonian": "Est", + "Ethiopian": "Eth", + "Fijian": "Fij", + "Filipina": "Phl", + "Finnish": "Fin", + "French": "Fra", + "French Guianan": "FG", + "French Polynesian": "FP", + "Gabonese": "Gab", + "Gambian": "Gam", + "Georgian": "Geo", + "German": "Ger", + "Ghanan": "Gha", + "Greek": "Gre", + "Greenlandic": "Grn", + "Grenadian": "Gda", + "Guamanian": "Gua", + "Guatemalan": "Gtm", + "Guinean": "Gui", + "Guyanese": "Guy", + "Haitian": "Hai", + "Honduran": "Hon", + "Hungarian": "Hun", + "I-Kiribati": "Kir", + "Icelandic": "Ice", + "Indian": "Ind", + "Indonesian": "Idn", + "Iranian": "Irn", + "Iraqi": "Irq", + "Irish": "Irl", + "Israeli": "Isr", + "Italian": "Ita", + "Ivorian": "IC", + "Jamaican": "Jam", + "Japanese": "Jpn", + "Jordanian": "Jor", + "Kazakh": "Kaz", + "Kenyan": "Ken", + "Kittitian": "SKN", + "Korean": "Kor", + "Kosovan": "Kos", + "Kurdish": "Kur", + "Kuwaiti": "Kuw", + "Kyrgyz": "Kyr", + "Laotian": "Lao", + "Latvian": "Lat", + "Lebanese": "Lbn", + "Liberian": "Lib", + "Libyan": "Lby", + "a Liechtensteiner": "Lie", + "Lithuanian": "Lit", + "Luxembourgian": "Lux", + "Macedonian": "Mac", + "Malagasy": "Mad", + "Malawian": "Mwi", + "Malaysian": "Mys", + "Maldivian": "Mdv", + "Malian": "Mal", + "Maltese": "Mlt", + "Marshallese": "MI", + "Mauritanian": "Mta", + "Mauritian": "Mts", + "Mexican": "Mex", + "Micronesian": "FSM", + "Moldovan": "Mol", + "Monégasque": "Mnc", + "Mongolian": "Mon", + "Montenegrin": "Mng", + "Moroccan": "Mor", + "Mosotho": "Les", + "Motswana": "Bot", + "Mozambican": "Moz", + "Namibian": "Nam", + "Nauruan": "Nau", + "Nepalese": "Npl", + "New Caledonian": "NC", + "a New Zealander": "NZ", + "Ni-Vanuatu": "Van", + "Nicaraguan": "Nic", + "Nigerian": "Nga", + "Nigerien": "Ngr", + "Niuean": "Niu", + "Norwegian": "Nor", + "Omani": "Omn", + "Pakistani": "Pak", + "Palauan": "Plu", + "Palestinian": "Pal", + "Panamanian": "Pan", + "Papua New Guinean": "PNG", + "Paraguayan": "Par", + "Peruvian": "Per", + "Polish": "Pol", + "Portuguese": "Por", + "Puerto Rican": "PR", + "Qatari": "Qat", + "Romanian": "Rom", + "Russian": "Rus", + "Rwandan": "Rwa", + "Sahrawi": "Sah", + "Saint Lucian": "SL", + "Salvadoran": "ES", + "Sammarinese": "SM", + "Samoan": "Sam", + "São Toméan": "STP", + "Saudi": "Sau", + "Scottish": "Sco", + "Senegalese": "Sen", + "Serbian": "Srb", + "Seychellois": "Sey", + "Sierra Leonean": "Sie", + "Singaporean": "Sng", + "Slovak": "Svk", + "Slovene": "Svn", + "a Solomon Islander": "SI", + "Somali": "Som", + "South African": "RSA", + "South Sudanese": "SS", + "Spanish": "Spa", + "Sri Lankan": "Sri", + "Sudanese": "Sud", + "Surinamese": "Sur", + "Swazi": "Swa", + "Swedish": "Swe", + "Swiss": "Swi", + "Syrian": "Syr", + "Taiwanese": "Tai", + "Tajik": "Taj", + "Tanzanian": "Tza", + "Thai": "Tha", + "Tibetan": "Tib", + "Togolese": "Tog", + "Tongan": "Ton", + "Trinidadian": "TT", + "Tunisian": "Tun", + "Turkish": "Tur", + "Turkmen": "Tkm", + "Tuvaluan": "Tuv", + "Ugandan": "Uga", + "Ukrainian": "Ukr", + "Uruguayan": "Uru", + "Uzbek": "Uzb", + "Vatican": "VC", + "Venezuelan": "Ven", + "Vietnamese": "Vnm", + "Vincentian": "SVG", + "Yemeni": "Yem", + "Zairian": "DRC", + "Zambian": "Zam", + "Ancient Chinese Revivalist": "Chi Rev", + "Ancient Egyptian Revivalist": "Egy Rev", + "Arabian Revivalist": "Ara Rev", + "Aztec Revivalist": "Azt Rev", + "Edo Revivalist": "Edo Rev", + "Roman Revivalist": "Rom Rev", + "": "None", + "none": "None", + "slave": "None", + "Stateless": "None" + }, + race: { + "white": "C", + "asian": "A", + "indo-aryan": "I", + "latina": "L", + "middle eastern": "ME", + "black": "B", + "pacific islander": "PI", + "malay": "M", + "amerindian": "AI", + "semitic": "S", + "southern european": "SE", + "mixed race": "MR", + }, + skin: { + "pure white": "P. Whi", + "extremely fair": "E. Fai", + "very fair": "V. Fai", + "extremely pale": "E. Pal", + "very pale": "V. Pal", + "light brown": "L. Br", + "dark brown": "D. Br", + "light olive": "L. Oli", + "dark olive": "D. Oli", + "light beige": "L. Bei", + "dark beige": "D. Bei", + "tan": "Tan", + "bronze": "Bron", + "ebony": "Ebon", + "pure black": "P. Bla", + "dark": "Dark", + "fair": "Fair", + "pale": "Pale" + }, + smartPiercing: { + setting: { + "off": "SP-", + "submissive": "SP:sub", + "lesbian": "SP:les", + "oral": "SP:oral", + "humiliation": "SP:humil", + "anal": "SP:anal", + "boobs": "SP:boobs", + "sadist": "SP:sade", + "masochist": "SP:pain", + "dom": "SP:dom", + "pregnancy": "SP:pregnancy", + "vanilla": "SP:vanilla", + "all": "SP:all", + "none": "SP:none", + "monitoring": "SP:monitoring", + "men": "SP:men", + "women": "SP:women", + "anti-men": "SP:anti-men", + "anti-women": "SP:anti-women", + } + }, + skills: { + entertainment: { + 10: null, + 29: {desc: "E", style: "aquamarine"}, + 59: {desc: "E+", style: "aquamarine"}, + 99: {desc: "E++", style: "aquamarine"}, + 999: {desc: "E+++", style: "aquamarine"} + }, + sex: { // indices: [sexSkills, hasVagina(0,1)] + 30: {desc: "S-", style: "aquamarine"}, + 90: {desc: "S+", style: "aquamarine"}, + 120: {desc: "S++", style: "aquamarine"}, + 180: { + 0: {desc: "Sh++", style: "aquamarine"}, + 1: {desc: "S++", style: "aquamarine"} + }, + 9999: {desc: "S++", style: "aquamarine"} + }, + whoring: { + 10: null, + 29: {desc: "W", style: "aquamarine"}, + 59: {desc: "W+", style: "aquamarine"}, + 99: {desc: "W++", style: "aquamarine"}, + 999: {desc: "W+++", style: "aquamarine"} + }, + mss: {desc: "MSS", style: "aquamarine"}, + fighter: {desc: "C", style: "aquamarine"}, + }, + sexDrive: { + XX: { + 5: {desc: "XY---", style: "red"}, + 15: {desc: "XY--", style: "red"}, + 35: {desc: "XY-", style: "red"}, + 65: {desc: "XY"}, + 85: {desc: "XY+", style: "green"}, + 95: {desc: "XY++", style: "green"}, + 999: {desc: "XY+++", style: "green"} + }, + XY: { + 5: {desc: "XX---", style: "red"}, + 15: {desc: "XX--", style: "red"}, + 35: {desc: "XX-", style: "red"}, + 65: {desc: "XX"}, + 85: {desc: "XX+", style: "green"}, + 95: {desc: "XX++", style: "green"}, + 999: {desc: "XX+++", style: "green"} + }, + energy: { + 20: {desc: "SD--", style: "red"}, + 40: {desc: "SD-", style: "red"}, + 60: {desc: "SD", style: "yellow"}, + 80: {desc: "SD+", style: "green"}, + 95: {desc: "SD++", style: "green"}, + 999: {desc: "Nympho!", style: "green"} + }, + synergy: { + omni: {desc: "Omni!", style: "green"}, + nymphomni: {desc: "Omni+Nympho!!", style: "green"} + } + } + } +}; diff --git a/saveTools/README.md b/saveTools/README.md new file mode 100644 index 0000000000000000000000000000000000000000..79d1949239601ec70cb217d59bc89e28a63f5706 --- /dev/null +++ b/saveTools/README.md @@ -0,0 +1,69 @@ +# Tool(s) for hacking saved games + +The script `fc_edit_save.py` can be used to read or write FC games saved with the "Save to Disk..." option. +It can convert the compressed files (usually named `free-cities-YYYYMMDD-HHMMSS.save`) to or from JSON format, +view or modify some variables, search for slaves matching some criteria to view or modify them, etc. + +## Dependencies + +The script `fc_edit_save.py` requires Python version 3.6 or later. + +In addition, it uses a small module called `lzstring`, available via [PyPI](https://pypi.org/project/lzstring/). +If you do not have it yet, you can install it with `pip3 install lzstring` or simply `pip install lzstring` if +your default version of Python is already Python 3.x. You can also install the module manually from PyPI +without using `pip`, but then you are on your own for copying the files in the right locations. + +## Usage + +To view the help message summarizing the command-line arguments, try: +``` +./fc_edit_save.py --help +``` + +To view a longer help message including details for each command and a list of examples, try: +``` +./fc_edit_save.py --long-help +``` + +The basic usage consists of: + + * reading an input file (compressed `*.save` file with the option `-i`, or uncompressed JSON file with the + option `-I`), + + * optionally modifying it with various commands, + + * then saving the result in some output file (compressed `*.save` file with the option `-o`, or uncompressed + JSON file with the option `-O`). + +If the options `-i`/`-I` or `-o`/`-O` are not used, then the standard input and standard output will be used, which +allows this script to be used easily in a pipe including other commands. If the input is a file, then the option +`-A` can also be used instead of `-o`/`-O` to set the name of the output file automatically. Try `--help` and +`--long-help` for more details. + +## Examples + +The following examples assume that you already have a saved game in the file `free-cities-20200401-123456.save`. + +To convert a compressed FC game file to a human-readable JSON file: +``` +./fc_edit_save.py -i free-cities-20200104-123456.save -p -O free-cities-20200104-123456.json +``` + +To convert a JSON file to a compressed FC game file: +``` +./fc_edit_save.py -I free-cities-20200104-123456.json -o free-cities-20200104-123456.save +``` + +To display the names and current assignments of all smart slaves (with more than 50 in intelligence): +``` +./fc_edit_save.py -i free-cities-20200104-123456.save -p --get-slaves 'intelligence>50' 'name,assignment' +``` + +To ensure that all your slaves have at least DD-cup boobs and change the hair of Miss Lily at random: +``` +./fc_edit_save.py -i free-cities-20200104-123456.save -A --set-slaves all 'boobs=atleast:900' --set-slaves 'Miss Lily' randomhair -v +``` +Note that the option `-A` in this example will save the results in the file `free-cities-20200104-123456-cheat.save`. +The option `-v` will show the changes that are applied to all slaves. + +For more examples, try `--long-help`. diff --git a/saveTools/fc_edit_save.py b/saveTools/fc_edit_save.py new file mode 100755 index 0000000000000000000000000000000000000000..2fb96f25d87c0e490b3f0971d58347fa48733925 --- /dev/null +++ b/saveTools/fc_edit_save.py @@ -0,0 +1,2041 @@ +#!/usr/bin/python3 +""" +View or modify Free Cities save files in native format or JSON format. + +Run this program with --help to see a summary of its options. +Try also --long-help to see more details and some examples. + +This software is released into the public domain (CC0). +""" + +# Written in 2020 by UnwrappedGodiva <UnwrappedGodiva+FC@gmail.com> +# +# To the extent possible under law, the author(s) have dedicated all copyright +# and related and neighboring rights to this software to the public domain +# worldwide. This software is distributed without any warranty. You should +# have received a copy of the CC0 Public Domain Dedication along with this +# software. If not, see <http://creativecommons.org/publicdomain/zero/1.0/>. +# +# In other words, feel free to do whatever you want with this code: modify it, +# borrow any part of it in your own code, rewrite it in a different language, +# remove this note and put your name on it, sell it for indecent amounts of +# money, or redistribute it under any software license. But there is no +# warranty. If it creates a giant black hole that destroys the known universe, +# do not blame me for it. + +# Sanity checks that should be performed on this code on a regular basis: +# pylint3 ./fc_edit_save.py +# yapf3 --style=Google -d ./fc_edit_save.py +# See also: http://google.github.io/styleguide/pyguide.html +# pylint: disable=too-many-lines,too-many-statements,too-many-branches + +__version__ = "0.3.0" + +import argparse +import copy +import fnmatch +import inspect +import random +import json +import operator +import os +import re +import shutil +import sys +import textwrap +# If you do not have the lzstring module yet, you can install it with: +# pip3 install lzstring (format used by Twine/SugarCube save files) +import lzstring + + +def version_check(): + """If this code compiles successfully, then the version is good enough.""" + version = ".".join(map(str, sys.version_info)) + return f"Format strings require python 3.6 or later. You have {version}." + + +# The following fields exist only in the PC and can be ignored in slaves. +# This list was generated from a diff between the PC and a slave around version +# 1065 and will have to be adjusted as new features are added to the game. +IGNORE_IN_SLAVES = [ + "ballsImplant", "counter.birthArcOwner", "counter.birthCitizen", + "counter.birthClient", "counter.birthDegenerate", "counter.birthElite", + "counter.birthFutaSis", "counter.birthLab", "counter.birthMaster", + "counter.birthOther", "counter.birthSelf", "counter.storedCum", + "criticalDamage", "degeneracy", "fertDrugs", "forcedFertDrugs", "newVag", + "origEye", "physicalImpairment", "pregMood", "refreshment", + "refreshmentType", "relationships", "reservedChildren", + "reservedChildrenNursery", "rumor", "sexualEnergy", "skill.cumTap", + "skill.engineering", "skill.hacking", "skill.medicine", "skill.slaving", + "skill.trading", "skill.warfare", "staminaPills", "title" +] + +# The following fields exist only in slaves and can be ignored in the PC. +# This list was generated from a diff between the PC and a slave, with a few +# additions for things that might break if they are modified. Although allowed, +# it is probably not a good idea to change PC.career and some other fields. +IGNORE_IN_PC = [ + "HGExclude", "NCSyouthening", "albinismOverride", "assignment", "attrKnown", + "canRecruit", "choosesOwnAssignment", "choosesOwnChastity", + "choosesOwnClothes", "clitSetting", "counter.PCChildrenFathered", + "counter.PCKnockedUp", "counter.births", "counter.pitKills", + "counter.publicUse", "currentRules", "custom.desc", "custom.hairVector", + "custom.image", "custom.label", "custom.title", "custom.titleLisp", "death", + "devotion", "dietCum", "dietMilk", "effectiveWhoreClass", "fetishKnown", + "fuckdoll", "haircuts", "indenture", "indentureRestrictions", + "lastWeeksCashIncome", "lastWeeksRepExpenses", "lastWeeksRepIncome", + "lifetimeCashExpenses", "lifetimeCashIncome", "lifetimeRepExpenses", + "lifetimeRepIncome", "newGamePlus", "oldDevotion", "oldTrust", "onDiet", + "origin", "override_Arm_H_Color", "override_Brow_H_Color", + "override_Eye_Color", "override_H_Color", "override_Pubic_H_Color", + "override_Race", "override_Skin", "porn", "pregControl", "readyProsthetics", + "recruiter", "relation", "relationTarget", "relationship", + "relationshipTarget", "rivalry", "rivalryTarget", "rudeTitle", "sentence", + "sexAmount", "sexQuality", "skill.DJ", "skill.anal", "skill.attendant", + "skill.bodyguard", "skill.combat", "skill.entertainer", + "skill.entertainment", "skill.farmer", "skill.headGirl", "skill.madam", + "skill.matron", "skill.milkmaid", "skill.nurse", "skill.oral", + "skill.recruiter", "skill.servant", "skill.stewardess", "skill.teacher", + "skill.vaginal", "skill.wardeness", "skill.whore", "skill.whoring", + "slaveCost", "subTarget", "toyHole", "training", "trust", + "useRulesAssistant", "weekAcquired", "whoreClass" +] + +# Aliases for field names used in --get-slaves. +FIELD_ALIASES = { + "name": ["birthName", "birthSurname", "slaveName", "slaveSurname"], + "names": ["birthName", "birthSurname", "slaveName", "slaveSurname"], + "parent": ["mother", "father"], + "parents": ["mother", "father"] +} + +# Each action can be a list of assignments or other actions that will be +# expanded recursively. The documentation of modify_slave() and parse_value() +# explains how the assignments work and how special values are parsed: +# "A~B" for a random range, "A|B" for a choice, "atleast:", "atmost:", etc. +# +# Improvements to consider: +# - Change the names of the actions to match the values or ranges described in +# the slave variables documentation or the conditions in src/js/assayJS.js. +# - add/remove piercings, tattoos, brand, ... +# - add or modify rules for the RA (Here be dragons) +SLAVE_ACTIONS = { + # special roles for the slaves + "Attendant": [ + "blind", "fetish=submissive", "mature", "genius", "femaleorgans", + "career=a masseuse|a yoga instructor", "skill.attendant=atleast:200", + "clothes=a kimono" + ], + "Agent": [ + "mature", "nympho", "fetish=dom", "genius", "career=an arcology owner", + "clothes=nice business attire" + ], + "Bodyguard": [ + "goodshape", "muscles=96", "verytall", "skill.combat=1", + "career=a bodyguard|a boxer|a soldier", "skill.bodyguard=atleast:200", + "clothes=a military uniform|a mounty outfit|a police uniform" + ], + "Concubine": [ + "prestige=atleast:3", "experienced", "master", "beautiful", "devoted", + "genius", "clothes=a ball gown|a succubus outfit|clubslut netting" + ], + "DJ": [ + "wife", "blind", "master", "goodshape", "beautiful", "genius", + "career=a house DJ|a musician", "skill.DJ=atleast:200", + "clothes=a t-shirt and panties|a t-shirt and thong" + ], + "Farmer": [ + "wife", "goodshape", "master", "experienced", "behavioralQuirk=funny", + "sexualQuirk=caring", "maleorgans", "career=a farmer|a rancher", + "skill.farmer=atleast:200", "clothes=Western clothing" + ], + "HeadGirl": [ + "rules.living=luxurious", "fetish=dom", "maleorgans", "femaleorgans", + "master", "genius", "career=a captain|a director|a Queen", + "skill.headGirl=atleast:200", "clothes=a ball gown" + ], + "Head Girl": ["HeadGirl"], + "Headgirl": ["HeadGirl"], + "HG": ["HeadGirl"], + "Recruiter": [ + "devoted", "genius", "master", "healthy", "beautiful", "experienced", + "rules.living=luxurious", "career=a princess|a spy|a talent scout", + "skill.recruiter=atleast:200", "clothes=a fallen nuns habit" + ], + "Madam": [ + "wife", "master", "genius", "mature", "maleorgans", + "career=a banker|a madam|a pimp", "skill.madam=atleast:200", + "clothes=leather pants and pasties" + ], + "Matron": [ + "sexualQuirk=caring", "fetish=dom", "mature", "genius", + "career=a babysitter|a nanny|a practitioner", + "skill.matron=atleast:200", "clothes=a slutty maid outfit|an apron" + ], + "Milkmaid": [ + "goodshape", "master", "sexualQuirk=caring", "behavioralQuirk=funny", + "maleorgans", "career=a milkmaid|a shepherd", + "skill.milkmaid=atleast:200", "clothes=Western clothing" + ], + "MilkMaid": ["Milkmaid"], + "Milk Maid": ["Milkmaid"], + "Nurse": [ + "wife", "fetish=dom", "goodshape", "genius", "beautiful", + "career=a doctor|a medic|a nurse|a surgeon", "skill.nurse=atleast:200", + "clothes=a slutty nurse outfit" + ], + "Teacher": [ + "mature", "genius", "beautiful", "career=a coach|a professor|a teacher", + "skill.teacher=atleast:200", "clothes=a schoolgirl outfit" + ], + "Schoolteacher": ["Teacher"], + "Stewardess": [ + "healthy", "mature", "genius", "nympho", "fetish=dom", + "career=a housekeeper|a housesitter", "skill.stewardess=atleast:200", + "clothes=a slutty maid outfit" + ], + "Wardeness": [ + "wife", "nympho", "fetish=sadist", "maleorgans", "goodshape", "devoted", + "career=a police officer|a prison guard", "skill.wardeness=atleast:200", + "clothes=a red army uniform|a schutzstaffel uniform" + ], + "Lurcher": ["maleorgans", "devoted", "goodshape"], + # fun combos + "perfect": [ + "healthy", "genius", "devoted", "goodshape", "tall", "master", + "behaves", "young" + ], + "boobdoll": ["healthy", "goodshape", "hugeboobs"], + "slut": ["healthy", "goodshape", "tall", "bigboobs", "nympho"], + "amazon": ["healthy", "goodshape", "verytall", "muscular", "bigboobs"], + "bimbo": [ + "healthy", "beautiful", "fakeboobs", "fakebutt", "lips=80", "dumb", + "clothes=a bimbo outfit" + ], + "cow": ["hugeboobs", "lactating", "counter.milk=atleast:2000~10000"], + "slug": ["nolegs", "vagina=atleast:4", "vaginaLube=atleast:2"], + "futa": ["maleorgans", "femaleorgans", "boobs=atleast:500"], + "sissy": ["dick=1", "balls=1", "scrotum=1", "trust=-50"], + "wellspring": [ + "young", "healthy", "vagina=3", "anus=3", "ovaries=1", "dick=5", + "balls=5", "prostate=1", "lactating", "hugeboobs" + ], + "onahole": ["femaleorgans", "fetish=mindbroken", "mute", "deaf", "nolimbs"], + # health + "healthy": [ + "health.condition=atleast:100", "health.health=atleast:100", + "health.illness=0", "health.longDamage=0", "health.shortDamage=0", + "health.tired=0", "minorInjury=0", "eye.left.type=1", + "eye.left.vision=2", "eye.right.type=1", "eye.right.vision=2", + "eyewear=none", "hears=0", "earImplant=0", "earwear=none", "smells=0", + "tastes=0", "heels=0", "voice=2", "voiceImplant=0", "electrolarynx=0", + "teeth=normal", "vaginaLube=1", "preg=atleast:-1", "chem=0", "addict=0", + "burst=0", "hasarms", "haslegs" + ], + "blind": [ + "eye.left.type=1", "eye.left.vision=0", "eye.right.type=1", + "eye.right.vision=0", "eyewear=none" + ], + "mute": ["voice=0"], + "deaf": ["hears=0"], + "hasarms": ["arm.left.type=1", "arm.right.type=1"], + "haslegs": ["leg.left.type=1", "leg.right.type=1"], + "haslimbs": ["hasarms", "haslegs"], + "noarms": ["arm.left=null", "arm.right=null"], + "nolegs": ["leg.left=null", "leg.right=null"], + "nolimbs": ["noarms", "nolegs"], + # intelligence + "smart": + ["intelligence=atleast:50~100", "intelligenceImplant=30", "accent=0"], + "genius": ["intelligence=100", "intelligenceImplant=30", "accent=0"], + "dumb": ["intelligence=atmost:-50~-16", "intelligenceImplant=0"], + # trust and devotion + "devoted": ["trust=100", "oldTrust=100", "devotion=100", "oldDevotion=100"], + # body shape and beauty + "goodshape": [ + "weight=0", "muscles=31~50", "waist=atmost:-50~-10", "shoulders=0", + "shouldersImplant=0", "hips=0~3", "hipsImplant=0", "boobsImplant=0", + "boobsImplantType=none", "butt=1~4", "buttImplant=0", + "buttImplantType=none", "face=atleast:50~100", "faceImplant=0", + "faceShape=normal|cute|sensual|exotic", "lips=21~70", "lipsImplant=0", + "bellySag=0", "belly=0", "bellyImplant=0" + ], + "beautiful": [ + "waist=atmost:-95~-30", "lips=41~71", "anus=atmost:2", + "vagina=atmost:2", "face=100", "faceShape=exotic", "teeth=normal", + "minorInjury=0", "scar={}", "makeup=3", "nails=2", "health.tired=0", + "boobShape=perky", "nipples=huge", "muscles=-4", + "underArmHStyle=hairless|bald|waxed|shaved", + "pubicHStyle=hairless|bald|waxed", "prestige=atleast:3", + "porn.prestige=atleast:3" + ], + "petite": ["height=110~149", "heightImplant=0"], + "short": ["height=150~159", "heightImplant=0"], + "average": ["height=160~169", "heightImplant=0"], + "tall": ["height=170~185", "heightImplant=0"], + "verytall": ["height=186~210", "heightImplant=0"], + "giant": ["height=atleast:220", "heightImplant=0"], + "veryweak": ["muscles=-95~-31"], + "weak": ["muscles=-30~-6"], + "toned": ["muscles=6~30"], + "fit": ["muscles=31~50"], + "muscular": ["muscles=51~95"], + "maleorgans": [ + "dick=inrange:2:3", "prostate=atleast:1", "balls=atleast:4", + "scrotum=atleast:4", "pubertyXY=1" + ], + "XYorgans": ["maleorgans"], + "femaleorgans": [ + "vagina=inrange:0:2", "vaginaLube=atleast:1", "ovaries=1", + "preg=atleast:-1", "pubertyXX=1" + ], + "XXorgans": ["femaleorgans"], + # boobs + "bigboobs": [ + "boobs=atleast:3000~5100", "boobsImplant=0", "boobsImplantType=none", + "boobShape=normal|perky|wide-set|torpedo-shaped", "nipples=cute|huge" + ], + "fakeboobs": [ + "boobs=atleast:3000~5100", "boobsImplant=600~1000", + "boobsImplantType=fillable", "boobShape=perky", "nipples=huge|fuckable" + ], + "hugeboobs": [ + "boobs=atleast:6000~10000", "boobsImplant=0", "boobsImplantType=none", + "boobShape=perky|wide-set|torpedo-shaped", "nipples=huge" + ], + "lactating": [ + "lactation=1", "lactationDuration=2", + "lactationAdaptation=atleast:51~100", "rules.lactation=maintain" + ], + # butt + "fakebutt": ["butt=3", "buttImplant=2", "buttImplantType=fillable"], + # hair + "randomhair": [ + "randomhaircolor", "randomhairlength", "randomhairstyle", + "randompubichairstyle" + ], + "copyhaircolor": [ + "pubicHColor=ref:hColor", "eyebrowHColor=ref:hColor", + "underArmHColor=ref:hColor" + ], + "randomhaircolor": [ + "hColor=blonde|golden|platinum blonde|strawberry-blonde|copper" + "|ginger|red|green|blue|pink|dark brown|brown|auburn|burgundy" + "|chocolate brown|chestnut|hazel|black|grey|silver|white", + "copyhaircolor" + ], + "lighthaircolor": [ + "hColor=blonde|golden|platinum blonde|strawberry-blonde|copper" + "|ginger|red|silver|white", "copyhaircolor" + ], + "darkhaircolor": [ + "hColor=dark brown|brown|auburn|burgundy|chocolate brown|chestnut" + "|hazel|black|grey", "copyhaircolor" + ], + "funhaircolor": ["hColor=red|green|blue|pink|white", "copyhaircolor"], + "randomhairlength": ["hLength=0~150"], + "verylonghair": ["hLength=100~149"], + "longhair": ["hLength=30~99"], + "mediumhair": ["hLength=10~19"], + "shoulderhair": ["mediumhair"], + "shorthair": ["hLength=0~9"], + "randomhairstyle": [ + "hStyle=shaved|buzzcut|trimmed|afro|cornrows|bun|neat|strip|tails" + "|up|ponytail|braided|dreadlocks|permed|curled|luxurious|bald" + "|messy bun|messy" + ], + "randompubicstyle": [ + "pubicHStyle=hairless|waxed|in a strip|neat|bushy|very bushy" + "|bushy in the front and neat in the rear|bald" + ], + "randompubichairstyle": ["randompubicstyle"], + # eyes + "randomeyecolor": [ + "eye.left.iris=blue|black|brown|green|turquoise|sky-blue|hazel" + "|pale-grey|white|pink|amber|red", "eye.right.iris=ref:eye.left.iris" + ], + "lighteyecolor": [ + "eye.left.iris=green|turquoise|sky-blue|pale-grey", + "eye.right.iris=ref:eye.left.iris" + ], + "darkeyecolor": [ + "eye.left.iris=black|brown|hazel|amber", + "eye.right.iris=ref:eye.left.iris" + ], + "funeyecolor": + ["eye.left.iris=white|pink|red", "eye.right.iris=ref:eye.left.iris"], + # age + "teen": [ + "actualAge=10~19", "physicalAge=ref:actualAge", + "visualAge=ref:actualAge", "ovaryAge=ref:actualAge", "ageImplant=0" + ], + "young": [ + "actualAge=18~25", "physicalAge=ref:actualAge", + "visualAge=ref:actualAge", "ovaryAge=ref:actualAge", "ageImplant=0" + ], + "mature": [ + "actualAge=36~40", "physicalAge=ref:actualAge", + "visualAge=ref:actualAge", "ovaryAge=30", "ageImplant=0" + ], + # skills and experience + "skilled": [ + "skill.vaginal=atleast:51~100", "skill.oral=atleast:51~100", + "skill.anal=atleast:51~100", "skill.whoring=atleast:51~100", + "skill.entertainment=atleast:51~100", "skill.combat=1" + ], + "master": [ + "skill.vaginal=100", "skill.oral=100", "skill.anal=100", + "skill.whoring=100", "skill.entertainment=100", "skill.combat=1" + ], + "allroles": [ + "skill.headGirl=100~200", "skill.recruiter=100~200", + "skill.bodyguard=100~200", "skill.madam=100~200", "skill.DJ=100~200", + "skill.nurse=100~200", "skill.teacher=100~200", + "skill.attendant=100~200", "skill.matron=100~200", + "skill.stewardess=100~200", "skill.milkmaid=100~200", + "skill.farmer=100~200", "skill.wardeness=100~200", + "skill.servant=100~200", "skill.entertainer=100~200", + "skill.whore=100~200" + ], + "experienced": [ + "counter.vaginal=atleast:1000~3000", "counter.anal=atleast:1000~3000", + "counter.penetrative=atleast:1500~5000", + "counter.oral=atleast:1000~3000", "counter.mammary=atleast:1000~3000" + ], + # behavior, fetishes, flaws and quirks + "behaves": [ + "energy=atleast:50~100", "attrXX=atleast:50~100", + "attrXY=atleast:50~100", "attrKnown=1", "fetishKnown=1", + "behavioralFlaw=none", "sexualFlaw=none" + ], + "nympho": [ + "energy=100", "attrXX=100", "attrXY=100", "attrKnown=1", + "fetishKnown=1", "fetish=none|submissive|boobs|dom|pregnancy", + "fetishStrength=100", "behavioralFlaw=none", "sexualFlaw=none", + "behavioralQuirk=none|none|none|none|confident|cutting|funny|fitness" + "|sinful|advocate", + "sexualQuirk=none|none|none|none|tease|romantic|perverted|caring" + "|unflinching|size queen" + ], + # relationships + "wife": ["relationship=-3", "relationshipTarget=-1"], +} + + +class FCBaseError(Exception): + """Base class for exceptions defined in this file.""" + + +class FCNotFoundError(FCBaseError): + """Exception raised when a dotted path leads nowhere.""" + + +class FCTypeError(FCBaseError): + """Exception raised when a dotted path leads to a wrong type.""" + + +class FCParamsError(FCBaseError): + """Exception raised when a command-line parameter is incorret.""" + + +class FCBadSelectorError(FCParamsError): + """Exception raised when a selector returns no results.""" + + +class AppendConstAction(argparse.Action): + """argparse.Action similar to "append" but using a tuple (`const`, values). + """ + + def __init__(self, option_strings, dest, nargs=None, const=None, **kwargs): + if nargs is None: + raise ValueError("nargs must be supplied") + if const is None: + raise ValueError("const must be supplied") + super(AppendConstAction, self).__init__( + option_strings, dest, nargs=nargs, const=const, **kwargs) + + def __call__(self, parser, namespace, values, option_string=None): + items = getattr(namespace, self.dest, None) + if items is None: + items = [] + else: + items = items[:] + items.append((self.const, values)) + setattr(namespace, self.dest, items) + + +class CommandGroupContainer: + """Wrapper for an ArgumentParser that adds actions in a list.""" + + def __init__(self, arg_parser=None, dest=None, action=AppendConstAction): + if arg_parser is None: + raise ValueError("arg_parser must be supplied") + if not callable(getattr(arg_parser, "add_argument", None)): + raise ValueError("arg_parser does not look like an ArgumentParser") + if dest is None: + raise ValueError("dest must be supplied") + self.arg_parser = arg_parser + self.dest = dest + self.action = action + self.help = [] + + def command_from_docstring(self, func): + """Registers a function in the ArgumentParser based on its docstring.""" + if func is None: + raise ValueError("func must be supplied") + if not func.__doc__: + message = "cannot register function {} without docstring" + raise ValueError(message.format(func.__name__)) + options = [] + arguments = [] + help_lines = [] + long_help_lines = None + re_command = re.compile(r'^\s+Command:\s+(.+?)\s*$') + for line in func.__doc__.splitlines(): + if not options: + re_match = re_command.match(line) + if re_match: + for arg in re_match.group(1).split(): + if arg.startswith("-") and not arguments: + options.append(arg) + else: + arguments.append(arg) + elif long_help_lines is None: + if line and not line.isspace(): + help_lines.append(line) + else: + long_help_lines = [] + else: + long_help_lines.append(line) + if not options: + message = "cannot find 'Command:' in docstring for function '{}'" + raise ValueError(message.format(func.__name__)) + # Compare the function signature with its docstring here to catch + # errors early instead of waiting until the function is called. + expected_args = len(inspect.signature(func).parameters) - 2 + if expected_args != len(arguments): + message = ("function '{}' expects {} arguments after save_obj but" + " its docstring shows {}") + raise ValueError( + message.format(func.__name__, expected_args, len(arguments))) + # Now register this function, its help text, and number of arguments. + short_help = textwrap.dedent("\n".join(help_lines)).strip() + self.arg_parser.add_argument( + *options, + action=self.action, + const=func, + dest=self.dest, + nargs=len(arguments), + metavar=tuple(arguments), + help=short_help) + # Save the long help texts. It would be nicer to store this in the + # ArgumentParser and retrieve it later, but I could not find an + # elegant way to do that without using its private classes. The + # handling of long help texts and the method format_long_help() + # should probably be rewritten. + long_help = textwrap.dedent("\n".join(long_help_lines)).strip() + self.help.append([options, arguments, short_help, long_help]) + + def format_long_help(self): + """Returns a list of long help texts for all commands.""" + help_sections = [] + for help_list in self.help: + for opt in help_list[0]: + if opt.startswith("--"): + break + else: + opt = help_list[0][0] + help_sections.append("\n".join([ + " ".join([opt] + help_list[1]), help_list[2], "", help_list[3] + ])) + return help_sections + + +class ParagraphWrapper: + """Wrapper for TextWrapper, formatting text and lists in paragraphs. + + This class splits the text in paragraphs separated by blank lines. Each + paragraph is wrapped separately. If a paragraph starts with "* " or "- ", + it is considered as a list: the lines following the first one will be + indented by two spaces. + + If the text is already indented (e.g., in a docstring), it will first be + de-indented (using textwrap.dedent()) before the line wrapping is done. + Additional blank lines before or after the text will be stripped. + """ + + def __init__(self, width=70, indent=None): + if indent is None: + indent = "" + elif isinstance(indent, int): + indent = " " * indent + self.wrapper = textwrap.TextWrapper( + width=width, + initial_indent=indent, + subsequent_indent=indent, + fix_sentence_endings=True, + break_long_words=False, + break_on_hyphens=False) + self.wrapper_list = textwrap.TextWrapper( + width=width, + initial_indent=indent, + subsequent_indent=indent + " ", + fix_sentence_endings=True, + break_long_words=False, + break_on_hyphens=False) + + def format_paragraphs(self, text): + """Wraps `text` to the specified width, preserving paragraph breaks.""" + return "\n\n".join([ + self.wrapper_list.fill(para) if para.startswith("* ") or + para.startswith("- ") else self.wrapper.fill(para) + for para in textwrap.dedent(text).strip().split("\n\n") + ]) + + +def deepmerge(dst, src, conflict_fail=False, list_skip_none=True): + """Recursively merges the content of src into dst, modifying it as needed. + + This is function is designed to merge objects converted to and from JSON + so it handles dictionaries, lists and scalar objects but not sets nor + other classes. + + FIXME: obsolete description + If both objects are dictionaries, then their keys are merged recursively. + If both objects are lists, then the elements of src replace those of dst, + unless an element of src is None and list_skip_none is True. In all other + cases src replaces dst, except if conflict_fail is True in which case a + TypeError exception is thrown. + + After writing this code, I found a more complete (and more complex) + implementation of JSON deep merge: https://github.com/avian2/jsonmerge + but I think that the short code included here is good enough for what we + need to do with the subset of JSON used in Twine/SugarCube save files. + + Args: + dst: The object to be modified. + src: The object from which the data will be copied. + conflict_fail: If True, an exception is thrown if src and dest are not + both dictionaries or lists. + list_skip_none: If True and both src and dst are lists, then the + elements of src that are None will be skipped. If False and both + src and dst are lists, then all elements of src will replace those + of dst even if they are None. + + Returns: + The results of the merge, which may be dst (modified) or src. + + Raises: + FCTypeError: If src and dst are not both dictionaries or lists and if + conflict_fail is True. + """ + if isinstance(dst, dict) and isinstance(src, dict): + for key in src: + if key in dst: + dst[key] = deepmerge( + dst[key], + src[key], + conflict_fail=conflict_fail, + list_skip_none=list_skip_none) + else: + dst[key] = src[key] + return dst + if isinstance(dst, list) and isinstance(src, list): + for i in range(min(len(src), len(dst))): + if not list_skip_none or src[i] is not None: + dst[i] = deepmerge( + dst[i], + src[i], + conflict_fail=conflict_fail, + list_skip_none=list_skip_none) + if len(src) > len(dst): + dst.extend(src[len(dst):]) + return dst + if ((dst is None or isinstance(dst, (str, int, float, bool))) and + (src is None or isinstance(src, (str, int, float, bool)))): + return src + if conflict_fail: + raise FCTypeError(f"Cannot merge {src.__class__.__name__} into " + "{dst.__class__.__name__}") + return src + + +def deepfind(src, + regex_key=None, + regex_val=None, + path=None, + callback=None, + context=None): + """Searches recursively in `src` for a matching key name or value.""" + found = 0 + if path is None: + path = [] + if isinstance(src, str): + if regex_val is not None and regex_val.search(src): + if callback: + callback(path, src, context) + found += 1 + elif isinstance(src, dict): + for key in src: + path.append(key) + if regex_key is not None and regex_key.search(key): + if callback: + callback(path, src[key], context) + found += 1 + found += deepfind( + src[key], + regex_key=regex_key, + regex_val=regex_val, + path=path, + callback=callback, + context=context) + path.pop() + elif isinstance(src, list): + # pylint: disable=consider-using-enumerate + for idx in range(len(src)): + path.append(idx) + found += deepfind( + src[idx], + regex_key=regex_key, + regex_val=regex_val, + path=path, + callback=callback, + context=context) + path.pop() + return found + + +def dotted_to_str(path): + """Converts a list of strings and ints into a Javascript-like dotted path. + + This is the opposite of parse_dotted(). + """ + return ".".join([ + f"[{part}]" if isinstance(part, int) else part for part in path + ]).replace(".[", "[") + + +def parse_dotted(dotted_path): + """Converts a Javascript-like dotted path to a list of strings and ints + + For example, the path "foo.bar[42][0].baz" will be decoded as a list + ["foo", "bar", 42, 0, "baz"] + """ + if dotted_path == "": + return [] + re_idx = re.compile(r'^(.+)\[(\d+)\]$') + plist = [] + for part in dotted_path.split("."): + rlist = [] + re_idx_match = re_idx.match(part) + while re_idx_match: + part = re_idx_match.group(1) + rlist.append(int(re_idx_match.group(2))) + re_idx_match = re_idx.match(part) + plist.append(part) + plist.extend(reversed(rlist)) + return plist + + +def get_dotted(src, dotted_path, null_parent_ok=False): + """Returns the object found at `dotted_path` in `src`.""" + # pylint: disable=too-many-branches + plist = parse_dotted(dotted_path) + obj = src + done = "." + for part in plist: + if obj is None: + if null_parent_ok: + return None + raise FCNotFoundError(f"cannot get field '{part}' from a null " + f"object: '{done}'") + if isinstance(part, int): + if not isinstance(obj, list): + raise FCTypeError(f"cannot get index {part} from object that " + f"is not a list: '{done}'") + elif part >= len(obj): + raise FCNotFoundError(f"cannot get index {part} from list of " + f"length {len(obj)}: '{done}'") + else: + obj = obj[part] + done = done + "[" + str(part) + "]" + else: + if not isinstance(obj, dict): + raise FCTypeError(f"cannot get field '{part}' from a list: " + f"'{done}'") + elif not part in obj: + raise FCNotFoundError(f"field '{part}' not found in '{done}'") + else: + obj = obj[part] + if done == ".": + done = part + else: + done = done + "." + part + return obj + + +def create_dotted(dotted_path, value): + """Creates an object that contains `value` at `dotted_path`.""" + plist = parse_dotted(dotted_path) + obj = value + for part in reversed(plist): + if isinstance(part, int): + new_list = [] + for _ in range(part): + new_list.append(None) + new_list.append(obj) + obj = new_list + else: + new_dict = {} + new_dict[part] = obj + obj = new_dict + #print(f"create_dotted({dotted_path}, {value}): {obj}") + return obj + + +def set_dotted(src, dotted_path, value, verbosity=None, conflict_fail=True): + """Changes the value of `dotted_path` in `src` to `value`.""" + if verbosity: + old_value = get_dotted(src, dotted_path, null_parent_ok=True) + if value == old_value: + if verbosity >= 2: + print(" - {} = {}".format(dotted_path, value)) + else: + if verbosity >= 1: + print(" - {} changed from {} to {}".format( + dotted_path, old_value, value)) + return deepmerge( + src, create_dotted(dotted_path, value), conflict_fail=conflict_fail) + + +def top_path_aliases(dotted_path): + """Substitutes the aliases $V.*, $PC.*, slaves[*] in a top-level path.""" + rematch = re.match(r'^\$?V\.(\w.*)$', dotted_path) + if rematch: + dotted_path = "state.delta[0].variables." + rematch.group(1) + else: + rematch = re.match(r'^slaves?(\[\d.*)$', dotted_path) + if rematch: + dotted_path = "state.delta[0].variables.slaves" + rematch.group(1) + else: + rematch = re.match(r'^\$?PC\.(\w.*)$', dotted_path) + if rematch: + dotted_path = "state.delta[0].variables.PC." + rematch.group(1) + return dotted_path + + +def parse_value(value, context_obj, path, ref_aliases=False): + """Parses the string `value`, which may refer to `path` in `context_obj`. + + See also the documentation for command_set(), which gives more details. + """ + # pylint: disable=no-member,too-many-branches + # if value starts with "string:", return it as is without processing + if value.startswith("string:"): + return value[7:] + # pick a random sub-expression if any "|" separators are present + if "|" in value: + value = random.choice(value.split("|")) + # check for prefixes requiring a comparison with the old value + compare_old = None + if value.startswith("atleast:"): + value = value[8:] + compare_old = "max" + elif value.startswith("atmost:"): + value = value[7:] + compare_old = "min" + elif value.startswith("inrange:"): + value = value[8:] + compare_old = "range" + # special values: A~B (random range), JSON values, or path to other value + if "~" in value: + vmin, vmax = value.split("~", 1) + value = random.randint(int(vmin), int(vmax)) + elif re.match(r'^-?\d+$', value): + value = int(value) + elif re.match(r'^-?\d?\.\d+$', value): + value = float(value) + elif value == "null": + value = None + elif value == "true": + value = True + elif value == "false": + value = False + elif ((value.startswith("{") and value.endswith("}")) or + (value.startswith("[") and value.endswith("]"))): + value = json.loads(value) + elif value.startswith("ref:"): + if ref_aliases: + value = get_dotted( + context_obj, top_path_aliases(value[4:]), null_parent_ok=True) + else: + value = get_dotted(context_obj, value[4:], null_parent_ok=True) + # now that we have the value, compare it with the old value if required + if compare_old is not None: + old_value = get_dotted(context_obj, path) + if compare_old == "max": + value = max(int(value), int(old_value)) + elif compare_old == "min": + value = min(int(value), int(old_value)) + elif compare_old == "range": + vmin, vmax = value.split(":", 1) + value = min(int(vmax), max(int(vmin), int(old_value))) + else: + raise RuntimeError("invalid compare_old") + return value + + +def fnmatch_slave_name(slave, name): + """Returns true if the `slave` matches the `name` (with shell wildcards).""" + field_names = ["slaveName", "slaveSurname", "birthName", "birthSurname"] + regex = re.compile(fnmatch.translate(name)) + for field in field_names: + if field in slave and slave[field] and regex.match(slave[field]): + return True + return False + + +def select_slaves(slaves, selector, show_fields=None): + """Returns a list of slaves matching `selector`. + + The `selector` is usually a comma-separated list of conditions but it also + accepts as special cases "all" or "*" to select all slaves, or square + brackets including a number or a list of numbers representing a list of + indexes in the `slaves` array. + + The standard conditions can be just a number matching a slave ID, or a + slave name (with shell wildcards), or a comparison between a variable + given by its dotted path and a scalar value (integer or string). + + Note that `show_fields` is modified if present. Tt should be a set that + will get additional elements for each dotted path found in the `selector`. + """ + if show_fields is None: + show_fields = set() + selected = [] + # "all" or "All" or "*" => select all slaves + if selector in {"all", "All", "*"}: + return slaves + # number or list of numbers in square brackets => array indexes + rematch = re.match(r'^\[(\d+[,\d\s]*)\]$', selector) + if rematch: + for idx in re.split(r',\s*', rematch.group(1)): + selected.append(slaves[int(idx)]) + return selected + # list of comparisons (field=value, field<value, ...) or slave IDs or names + for slave in slaves: + for expr in re.split(r',\s*', selector): + # if the condition is just a number, try to match a slave ID + nummatch = re.match(r'^(\d+)$', expr) + if nummatch and slave["ID"] == int(nummatch.group(1)): + selected.append(slave) + show_fields.add("ID") + break + # try different comparison operators: <=, <, >=, >, !=, ==, = + for op_str, op_class, op_func in [ + ("<=", int, operator.le), + ("<", int, operator.lt), + (">=", int, operator.ge), + (">", int, operator.gt), + ("!=", str, operator.ne), + ("==", str, operator.eq), + ("=", str, operator.eq), + ]: + rematch = re.match(rf'^(.+?)\s*{op_str}\s*(.+)$', expr) + if rematch: + break + else: + op_func = None + if op_func: + if op_func( + op_class(get_dotted(slave, rematch.group(1))), + op_class(rematch.group(2))): + selected.append(slave) + show_fields.add(rematch.group(1)) + break + else: + continue + # else try to match a slave name (shell wildcards allowed) + if fnmatch_slave_name(slave, expr): + selected.append(slave) + show_fields.add("name") + break + # return what was found (maybe nothing) + return selected + + +def expand_action(actions_dict, action): + """Returns the expanded `action` from `actions_dict`, recursively.""" + actions_list = [] + for subaction in actions_dict[action]: + if subaction in actions_dict: + actions_list += expand_action(actions_dict, subaction) + elif "=" in subaction: + actions_list.append(subaction) + else: + raise FCParamsError("unknown action: '{}' used in " + "'{}'".format(subaction, action)) + return actions_list + + +def modify_slave(slave, actions_list, verbosity=None, ignore=None): + """Modifies a slave or PC according to a list of actions. + + Each action can be a list of assignments or other actions that will be + expanded recursively. Assignments must contain an equal sign ("=") and + will assign a new value to a field in the current slave or PC. Special + values are recognized by parse_value(). + + Args: + slave: The slave or PC to be modified. + actions_list: The list of modifications to be applied. + verbose: If true, print the list of changes. + ignore: List of fields that should be ignored if found in an assignment. + """ + if verbosity and verbosity >= 1: + if slave["slaveName"]: + print(f'Actions for slave {slave["ID"]} "{slave["slaveName"]}":' + f' {actions_list}') + else: + print(f'Actions for slave {slave["ID"]}: {actions_list}') + if ignore is None: + ignore = [] + assignments = [] + for action in actions_list: + if action in SLAVE_ACTIONS: + assignments += expand_action(SLAVE_ACTIONS, action) + elif "=" in action: + assignments.append(action) + elif action == "": + pass + else: + raise FCParamsError("unknown action: '{}'".format(action)) + for assignment in assignments: + re_eq = re.match(r'^(.+?)\s*=\s*(.+)$', assignment) + if re_eq: + path = re_eq.group(1) + if path in ignore: + continue # skip to the next assignment + value = parse_value(re_eq.group(2), slave, path) + set_dotted( + slave, path, value, verbosity=verbosity, conflict_fail=False) + else: + raise FCParamsError("invalid assignment: '{}'".format(assignment)) + + +def generate_slave_id(game_vars): + """Returns the next available slave ID.""" + # for compatibility, use the same technique as the Javascript code + all_slave_ids = [] + for slist in ["slaves", "tanks", "cribs"]: + all_slave_ids += [slave["ID"] for slave in game_vars[slist]] + while game_vars["IDNumber"] in all_slave_ids: + game_vars["IDNumber"] += 1 + # ugly way to do the same as "return V.IDNumber++;" in src/js/utilsFC.js + game_vars["IDNumber"] += 1 + return game_vars["IDNumber"] - 1 + + +def generate_missing_parent_id(game_vars): + """Returns the next available negative slave ID (starting at -10000).""" + if game_vars["missingParentID"]: + game_vars["missingParentID"] -= 1 + else: + game_vars["missingParentID"] = -10001 + return game_vars["missingParentID"] + 1 + + +def clone_slave(game_vars, orig_slave, same_parents=False): + """Clone a slave `orig_slave` and give it a new ID.""" + new_slave = copy.deepcopy(orig_slave) + new_slave["ID"] = generate_slave_id(game_vars) + for zero_key in [ + "bodyswap", "clone", "cloneID", "cumSource", "daughters", + "milkSource", "origBodyOwnerID", "preg", "pregType", "pregSource", + "pregWeek", "readyOva", "recruiter", "relation", "relationTarget", + "relationship", "relationshipTarget", "rivalry", "rivalryTarget", + "sisters", "subTarget" + ]: + if zero_key in new_slave: + new_slave[zero_key] = 0 + for counter in [ + "PCChildrenFathered", "PCKnockedUp", "births", "birthsTotal", + "laborCount", "slavesFathered", "slavesKnockedUp" + ]: + if counter in new_slave["counter"]: + new_slave["counter"][counter] = 0 + if "origBodyOwner" in new_slave: + new_slave["origBodyOwner"] = "" + if "womb" in new_slave: + new_slave["womb"] = [] + if same_parents: + # Add a sister (mother and father already copied but may need changes). + if orig_slave["mother"] == -1: + game_vars["PC"]["daughters"] += 1 + elif orig_slave["mother"] == 0 or orig_slave["mother"] == -2: + orig_slave["mother"] = generate_missing_parent_id(game_vars) + new_slave["mother"] = orig_slave["mother"] + if orig_slave["father"] == -1: + game_vars["PC"]["daughters"] += 1 + elif orig_slave["father"] == 0 or orig_slave["father"] == -2: + orig_slave["father"] = generate_missing_parent_id(game_vars) + new_slave["father"] = orig_slave["father"] + # Adjust the counters of daughters and sisters. + for slave in game_vars["slaves"]: + if (slave["ID"] == new_slave["mother"] or + slave["ID"] == new_slave["father"]): + slave["daughters"] += 1 + if (slave["mother"] == new_slave["mother"] or + slave["father"] == new_slave["father"] or + slave["mother"] == new_slave["father"] or + slave["father"] == new_slave["mother"]): + slave["sisters"] += 1 + new_slave["sisters"] += 1 + else: + # Clear the family info. + new_slave["sisters"] = 0 + new_slave["mother"] = 0 + new_slave["father"] = 0 + new_slave["slaveSurname"] = 0 + new_slave["birthSurname"] = 0 + new_slave["slaveName"] = "Clone #{}".format(new_slave["ID"]) + if new_slave["birthName"]: + # Append a number to the birth name, but check if it is not used yet. + base_name = new_slave["birthName"] + clone_num = 1 + end_num = re.match(r'^(.+) (\d+)$', base_name) + if end_num: + base_name = end_num.group(1) + clone_num = int(end_num.group(2)) + for name in [slave["birthName"] for slave in game_vars["slaves"]]: + end_num = re.match(rf'^{base_name} (\d+)$', name) + if end_num and int(end_num.group(1)) > clone_num: + clone_num = int(end_num.group(1)) + clone_num += 1 + new_slave["birthName"] = f"{base_name} {clone_num}" + new_slave["assignment"] = "rest" + if game_vars["JobIDArray"]: + if "rest" in game_vars["JobIDArray"]: + game_vars["JobIDArray"]["rest"].append(new_slave["ID"]) + else: + game_vars["JobIDArray"]["rest"] = [new_slave["ID"]] + if game_vars["slaveIndices"]: + max_val = max(game_vars["slaveIndices"].values()) + game_vars["slaveIndices"][str(new_slave["ID"])] = max_val + 1 + game_vars["slaves"].append(new_slave) + return new_slave + + +# Silence pylint here because many of the commands do not use their `options`. +# pylint: disable=unused-argument + + +def command_get(save_obj, target, options=None): + """Returns the part of `save_obj` that matches the path `target` + + Command: -g --get VAR + Get JSON object VAR (shortcuts: V.*, slaves[N].*, PC.*). + + The JSON object matching the dotted path `VAR` will be output. The + following shortcuts are availble: + + - V.* or $V.* replaces state.delta[0].variables.* + + - slaves[N].* replaces state.delta[0].variables.slaves[N].*, where N + is the index number of the slave in the slaves array, starting at 0. + + - PC.* or $PC.* replaces state.delta[0].variables.PC.* + + This command should appear last on the command line, otherwise the + selected object(s) will be replaced by the next command. + """ + path = top_path_aliases(target) + obj = get_dotted(save_obj, path) + return obj + + +def command_set(save_obj, changes, options=None): + """Applies the assignment(s) in `changes` to `save_obj`. + + Command: -s --set VAR=VAL[,VAR=VAL...] + Set JSON object VAR to value VAL. + + Multiple assignments can be separated by commas, as long as they are + passed as a single parameter on the command line (using quotes as + necessary). + + Note that each dotted path `VAR` is relative to the top level of the + game state. The same shortcuts as for --get are available (V.*, + slaves[N].*, PC.*). + + The value `VAR` can be any string or number, with special meanings + interpreted in the following order: + + - If the value starts with "string:" then the following string will be + used as is, disabling the interpretation of special characters except + for the comma "," that always separates the assignments. + + - One or more "|" characters can be used to split the value in a list + of sub-strings from which one will be picked at random. + + - If the value starts with "atleast:", "atmost:", or "inrange:", then + the following string defines respectively the minimum, maximum, or + both (two integers separated by ":") for `VAR`. The old value of + `VAR` will be compared with the limit(s) and will be changed only if + necessary. + + - If the value contains two numbers (postive or negative) separated by + "~" (tilde), then the result will be picked at random between these + two numbers (inclusive). + + - If the value looks like an integer or a floating-point number, then + the result will be converted to a number. You can force a number to + be interpreted as a string by prefixing it with "string:". + + - If the value is "null", "true" or "false", then it will be converted + to JSON null, true or false. You can force a special value to be + interpreted as a string by prefixing it with "string:". + + - If the value is surrounded by "{" and "}" or by "[" and "]", then it + will be interpreted as a JSON object or JSON list. + Note: due to a limitation of the implementation, the assignments are + split along the commas before the values are parsed, which means that + it is currently not possible to supply a JSON object or list + containing more than one element. This can be considered as a bug. A + workaround is to use --json instead of --set. + + - If the value starts with "ref:", then it is interpreted as a + reference to another variable. The shortcuts V.*, PC.* or + slave[N].* are allowed in the reference for the --set command but not + for --set-slaves. + + The special meanings are interpreted in the order given above as the + value is read, which allows some complex combinations. For example, + the value "10~20|ref:some.var|atleast:-5" means that the result will + be either an integer between 10 and 20, or a copy of the other + variable `some.var`, or will keep the current value as long as it is + greater or equal to the minimum -5. In this example, the choice + delimiter "|" is interpreted first and one of the sub-strings is + selected at random, then the value of that sub-string is parsed + according to the next rules. + """ + if options is not None and options.verbose: + verbosity = options.verbose + else: + verbosity = None + for assignment in re.split(r',\s*', changes): + try: + path, valexpr = assignment.split("=", 1) + except ValueError: + raise FCParamsError( + "argument to --set should be of the form dotted.name=value: " + + assignment) + path = top_path_aliases(path) + value = parse_value(valexpr, save_obj, path, ref_aliases=True) + save_obj = set_dotted(save_obj, path, value, verbosity=verbosity) + if changes: + save_obj = set_dotted(save_obj, "state.delta[0].variables.cheater", 1) + return save_obj + + +def command_json(save_obj, jstring, options=None): + """Merges the JSON strong `jstring` into `save_obj`. + + Command: -j --json JSON + Merge JSON string into JSON output. + + A JSON object (as complex as necessary) will be merged with the + current state of the game. As the merge is done from the top level, + the object should probably follow the structure + `{"state":{"delta":[{"variables":{...}}]}}` where "..." represents + the variable(s) to be modified. + """ + return deepmerge(save_obj, json.loads(jstring)) + + +def _print_dotted(path, value, options): + """Callback for find key/value: prints the path, not the value.""" + print(dotted_to_str(path)) + + +def _print_dotted_val(path, value, options): + """Callback for find key/value: prints the path and abbreviated value.""" + if options.width: + width = options.width + else: + width = shutil.get_terminal_size().columns + path_str = dotted_to_str(path) + val_abbr = textwrap.shorten( + json.dumps(value, sort_keys=options.sort), + width=max(3, width - len(path_str)), + placeholder="...") + print(f"{path_str} = {val_abbr}") + + +def _print_dotted_val_full(path, value, options): + """Callback for find key/value: prints the path and whole value.""" + val_str = json.dumps(value, sort_keys=options.sort, indent=options.indent) + print(f"{dotted_to_str(path)} = {val_str}") + + +def command_find_key(save_obj, key_name, options=None): + """Tries to find a key matching `key_name` in `save_obj`. + + Command: --find-key NAME + Tries to find a VAR (key) that matches NAME. + + The NAME to search for can be specified as a string with optional + shell wildcards ("*", "?") or as a regular expression surrounded by + "/.../". + + For each key that matches NAME, the path to that key will be printed. + If the verbosity level is at least one (-v), then the value will also + be printed but limited to its first 30 characters. If the verbosity + level is at least 2 (-vv), then the whole value will be printed + regardless of its length. + + If this option appears last on the command line, the total number of + matches will be printed at the end. + """ + re_match = re.match(r"^/(.+)/$", key_name) + if re_match: + regex_key = re.compile(re_match.group(1)) + else: + regex_key = re.compile("^" + fnmatch.translate(key_name) + "$") + if options is None or options.verbose <= 0: + callback = _print_dotted + elif options.verbose <= 1: + callback = _print_dotted_val + else: + callback = _print_dotted_val_full + return deepfind( + save_obj, regex_key=regex_key, callback=callback, context=options) + + +def command_find_value(save_obj, value_str, options=None): + """Tries to find a string containing `value_str` in `save_obj`. + + Command: --find-value NAME + Tries to find a string value that matches NAME. + + The NAME to search for can be specified as a string with optional + shell wildcards ("*", "?") or as a regular expression surrounded by + "/.../". + + For each key that matches NAME, the path to that key will be printed. + If the verbosity level is at least one (-v), then the value will also + be printed but limited to its first 30 characters. If the verbosity + level is at least 2 (-vv), then the whole value will be printed + regardless of its length. + + If this option appears last on the command line, the total number of + matches will be printed at the end. + """ + re_match = re.match(r"^/(.+)/$", value_str) + if re_match: + regex_val = re.compile(re_match.group(1)) + else: + regex_val = re.compile("^" + fnmatch.translate(value_str) + "$") + if options is None or options.verbose <= 0: + callback = _print_dotted + elif options.verbose <= 1: + callback = _print_dotted_val + else: + callback = _print_dotted_val_full + return deepfind( + save_obj, regex_val=regex_val, callback=callback, context=options) + + +def command_get_slaves(save_obj, selector, show_vars, options=None): + """Returns specific fields from the slaves matching `selector`. + + Command: --get-slaves SELECTOR VAR[,VAR...] + Select the slaves matching SELECTOR and output the fields + named VAR plus those used for the selection, or "*" to output + all fields. + + SELECTOR can be one of the following: + + - "*", "all", or "All" to select all slaves. + + - A condition or comma-separated list of conditions, using one of + the numerical comparison operators "<", "<=", ">", ">=", or a string + comparison "!=" or "==", or just a number matching a slave ID, or a + name matching a slave name, surname, birth name or birth surname + (shell wildcards are allowed). Any slave that matches at least one + of the conditions will be selected. For example, + "Miss A*, skill.oral<30" would select Miss Anne and any slave who has + less than 30 in the oral skill. + + - In square brackets "[...]", a number or a comma-separated list of + numbers. The numbers will be used as indexes in the slaves array, + starting at 0. As the ordering of the slaves array is difficult to + predict, it is usually better to refer to the slaves by their ID + or by their name as described in the previous paragraph instead of + using their index in square brackets. + + This command should appear last on the command line, otherwise the + selected object(s) will be replaced by the next command. + + VAR,[,VAR...] can be one of the following: + + - "" (empty), "*", "all", or "All" to select all fields for each slave + + - A dotted path to a variable such as "eye.left" or "intelligence", + or a comma-separated list of them. + + - One of the special aliases "name", "names", "parent", or "parents". + "name" or "names" will be replaced by "birthName", "birthSurname", + "slaveName", "slaveSurname". "parent" or "parents" will be replaced + by "mother" and "father". + """ + if selector is None: + raise RuntimeError("missing selector for --get-slaves command") + slaves = get_dotted(save_obj, "state.delta[0].variables.slaves") + show_fields = {"ID"} + selected = select_slaves(slaves, selector, show_fields=show_fields) + if show_vars and show_vars not in {"all", "All", "*"}: + for field in re.split(r',\s*', show_vars): + show_fields.add(field) + for field in FIELD_ALIASES: + if field in show_fields: + for new_field in FIELD_ALIASES[field]: + show_fields.add(new_field) + show_fields.remove(field) + filtered = [] + for slave in selected: + out = {} + for field in show_fields: + set_dotted(out, field, get_dotted(slave, field)) + filtered.append(out) + return filtered + return selected + + +def command_set_slaves(save_obj, selector, changes, options=None): + """Modifies the slaves matching `selector` according to `changes`. + + Command: --set-slaves SELECTOR VAR=VAL[,VAR=VAL...] + Modify all slaves matching SELECTOR and apply the changes VAR=VAL. + + If this command appears last on the command line and the output is + uncompressed JSON, then only the selected slaves will be output. + Using --set-slaves with an empty list of assignments ("") produces + the same results as --get-slaves with the same selector and an empty + list of selected fields or "*". + + If the verbosity level is at least 1, then the list of changes will + be printed for each slave. If the verbosity level is at least 2, + then the list of variables that are checked but not changed (because + their value is already acceptable) will also be printed. + """ + if selector is None: + raise RuntimeError("missing selector for set_slaves command") + if changes is None: + raise RuntimeError("missing assignments for set_slaves command") + if options is not None and options.verbose: + verbosity = options.verbose + else: + verbosity = None + slaves = get_dotted(save_obj, "state.delta[0].variables.slaves") + selected_slaves = select_slaves(slaves, selector) + assignments = re.split(r',\s*', changes) + for slave in selected_slaves: + modify_slave( + slave, assignments, verbosity=verbosity, ignore=IGNORE_IN_SLAVES) + if changes: + save_obj = set_dotted(save_obj, "state.delta[0].variables.cheater", 1) + return selected_slaves + + +def _command_copy_or_clone(save_obj, + selector, + changes, + options=None, + same_parents=False): + """Implements command_copy_slave or command_clone_slave.""" + if selector is None: + raise RuntimeError("missing selector for copy_slaves command") + if changes is None: + raise RuntimeError("missing assignments for copy_slaves command") + if options is not None and options.verbose: + verbosity = options.verbose + else: + verbosity = None + game_vars = get_dotted(save_obj, "state.delta[0].variables") + selected_slaves = select_slaves(game_vars["slaves"], selector) + if not selected_slaves: + raise FCBadSelectorError("no slaves match the selector " + "'{}'".format(selector)) + new_slave = clone_slave( + game_vars, selected_slaves[-1], same_parents=same_parents) + assignments = re.split(r',\s*', changes) + modify_slave( + new_slave, assignments, verbosity=verbosity, ignore=IGNORE_IN_SLAVES) + save_obj = set_dotted(save_obj, "state.delta[0].variables.cheater", 1) + return new_slave + + +def command_copy_slave(save_obj, selector, changes, options=None): + """Returns a copy of the last slave matching `selector`. + + Command: --copy-slave SELECTOR VAR=VAL[,VAR=VAL...] + Copy the last slave matching SELECTOR and apply the changes VAR=VAL. + + A copy of the slave matching SELECTOR (or of the last match if + the selector matches multiple slaves) is created and then modified + according to the assignments VAR=VAL. The new clone has no family. + + The new clone is named "Clone #ID" where ID is its ID number. You + can rename the new clone by including "slaveName=NNN" in the list + of assignments, where NNN is the new name of the clone. + + The commands --copy-slave and --clone-slave are similar, but + --copy-slave erases the family relationships and family name of the + clone, so it forgets about its origins. + """ + return _command_copy_or_clone( + save_obj, selector, changes, options=options, same_parents=False) + + +def command_clone_slave(save_obj, selector, changes, options=None): + """Returns a clone of the last slave matching `selector`. + + Command: --clone-slave SELECTOR VAR=VAL[,VAR=VAL...] + Clone the last slave matching SELECTOR and apply the changes VAR=VAL. + + A clone of the slave matching SELECTOR (or of the last match if + the selector matches multiple slaves) is created and then modified + according to the assignments VAR=VAL. The new clone is a twin sister + of the original slave. + + The new clone is named "Clone #ID" where ID is its ID number. You + can rename the new clone by including "slaveName=NNN" in the list + of assignments, where NNN is the new name of the clone. + + The commands --copy-slave and --clone-slave are similar, but + --clone-slave creates the clone as a sister of the original slave, + keeping the same family name. The number of daughters and sisters + of the other family members are updated accordingly. + + The clone will have the same age as the original slave so it will be + her twin sister, but you can change that by assigining a different + age to the clone or using one of the shortcut actions "teen", "young", + or "mature". + """ + return _command_copy_or_clone( + save_obj, selector, changes, options=options, same_parents=True) + + +def command_get_pc(save_obj, show_vars, options=None): + """Returns specific fields from the player character. + + Command: --get-pc VAR[,VAR...] + Output the fields of the PC named VAR, or "*" to output all fields. + + This command should appear last on the command line, otherwise the + selected object(s) will be replaced by the next command. + """ + player = get_dotted(save_obj, "state.delta[0].variables.PC") + show_fields = set() + if show_vars and show_vars not in {"all", "All", "*"}: + for field in re.split(r',\s*', show_vars): + show_fields.add(field) + for field in FIELD_ALIASES: + if field in show_fields: + for new_field in FIELD_ALIASES[field]: + show_fields.add(new_field) + show_fields.remove(field) + out = {} + for field in show_fields: + set_dotted(out, field, get_dotted(player, field)) + return out + return player + + +def command_set_pc(save_obj, changes, options=None): + """Applies some `changes` to the player character. + + Command: --set-pc VAR=VAL[,VAR=VAL...] + Modify the player character according to the changes VAR=VAL. + + This is similar to --set-slaves, but for the player character. The + same shortcut actions (see --list-actions) can be used for the PC, but + the ones modifying fields that do not exist in the PC will be silently + ignored. + + If this command appears last on the command line and the output is + uncompressed JSON, then only the PC object will be output. + """ + if changes is None: + raise RuntimeError("missing assignments for set_pc command") + if options is not None and options.verbose: + verbosity = options.verbose + else: + verbosity = None + player = get_dotted(save_obj, "state.delta[0].variables.PC") + assignments = re.split(r',\s*', changes) + modify_slave(player, assignments, verbosity=verbosity, ignore=IGNORE_IN_PC) + if changes: + save_obj = set_dotted(save_obj, "state.delta[0].variables.cheater", 1) + return player + + +def command_top_up(save_obj, options=None): + """Tops up the cach and reputation. + + Command: --top-up --topup + Maximize cash (100M) and reputation (20k). + + This is a quick way to get a ridiculously high amount of money and + unlock all features of the arcology without having to click 1000 times + on "Add 100000 money" in cheat mode. Of course you will be flagged as + a cheater if you use this command or any other command that modifies + the state of the game. + """ + return command_set( + save_obj, "V.cash=100000000, V.rep=20000", options=options) + + +# pylint: enable=unused-argument + + +def list_actions(width=None, verbosity=0): + """Prints the list of shortcuts ("actions") for the assignments.""" + maxkeylen = max([len(key) for key in SLAVE_ACTIONS]) + if width: + if width < max(40, maxkeylen + 20): + raise FCParamsError("width {} must be at least " + "{}".format(width, max(40, maxkeylen + 20))) + else: + width = shutil.get_terminal_size().columns - 2 + wrapper = textwrap.TextWrapper( + width=width, + subsequent_indent=(" " * (maxkeylen + 2)), + break_long_words=False, + break_on_hyphens=False) + print("The following actions can replace an assignment VAR=VAL:") + if verbosity < 3: + print("(You can increase the verbosity level to see more details)") + if verbosity >= 1: + if verbosity >= 2: + actions = {} + for key in SLAVE_ACTIONS: + actions[key] = expand_action(SLAVE_ACTIONS, key) + else: + actions = SLAVE_ACTIONS + for key in sorted(actions): + print( + wrapper.fill("{:<{}} {}".format(key, maxkeylen, actions[key]))) + if verbosity >= 3: + all_assignments = [] + for assignments in actions.values(): + all_assignments += assignments + print("\nThese actions include the following assignments:") + for assignment in sorted(set(all_assignments)): + print(wrapper.fill(assignment)) + else: + for key in sorted(SLAVE_ACTIONS): + print(key) + + +def read_game_file(infile, compressed=True): + """Reads and returns the optionally compressed JSON data from infile.""" + if compressed: + b64_data = infile.read() + json_string = lzstring.LZString().decompressFromBase64(b64_data) + else: + json_string = infile.read() + parsed_json = json.loads(json_string) + return parsed_json + + +def write_game_file(outfile, obj, compressed=True, indent=None, sort_keys=True): + """Writes obj as JSON string in outfile, with optional compression.""" + if indent is not None and indent > 0: + separators = (',', ': ') + else: + separators = (',', ':') + json_string = json.dumps( + obj, indent=indent, sort_keys=sort_keys, separators=separators) + if compressed: + b64_data = lzstring.LZString().compressToBase64(json_string) + outfile.write(b64_data) + else: + outfile.write(json_string) + outfile.write("\n") + + +def modify_file_name(orig_name, suffix="-cheat"): + """Modifies a file name to insert a suffix before the file extension.""" + if orig_name is None or orig_name == "-" or orig_name == "<stdin>": + raise FCParamsError("the input is not a named file. Cannot generate " + "output file name") + # If the suffix is already present before the file extension, add a number. + re_fname = re.match(rf"^(.+{re.escape(suffix)})(\d+)(\..+?)$", orig_name) + if re_fname: + return "".join([ + re_fname.group(1), + str(int(re_fname.group(2)) + 1), + re_fname.group(3) + ]) + re_fname = re.match(rf"^(.+{re.escape(suffix)})(\..+?)$", orig_name) + if re_fname: + return "".join([re_fname.group(1), "2", re_fname.group(2)]) + # If there is no suffix but there is a file extension, insert it before. + re_fname = re.match(r"^(.+)(\..+?)$", orig_name) + if re_fname: + return "".join([re_fname.group(1), suffix, re_fname.group(2)]) + # If the suffix is already present without file exension, add a number. + re_fname = re.match(rf"^(.+{re.escape(suffix)})(\d+)$", orig_name) + if re_fname: + return "".join([re_fname.group(1), str(int(re_fname.group(2)) + 1)]) + re_fname = re.match(rf"^(.+{re.escape(suffix)})$", orig_name) + if re_fname: + return "".join([re_fname.group(1), "2"]) + # Else just append the suffix. + return orig_name + suffix + + +def print_long_help(help_sections, width=None, verbosity=0): + """Displays the detailed help messages when --long-help is used.""" + + def print_title(line): + """Prints a line followed by a line of dashes of the same length.""" + print(line) + print("-" * len(line)) + + long_help_intro_text = """ + The commands can appear multiple times on the command line. They + modify the game variables loaded from the input file. + + VAR, VAL, VAR=VAL, and SELECTOR have the following meanings: + + - VAR is a dotted path to an object, such as "arm.left.type" for a + slave or "state.delta[0].variables.slaves[0]" from the top level. + + - VAL can be a string, a number, true, false, null, a set of random + choices separated by "|", a random number from a range A~B (using "~"), + a JSON object or array if it is surrounded by "{}" or "[]", a reference + to another variable prefixed with "ref:", or a string without any of + these special meanings if prefixed with "string:". See --set for + details. + + - SELECTOR selects one or more slaves by id (number or comma-separated + list of numbers), by condition ("intelligence>50" or "genes=XY"), or by + name or comma-separated list of names with wildcards (matching name, + surname, birth name, or birth surname). See --get-slaves for details. + "*" or "all" selects all slaves. + + - Assignments VAR=VAL can be repeated, separated by commas. They can + also be replaced by actions that are shortcuts to a list of assignments. + Use --list-actions to see the list of shortcuts and their expansion. + + The commands --get, --get-slaves and --get-pc are only useful if they + appear last on the command line and if the output is not compressed. + In that case, only the selected objects will be output in JSON format. + Otherwise the output will include the whole game state. + """ + long_examples = [ + """{prog} -i free-cities-123456.save -p -O free-cities-123456.json + + Loads the file "free-cities-123456.save" (-i ...) and saves its + contents as a JSON file (-O ...) with nice indentation (-p). You can + then use any JSON processing tool to analyze the contents of this new + file or modify it. Without the arguments + "-O free-cities-123456.json", this command would send the formatted + JSON to standard output where it could be directly piped to other + commands such as "grep". + """, + """{prog} -I free-cities-123456.json -o free-cities-123456.save + + Loads the JSON file "free-cities-123456.json" (-I ...) and saves it as + a compressed file "free-cities-123456.save" (-o ...). + """, + """{prog} -i fc-123456.save -p --get-slaves all name,career,assignment + + Selects all slaves from the file "fc-123456.save" and prints their + names and surnames, as well as their past career and current + assignment. + """, + """{prog} -i fc-123456.save -A --clone-slave "Miss Lily" "Nurse,slaveName=Doctor Diana,randomhair" + + Creates a twin sister of the slave called "Miss Lily" and modifies the + new clone to be an ideal nurse (which makes her your genius, beautiful + wife with a dom fetish and career experience as a nurse). Sets her + slave name to be "Doctor Diana" and randomizes her hairstyle and + color. Saves the results in a file called "fc-123456-cheat.save" + (option -A with the default suffix "-cheat"). + """, + """{prog} -i fc-123456.save -A --set-slaves "intelligence>50" nympho,bimbo + + Selects all your smart slaves (intelligence>50) and turns them into + dumb bimbos with fake boobs and fake butt who are addicted to sex. + Saves the results in a file called fc-123456-cheat.save. + """, + """{prog} -i fc-123456.save --set-slaves all healthy,boobs=atleast:900 --no-output -v + + Selects all slaves and displays what changes would be applied to make + them healthy with boobs that are at least DD-cup. A summary of the + changes is displayed (-v) but they are not saved to a file nor shown + on standard output (--no-output). With an additional verbosity level + (-vv), this command would also display a detailed list of variables + that are checked but not changed if their value is already in the + acceptable range. + """, + """{prog} -i fc-123456.save --suffix=-godmode -A --top-up --set-slaves all perfect,experienced + + Maximizes your money and reputation (--top-up), then modifies all + slaves to be healthy geniuses with goddess bodies, perfect skills and + experience. Saves the results in a new file called + "fc-123456-godmode.save" (option -A with a custom --suffix). + Playing the game would be rather pointless with that extreme cheating + but this can be useful for testing some new features. + """, + """{prog} --list-actions -v + + Lists the shortcuts (actions) for the assignments that can be used + with the commands --set-slaves, --copy-slave, --clone-slave, or + --set-pc. With verbosity level 1 (-v), this command shows the + lists of assignments or other actions associated with each action. + With verbosity level 2 (-vv), these lists would be expanded + recursively. With the default verbosity (no -v), only the names of + the actions would be listed. + """ + ] + if width: + minwidth = 30 + if width < minwidth: + raise FCParamsError(f"width {width} must be at least {minwidth}") + else: + width = shutil.get_terminal_size().columns - 2 + print_title("Specifying commands") + help_wrapper = ParagraphWrapper(width=width, indent=0) + print(help_wrapper.format_paragraphs(long_help_intro_text)) + print("\n") + print_title("List of available commands") + indent_wrapper = ParagraphWrapper(width=width, indent=8) + for help_section in help_sections: + (first_line, _, other_lines) = help_section.partition("\n") + print(help_wrapper.format_paragraphs(first_line)) + print(indent_wrapper.format_paragraphs(other_lines)) + print("") + print_title("Examples") + for example in long_examples: + (first_line, _, + other_lines) = example.replace("{prog}", sys.argv[0]).partition("\n") + print(help_wrapper.format_paragraphs(first_line)) + print(indent_wrapper.format_paragraphs(other_lines)) + print("") + + +def main(): + """If you run this program from the command line, you will end up here...""" + + parser = argparse.ArgumentParser( + description="View or modify Free Cities save files, compressed or not.", + fromfile_prefix_chars="@", + epilog="These commands modify the game variables and can appear" + " multiple times on the command line. The commands --get," + " --get-slaves and --get-pc are only useful if they appear last on the" + " command line and if the output is not compressed. See --long-help" + " for details.") + parser.add_argument( + "-H", + "--long-help", + action="store_true", + help="More detailed help and examples.") + parser.add_argument( + "-V", + "--version", + action="version", + version="%(prog)s " + __version__, + help="Show this program's version number and exit.") + infile_group = parser.add_mutually_exclusive_group() + infile_group.add_argument( + "-i", + "--infile", + "--input", + type=argparse.FileType("r"), + help="Name of the compressed input file.") + infile_group.add_argument( + "-I", + "--injson", + "--input-json", + type=argparse.FileType("r"), + default=sys.stdin, + help="Name of the JSON input file (default: stdin).") + outfile_group = parser.add_mutually_exclusive_group() + outfile_group.add_argument( + "-o", + "--outfile", + "--output", + type=argparse.FileType("w"), + help="Name of the compressed output file.") + outfile_group.add_argument( + "-O", + "--outjson", + "--output-json", + type=argparse.FileType("w"), + default=sys.stdout, + help="Name of the JSON output file (default: stdout).") + outfile_group.add_argument( + "-A", + "--output-auto", + action="store_true", + help="Generate the output file name from the input file name by adding" + " a suffix to it (see --suffix). The output will be compressed or" + " not, depending on the type of the input.") + outfile_group.add_argument( + "-n", + "--no-output", + action="store_true", + help="Do not output the JSON data. Can be used with an increased" + " verbosity level to see what changes are applied without saving them.") + parser.add_argument( + "-a", + "--output-all", + action="store_true", + help="When the output is uncompressed JSON, output the whole game state" + " even if only a part of it is selected by the last command.") + parser.add_argument( + "-v", + "--verbose", + action="count", + default=0, + help="Verbosity level when modifying data or for --list-actions. Can" + " be repeated to be more verbose (default: 0).") + parser.add_argument( + "--width", + action="store", + type=int, + help="Width of the output for --list-actions (default is term width).") + parser.add_argument( + "--suffix", + action="store", + type=str, + default="-cheat", + help="Suffix added to the file name by --output-auto (default:" + " '-cheat'). Can be set to an empty string if you want to overwrite" + " the input file. Note: if the suffix starts with a dash, write this" + " argument as '--suffix=-mysuffix' and not '--suffix -mysuffix'.") + parser.add_argument( + "--indent", + action="store", + type=int, + help="Indent the JSON output by that many spaces per level.") + parser.add_argument( + "--sort", action="store_true", help="Sort the JSON keys.") + parser.add_argument( + "-p", + "--pretty-print", + action="store_true", + help="Same as --indent 2 --sort.") + parser.add_argument( + "--list-actions", + action="store_true", + help="Print the list of known actions (shortcuts) and exit. With" + " increased verbosity levels you can see the list of actions, their" + " definitions, the fully expanded definitions, and the list of" + " assignments.") + parser.add_argument( + "--no-taint", action="store_true", help=argparse.SUPPRESS) + cmd_arg_group = parser.add_argument_group("optional, repeatable commands") + cmd_group = CommandGroupContainer(cmd_arg_group, dest="commands") + # Register all functions that declare arguments in their docstring + cmd_group.command_from_docstring(command_get) + cmd_group.command_from_docstring(command_set) + cmd_group.command_from_docstring(command_json) + cmd_group.command_from_docstring(command_find_key) + cmd_group.command_from_docstring(command_find_value) + cmd_group.command_from_docstring(command_get_slaves) + cmd_group.command_from_docstring(command_set_slaves) + cmd_group.command_from_docstring(command_copy_slave) + cmd_group.command_from_docstring(command_clone_slave) + cmd_group.command_from_docstring(command_get_pc) + cmd_group.command_from_docstring(command_set_pc) + cmd_group.command_from_docstring(command_top_up) + # And now try to make sense of all this + try: + args = parser.parse_args() + + if args.long_help: + print_long_help( + cmd_group.format_long_help(), + width=args.width, + verbosity=args.verbose) + exit(0) + if args.list_actions: + list_actions(width=args.width, verbosity=args.verbose) + exit(0) + if args.pretty_print: + args.indent = 2 + args.sort = True + if args.output_auto: + if args.infile is not None: + outname = modify_file_name(args.infile.name, suffix=args.suffix) + args.outfile = open(outname, "w") + else: + outname = modify_file_name(args.injson.name, suffix=args.suffix) + args.outjson = open(outname, "w") + except FCBaseError as error: + print(f"Error in command-line arguments: {error}.") + exit(2) + + if args.infile is not None: + parsed_json = read_game_file(args.infile, compressed=True) + else: + parsed_json = read_game_file(args.injson, compressed=False) + + # Execute all commands that were passed on the command line + if args.commands: + for func, fargs in args.commands: + try: + result_obj = func(parsed_json, *fargs, options=args) + except FCBaseError as error: + fname = func.__name__.replace("command_", "").replace("_", "-") + if isinstance(error, FCParamsError) and fargs: + print("Error in {}: {} in arguments " + "{}".format(fname, str(error), fargs)) + else: + print("Error in {}: {}".format(fname, str(error))) + exit(2) + else: + result_obj = parsed_json + + # Unless the --no-taint option has been used, add a variable that marks + # the save file as tainted. This should help when triaging bug reports: + # if a tainted save file appears in a bug report, then the user could be + # asked to try and reproduce the bug without hacking the state of the + # game. + if not args.no_taint: + set_dotted(parsed_json, "state.delta[0].variables.taintedSaveFile", + "{} v{}".format(os.path.basename(sys.argv[0]), __version__)) + + if args.no_output: + pass + elif args.outfile is not None: + write_game_file( + args.outfile, + parsed_json, + compressed=True, + indent=args.indent, + sort_keys=args.sort) + elif args.output_all: + write_game_file( + args.outjson, + parsed_json, + compressed=False, + indent=args.indent, + sort_keys=args.sort) + else: + write_game_file( + args.outjson, + result_obj, + compressed=False, + indent=args.indent, + sort_keys=args.sort) + + +if __name__ == '__main__': + main() diff --git a/src/002-config/fc-version.js b/src/002-config/fc-version.js index 9be650848c218e931834205c1b94a8380a525fb8..dda732eac5bddf28028fe2ede56ffabe69dd2a0a 100644 --- a/src/002-config/fc-version.js +++ b/src/002-config/fc-version.js @@ -1,7 +1,7 @@ App.Version = { base: "0.10.7.1", // The vanilla version the mod is based off of, this should never be changed. pmod: "3.4.0", - release: 1065, + release: 1066, }; /* Use release as save version */ diff --git a/src/data/backwardsCompatibility/backwardsCompatibility.js b/src/data/backwardsCompatibility/backwardsCompatibility.js index 930e64d4bce2e80a5c1e00c310d766475e390af3..3eb0251be825d75d3e08964f423acf58b7abff69 100644 --- a/src/data/backwardsCompatibility/backwardsCompatibility.js +++ b/src/data/backwardsCompatibility/backwardsCompatibility.js @@ -105,6 +105,7 @@ App.Update.backwardsCompatibility = function() { div.append(`Cleaning up... `); jQuery('#backwardsCompatibility').append(div); App.Update.cleanUp(div); + App.UI.SlaveSummary.settingsChanged(); } catch (error) { div = document.createElement('p'); div.className = "red"; @@ -113,7 +114,6 @@ App.Update.backwardsCompatibility = function() { State.restore(); throw (error); // rethrow the exception to Sugarcube so we get a fancy stack trace } - return; }; App.Update.globalVariables = function(node) { @@ -199,6 +199,13 @@ App.Update.globalVariables = function(node) { if (V.sortSlavesBy === "income" || V.sortSlavesBy === "lastWeeksCashIncome") { V.sortSlavesBy = "weeklyIncome"; } + + if (typeof V.abbreviateClothes === "number") { + for (const key of ["clothes", "devotion", "diet", "drugs", "genitalia", "health", "hormoneBalance", + "mental", "nationality", "origins", "physicals", "race", "rules", "rulesets", "skills"]) { + V.UI.slaveSummary.abbreviation[key] = V["abbreviate" + capFirstChar(key)]; + } + } } if (typeof V.taitorWeeks !== "undefined") { diff --git a/src/data/onLoad.js b/src/data/onLoad.js deleted file mode 100644 index 349c62cad99c864af3de06bcd6c5b18927c90eb3..0000000000000000000000000000000000000000 --- a/src/data/onLoad.js +++ /dev/null @@ -1,10 +0,0 @@ -/** - * - * @param {object} save - */ -Config.saves.onLoad = function(save) { - // get the old State.variables for easier access. - const V = save.state.history[0].variables; - - App.UI.Theme.onLoad(V); -}; diff --git a/src/debugging/debugJS.js b/src/debugging/debugJS.js index d2c4dabc29c39f9086ba2f7c25c9ad1f57d7eda9..92f29adeb43750a93d37d3053a083816f3b243e3 100644 --- a/src/debugging/debugJS.js +++ b/src/debugging/debugJS.js @@ -121,6 +121,6 @@ App.Debug.dumpGameState = function() { App.Debug.slaveSummaryText = function(idx) { let span = document.createElement("span"); - span.appendChild(SlaveSummary(State.variables.slaves[idx])); + span.appendChild(App.UI.SlaveSummary.render(State.variables.slaves[idx])); return span.outerHTML; }; diff --git a/src/events/intro/introSummary.tw b/src/events/intro/introSummary.tw index f4663b62525eb58ea43159b44ba9be197235dae6..25f164f96dbd605afa5e7a460a21a47cf22a77d6 100644 --- a/src/events/intro/introSummary.tw +++ b/src/events/intro/introSummary.tw @@ -78,7 +78,7 @@ You may review your settings before clicking "Continue" to begin.<br> <<if ($economy != 100) || ($seeDicks != 25) || ($continent != "North America") || ($internationalTrade != 1) || ($internationalVariety != 1) || ($seeRace != 1) || ($seeNationality != 1) || ($seeExtreme != 0) || ($seeCircumcision != 1) || ($seeAge != 1) || ($plot != 1)>> | [[restore defaults|Intro Summary][$seeDicks = 25, $economy = 100, $continent = "North America", $internationalTrade = 1, $internationalVariety = 1, $seeRace = 1, $seeNationality = 1, $seeExtreme = 0, $seeCircumcision = 1, $seeAge = 1, $plot = 1]] <</if>> - | [[Cheat Start|init Nationalities][cashX(1000000, "cheating"), $PC.rules.living = "luxurious",repX(20000, "cheating"), $dojo += 1, $cheatMode = 1, $seeDesk = 0, $seeFCNN = 0, $sortSlavesBy = "devotion", $sortSlavesOrder = "descending", $sortSlavesMain = 0, $rulesAssistantMain = 1, $abbreviateDevotion = 1, $abbreviateRules = 1, $abbreviateClothes = 2, $abbreviateHealth = 1, $abbreviateDiet = 1, $abbreviateDrugs = 1, $abbreviateRace = 1, $abbreviateNationality = 1, $abbreviateGenitalia = 1, $abbreviatePhysicals = 1, $abbreviateSkills = 1, $abbreviateMental = 2, $PC.skill.trading = 100, $PC.skill.warfare = 100, $PC.skill.slaving = 100, $PC.skill.engineering = 100, $PC.skill.medicine = 100, $PC.skill.hacking = 100, resetEyeColor($PC)]] //Intended for debugging: may have unexpected effects// + | [[Cheat Start|init Nationalities][cashX(1000000, "cheating"), $PC.rules.living = "luxurious",repX(20000, "cheating"), $dojo += 1, $cheatMode = 1, $seeDesk = 0, $seeFCNN = 0, $sortSlavesBy = "devotion", $sortSlavesOrder = "descending", $sortSlavesMain = 0, $rulesAssistantMain = 1, $UI.slaveSummary.abbreviation = {devotion: 1, rules: 1, clothes: 2, health: 1, diet: 1, drugs: 1, race: 1, nationality: 1, genitalia: 1, physicals: 1, skills: 1, mental: 2}, $PC.skill.trading = 100, $PC.skill.warfare = 100, $PC.skill.slaving = 100, $PC.skill.engineering = 100, $PC.skill.medicine = 100, $PC.skill.hacking = 100, resetEyeColor($PC)]] //Intended for debugging: may have unexpected effects// <<set $minimumSlaveAge = variableAsNumber($minimumSlaveAge, 3, 18, 18)>> <<set $retirementAge = variableAsNumber($retirementAge, 25, 120, 45)>> @@ -1362,4 +1362,4 @@ You may review your settings before clicking "Continue" to begin.<br> <</if>> </div> -</div> \ No newline at end of file +</div> diff --git a/src/facilities/farmyard/farmyardReport.tw b/src/facilities/farmyard/farmyardReport.tw index feb3c95fb00da62e733da16154ee7468a32cbe51..e07eb815b2498f52d88e795fec7775ec6378da9c 100644 --- a/src/facilities/farmyard/farmyardReport.tw +++ b/src/facilities/farmyard/farmyardReport.tw @@ -209,13 +209,13 @@ <</switch>> /* TODO: should FS with "spare" living rules cause some minor health damage and devotion / trust loss? */ <<if ($slaves[$i].health.condition < -80)>> - <<run improveCondition($slaves[_FLs], 20)>> + <<run improveCondition($slaves[$i], 20)>> <<elseif $slaves[$i].health.condition < -40>> - <<run improveCondition($slaves[_FLs], 15)>> + <<run improveCondition($slaves[$i], 15)>> <<elseif $slaves[$i].health.condition < 0>> - <<run improveCondition($slaves[_FLs], 10)>> + <<run improveCondition($slaves[$i], 10)>> <<elseif $slaves[$i].health.condition < 90>> - <<run improveCondition($slaves[_FLs], 7)>> + <<run improveCondition($slaves[$i], 7)>> <</if>> <<if ($slaves[$i].devotion <= 20) && ($slaves[$i].trust >= -20)>> <<set $slaves[$i].devotion -= 5, $slaves[$i].trust -= 5>> diff --git a/src/facilities/nursery/nurseryWidgets.js b/src/facilities/nursery/nurseryWidgets.js index 764f268c1f28f4cbcb89ad0d97ec9624d93e4b71..ce37ddade40cd968014524a9cf417ef04844854f 100644 --- a/src/facilities/nursery/nurseryWidgets.js +++ b/src/facilities/nursery/nurseryWidgets.js @@ -14,12 +14,14 @@ App.Facilities.Nursery.InfantSummary = function(child) { r = ``; function InfantSummaryUncached(child) { - if (V.abbreviateHealth === 1) { + /** @type {App.UI.SlaveSummary.AbbreviationState} */ + const abbreviate = V.UI.slaveSummary.abbreviation; + if (abbreviate.health === 1) { // shortHealth(child); - } else if (V.abbreviateHealth === 2) { + } else if (abbreviate.health === 2) { // longHealth(child); } - if (V.abbreviateNationality + V.abbreviateGenitalia + V.abbreviatePhysicals + V.abbreviateSkills + V.abbreviateMental !== 0) { + if (abbreviate.nationality + abbreviate.genitalia + abbreviate.physicals + abbreviate.skills + abbreviate.mental !== 0) { r += `<br> `; if (V.seeImages !== 1 || V.seeSummaryImages !== 1 || V.imageChoice === 1) { r += " "; @@ -40,32 +42,32 @@ App.Facilities.Nursery.InfantSummary = function(child) { } const firstLetter = V.desc.substring(0, 1).toUpperCase(); V.desc = firstLetter + V.desc.substring(1); - r += `<strong><span class="coral">${V.desc}${V.abbreviatePhysicals === 2? '.' : ''}</span></strong> `; + r += `<strong><span class="coral">${V.desc}${abbreviate.physicals === 2? '.' : ''}</span></strong> `; if (V.seeRace) { r += `<span class="tan">`; - if (V.abbreviateRace === 1) { + if (abbreviate.race === 1) { shortRace(child); - } else if (V.abbreviateRace === 2) { + } else if (abbreviate.race === 2) { longRace(child); } r += `</span> `; } - if (V.abbreviateNationality === 1) { + if (abbreviate.nationality === 1) { shortNationality(child); - } else if (V.abbreviateNationality === 2) { + } else if (abbreviate.nationality === 2) { longNationality(child); } - if (V.abbreviatePhysicals === 1) { + if (abbreviate.physicals === 1) { shortSkin(child); } else { r += `<span class="pink">${child.skin.charAt(0).toUpperCase() + child.skin.slice(1)} skin.</span> `; } - if (V.abbreviatePhysicals === 1) { + if (abbreviate.physicals === 1) { shortAge(child); shortFace(child); shortEyes(child); r += `</span> `; - } else if (V.abbreviatePhysicals === 2) { + } else if (abbreviate.physicals === 2) { longAge(child); longFace(child); longEyes(child); @@ -75,39 +77,39 @@ App.Facilities.Nursery.InfantSummary = function(child) { if (V.seeImages !== 1 || V.seeSummaryImages !== 1 || V.imageChoice === 1) { r += " "; } - if (V.abbreviateSkills === 1) { + if (abbreviate.skills === 1) { shortIntelligence(child); shortPrestige(child); shortPornPrestige(child); - } else if (V.abbreviateSkills === 2) { + } else if (abbreviate.skills === 2) { longIntelligence(child); longPrestige(child); longPornPrestige(child); } - if (V.abbreviateMental === 1) { + if (abbreviate.mental === 1) { shortBehaviorFlaw(child); shortSexFlaw(child); - } else if (V.abbreviateMental === 2) { + } else if (abbreviate.mental === 2) { longBehaviorFlaw(child); longSexFlaw(child); } - if ((child.relationship !== 0) || (V.abbreviateClothes === 2) || (V.abbreviateRulesets === 2)) { + if ((child.relationship !== 0) || (abbreviate.clothes === 2) || (abbreviate.rulesets === 2)) { r += `<br> `; if (V.seeImages !== 1 || V.seeSummaryImages !== 1 || V.imageChoice === 1) { r += ` `; } } - if (V.abbreviateMental === 1) { + if (abbreviate.mental === 1) { r += `<span class="lightgreen">`; shortExtendedFamily(child); r += `</span> `; shortRival(child); - } else if (V.abbreviateMental === 2) { + } else if (abbreviate.mental === 2) { longExtendedFamily(child); longRival(child); } r += ` `; - if (V.abbreviateOrigins === 2 && child.origin !== 0) { + if (abbreviate.origins === 2 && child.origin !== 0) { origins(child); } return r; @@ -2065,6 +2067,8 @@ App.Facilities.Nursery.LongInfantDescription = function(child) { */ App.Facilities.Nursery.ChildSummary = function(child) { "use strict"; + /** @type {App.UI.SlaveSummary.AbbreviationState} */ + const abbreviate = V.UI.slaveSummary.abbreviation; let r = ``; @@ -2074,39 +2078,39 @@ App.Facilities.Nursery.ChildSummary = function(child) { * @returns {string} */ function ChildSummaryUncached(child) { - if (V.abbreviateDevotion === 1) { + if (abbreviate.devotion === 1) { shortDevotion(child); - } else if (V.abbreviateDevotion === 2) { + } else if (abbreviate.devotion === 2) { longDevotion(child); } if (child.fuckdoll === 0) { - if (V.abbreviateRules === 1) { + if (abbreviate.rules === 1) { shortRules(child); - } else if (V.abbreviateRules === 2) { + } else if (abbreviate.rules === 2) { longRules(child); } } - if (V.abbreviateDiet === 1) { + if (abbreviate.diet === 1) { shortWeight(child); - } else if (V.abbreviateDiet === 2) { + } else if (abbreviate.diet === 2) { longWeight(child); } - if (V.abbreviateDiet === 1) { + if (abbreviate.diet === 1) { shortDiet(child); - } else if (V.abbreviateDiet === 2) { + } else if (abbreviate.diet === 2) { longDiet(child); } - if (V.abbreviateHealth === 1) { + if (abbreviate.health === 1) { shortHealth(child); - } else if (V.abbreviateHealth === 2) { + } else if (abbreviate.health === 2) { longHealth(child); } - if (V.abbreviateDrugs === 1) { + if (abbreviate.drugs === 1) { shortDrugs(child); - } else if (V.abbreviateDrugs === 2) { + } else if (abbreviate.drugs === 2) { longDrugs(child); } - if (V.abbreviateNationality + V.abbreviateGenitalia + V.abbreviatePhysicals + V.abbreviateSkills + V.abbreviateMental !== 0) { + if (abbreviate.nationality + abbreviate.genitalia + abbreviate.physicals + abbreviate.skills + abbreviate.mental !== 0) { r += `<br> `; if (V.seeImages !== 1 || V.seeSummaryImages !== 1 || V.imageChoice === 1) { r += " "; @@ -2115,32 +2119,32 @@ App.Facilities.Nursery.ChildSummary = function(child) { V.desc = SlaveTitle(child); const firstLetter = V.desc.substring(0, 1).toUpperCase(); V.desc = firstLetter + V.desc.substring(1); - r += `<strong><span class="coral">${V.desc}${V.abbreviatePhysicals === 2? '.' : ''}</span></strong> `; + r += `<strong><span class="coral">${V.desc}${abbreviate.physicals === 2? '.' : ''}</span></strong> `; if (V.seeRace) { r += `<span class="tan">`; - if (V.abbreviateRace === 1) { + if (abbreviate.race === 1) { shortRace(child); - } else if (V.abbreviateRace === 2) { + } else if (abbreviate.race === 2) { longRace(child); } r += `</span> `; } - if (V.abbreviateNationality === 1) { + if (abbreviate.nationality === 1) { shortNationality(child); - } else if (V.abbreviateNationality === 2) { + } else if (abbreviate.nationality === 2) { longNationality(child); } - if (V.abbreviatePhysicals === 1) { + if (abbreviate.physicals === 1) { shortSkin(child); } else { r += `<span class="pink">${child.skin.charAt(0).toUpperCase() + child.skin.slice(1)} skin.</span> `; } - if (V.abbreviateGenitalia === 1) { + if (abbreviate.genitalia === 1) { shortGenitals(child); - } else if (V.abbreviateGenitalia === 2) { + } else if (abbreviate.genitalia === 2) { longGenitals(child); } - if (V.abbreviatePhysicals === 1) { + if (abbreviate.physicals === 1) { shortAge(child); shortFace(child); shortEyes(child); @@ -2160,7 +2164,7 @@ App.Facilities.Nursery.ChildSummary = function(child) { shortLactation(child); // shortMods(child); r += `</span> `; - } else if (V.abbreviatePhysicals === 2) { + } else if (abbreviate.physicals === 2) { longAge(child); longFace(child); longEyes(child); @@ -2181,7 +2185,7 @@ App.Facilities.Nursery.ChildSummary = function(child) { } r += `</span> `; } - if (V.abbreviateHormoneBalance === 1) { + if (abbreviate.hormoneBalance === 1) { if (child.hormoneBalance <= -21) { r += `<span class="deepskyblue">`; r += ` <strong>HB:M</strong> `; @@ -2193,7 +2197,7 @@ App.Facilities.Nursery.ChildSummary = function(child) { r += ` <strong>HB:F</strong> `; } r += `</span> `; - } else if (V.abbreviateHormoneBalance === 2) { + } else if (abbreviate.hormoneBalance === 2) { r += `<span class=`; if (child.hormoneBalance <= -21) { r += `"deepskyblue"`; @@ -2230,7 +2234,7 @@ App.Facilities.Nursery.ChildSummary = function(child) { if (V.seeImages !== 1 || V.seeSummaryImages !== 1 || V.imageChoice === 1) { r += " "; } - if (V.abbreviateSkills === 1) { + if (abbreviate.skills === 1) { shortIntelligence(child); shortSexSkills(child); if (child.skill.combat > 0) { @@ -2239,7 +2243,7 @@ App.Facilities.Nursery.ChildSummary = function(child) { r += "</span> "; shortPrestige(child); shortPornPrestige(child); - } else if (V.abbreviateSkills === 2) { + } else if (abbreviate.skills === 2) { longIntelligence(child); longSexSkills(child); if (child.skill.combat > 0) { @@ -2249,7 +2253,7 @@ App.Facilities.Nursery.ChildSummary = function(child) { longPrestige(child); longPornPrestige(child); } - if (V.abbreviateMental === 1) { + if (abbreviate.mental === 1) { if (child.fetish !== "mindbroken") { if (child.fetishKnown === 1) { shortFetish(child); @@ -2266,7 +2270,7 @@ App.Facilities.Nursery.ChildSummary = function(child) { shortSexFlaw(child); shortBehaviorQuirk(child); shortSexQuirk(child); - } else if (V.abbreviateMental === 2) { + } else if (abbreviate.mental === 2) { if (child.fetish !== "mindbroken") { if (child.fetishKnown === 1) { longFetish(child); @@ -2287,23 +2291,23 @@ App.Facilities.Nursery.ChildSummary = function(child) { if (child.custom.label) { r += `<strong><span class="yellow">${capFirstChar(child.custom.label)}</span></strong> `; } - if ((child.relationship !== 0) || (V.abbreviateClothes === 2) || (V.abbreviateRulesets === 2)) { + if ((child.relationship !== 0) || (abbreviate.clothes === 2) || (abbreviate.rulesets === 2)) { r += `<br> `; if (V.seeImages !== 1 || V.seeSummaryImages !== 1 || V.imageChoice === 1) { r += ` `; } } - if (V.abbreviateMental === 1) { + if (abbreviate.mental === 1) { r += `<span class="lightgreen">`; shortExtendedFamily(child); r += `</span> `; shortClone(child); shortRival(child); - } else if (V.abbreviateMental === 2) { + } else if (abbreviate.mental === 2) { longExtendedFamily(child); } if (child.fuckdoll === 0) { - if (V.abbreviateClothes === 2) { + if (abbreviate.clothes === 2) { r += ` `; if (child.choosesOwnClothes === 1) { r += "Dressing herself. "; @@ -2325,7 +2329,7 @@ App.Facilities.Nursery.ChildSummary = function(child) { } r += ` `; rulesAssistant(child); - if (V.abbreviateOrigins === 2 && child.origin !== 0) { + if (abbreviate.origins === 2 && child.origin !== 0) { origins(child); } return r; @@ -6448,7 +6452,7 @@ App.Facilities.Nursery.ChildSummary = function(child) { function rulesAssistant(child) { if (child.useRulesAssistant === 0) { r += `<span class="lightgreen">RA-Exempt</span> `; - } else if (V.abbreviateRulesets === 2 && (child.currentRules !== undefined) && (child.currentRules.length > 0)) { + } else if (abbreviate.rulesets === 2 && (child.currentRules !== undefined) && (child.currentRules.length > 0)) { r += `Rules: ${V.defaultRules.filter(x => ruleApplied(child, x)).map(x => x.name).join(", ")}`; } } diff --git a/src/interaction/slaveInteract.js b/src/interaction/slaveInteract.js index d8a3f70062e55d3aa90e6690a4d87ef547c39b22..b187865f2dd471fc3c70e8c0586a6377d9d96cb0 100644 --- a/src/interaction/slaveInteract.js +++ b/src/interaction/slaveInteract.js @@ -306,211 +306,226 @@ App.UI.SlaveInteract.drugs = function(slave) { He, His } = getPronouns(slave); - const drugOptions = []; const drugLevelOptions = []; + const lips = []; + const breasts = []; + const nipples = []; + const butt = []; + const dick = []; + const balls = []; + const fertility = []; + const hormones = []; + const psych = []; + const misc = []; if (slave.drugs !== "no drugs") { drugLevelOptions.push({text: `None`, updateSlave: {drugs: `no drugs`}}); } if (slave.indentureRestrictions < 2) { - if ( - slave.drugs === "intensive breast injections" || - slave.drugs === "intensive butt injections" || - slave.drugs === "intensive penis enhancement" || - slave.drugs === "intensive testicle enhancement" - ) { - switch (slave.drugs) { - case "intensive breast injections": - drugLevelOptions.push({text: `Moderate`, updateSlave: {drugs: `breast injections`}}); - break; - case "intensive butt injections": - drugLevelOptions.push({text: `Moderate`, updateSlave: {drugs: `butt injections`}}); - break; - case "intensive penis enhancement": - drugLevelOptions.push({text: `Moderate`, updateSlave: {drugs: `penis enhancement`}}); - break; - case "intensive testicle enhancement": - drugLevelOptions.push({text: `Moderate`, updateSlave: {drugs: `testicle enhancement`}}); - break; - } - } else if (slave.drugs === "breast injections" || slave.drugs === "butt injections" || slave.drugs === "penis enhancement" || slave.drugs === "testicle enhancement") { - drugLevelOptions.push({text: `Intensify`, updateSlave: {drugs: "intensive " + slave.drugs}}); - } + + // Psych if (slave.intelligence > -100 && slave.indentureRestrictions < 1) { - drugOptions.push({text: `Psychosuppressants`, updateSlave: {drugs: `psychosuppressants`}}); + psych.push({text: `Psychosuppressants`, updateSlave: {drugs: `psychosuppressants`}}); } else if (slave.intelligence > -100) { - drugOptions.push({text: `Psychosuppressants`, disabled: `Cannot suppress indentured slave`}); + psych.push({text: `Psychosuppressants`, disabled: `Cannot suppress indentured slave`}); } else if (slave.indentureRestrictions < 1) { - drugOptions.push({text: `Psychosuppressants`, disabled: `Too stupid to suppress`}); + psych.push({text: `Psychosuppressants`, disabled: `Too stupid to suppress`}); } else { - drugOptions.push({text: `Psychosuppressants`, disabled: `Too stupid and indentured to suppress`}); + psych.push({text: `Psychosuppressants`, disabled: `Too stupid and indentured to suppress`}); } if (V.arcologies[0].FSSlaveProfessionalismResearch === 1) { if (canImproveIntelligence(slave)) { - drugOptions.push({text: `Psychostimulants`, updateSlave: {drugs: `psychostimulants`}}); + psych.push({text: `Psychostimulants`, updateSlave: {drugs: `psychostimulants`}}); + } else { + psych.push({text: `Psychostimulants`, disabled: `Cannot improve intelligence`}); + } + } + + // Breasts + if (V.arcologies[0].FSSlimnessEnthusiastResearch === 1) { + if ((slave.boobs - slave.boobsImplant - slave.boobsMilk) > 100) { + breasts.push({text: `Reducers`, updateSlave: {drugs: `breast redistributors`}}); } else { - drugOptions.push({text: `Psychostimulants`, disabled: `Cannot improve intelligence`}); + breasts.push({text: `Reducers`, disabled: `Boobs are too small`}); } } if (slave.boobs < 48000) { - drugOptions.push({text: `Breasts`, updateSlave: {drugs: `breast injections`}}); + breasts.push({text: `Enhancement`, updateSlave: {drugs: `breast injections`}}); + breasts.push({text: `Intensive enhancement`, updateSlave: {drugs: `intensive breast injections`}}); } else { - drugOptions.push({text: `Breasts`, disabled: `Boobs are too large`}); + breasts.push({text: `Enhancement`, disabled: `Boobs are too large`}); } if (V.arcologies[0].FSAssetExpansionistResearch === 1) { if (slave.boobs < 25000) { - drugOptions.push({text: `Hyper-Breasts`, updateSlave: {drugs: `hyper breast injections`}}); + breasts.push({text: `Hyper enhancement`, updateSlave: {drugs: `hyper breast injections`}}); } else { - drugOptions.push({text: `Hyper Breasts`, disabled: `Boobs are too large`}); + breasts.push({text: `Hyper enhancement`, disabled: `Boobs are too large`}); + } + } + + // Nipples + if (V.arcologies[0].FSSlimnessEnthusiastResearch === 1) { + if (slave.nipples === "huge" || slave.nipples === "puffy" || slave.nipples === "cute") { + nipples.push({text: `Reducers`, updateSlave: {drugs: `nipple atrophiers`}}); + } else { + nipples.push({text: `Reducers`, disabled: `Nipples are ${slave.nipples}`}); } } if(V.dispensary) { if ((["inverted", "partially inverted", "cute", "tiny", "puffy"].includes(slave.nipples))) { - drugOptions.push({text: `Nipple enhancers`, updateSlave: {drugs: `nipple enhancers`}}); + nipples.push({text: `Enhancement`, updateSlave: {drugs: `nipple enhancers`}}); } else if(slave.nipples === "huge") { - drugOptions.push({text: `Nipple enhancers`, disabled: `Nipples are already huge`}); + nipples.push({text: `Enhancement`, disabled: `Nipples are already huge`}); } else { - drugOptions.push({text: `Nipple enhancers`, disabled: `Has no effect on ${slave.nipples} nipples`}); + nipples.push({text: `Enhancement`, disabled: `Has no effect on ${slave.nipples} nipples`}); } } + + // Butt if (V.arcologies[0].FSSlimnessEnthusiastResearch === 1) { - if ((slave.boobs - slave.boobsImplant - slave.boobsMilk) > 100) { - drugOptions.push({text: `Breast reducers`, updateSlave: {drugs: `breast redistributors`}}); - } else { - drugOptions.push({text: `Breast reducers`, disabled: `Boobs are too small`}); - } - if (slave.nipples === "huge" || slave.nipples === "puffy" || slave.nipples === "cute") { - drugOptions.push({text: `Nipple reducers`, updateSlave: {drugs: `nipple atrophiers`}}); + if (slave.butt - slave.buttImplant > 0) { + butt.push({text: `Reducers`, updateSlave: {drugs: `butt redistributors`}}); } else { - drugOptions.push({text: `Nipple reducers`, disabled: `Nipples are ${slave.nipples}`}); + butt.push({text: `Reducers`, disabled: `Butt is too small`}); } } if (slave.butt < 9) { - drugOptions.push({text: `Buttocks`, updateSlave: {drugs: `butt injections`}}); + butt.push({text: `Enhancement`, updateSlave: {drugs: `butt injections`}}); + butt.push({text: `Intensive enhancement`, updateSlave: {drugs: `intensive butt injections`}}); } else { - drugOptions.push({text: `Buttocks`, disabled: `Butt is too large`}); + butt.push({text: `Enhancement`, disabled: `Butt is too large`}); } if (V.arcologies[0].FSAssetExpansionistResearch === 1) { if (slave.butt < 20) { - drugOptions.push({text: `Hyper-Buttocks`, updateSlave: {drugs: `hyper butt injections`}}); + butt.push({text: `Hyper enhancement`, updateSlave: {drugs: `hyper butt injections`}}); } else { - drugOptions.push({text: `Hyper Buttocks`, disabled: `Butt is too large`}); + butt.push({text: `Hyper enhancement`, disabled: `Butt is too large`}); } } + + // Lips if (V.arcologies[0].FSSlimnessEnthusiastResearch === 1) { - if (slave.butt - slave.buttImplant > 0) { - drugOptions.push({text: `Butt reducers`, updateSlave: {drugs: `butt redistributors`}}); + if (slave.lips - slave.lipsImplant > 0) { + lips.push({text: `Reducers`, updateSlave: {drugs: `lip atrophiers`}}); } else { - drugOptions.push({text: `Butt reducers`, disabled: `Butt is too small`}); + lips.push({text: `Reducers`, disabled: `Lips are too small`}); } } if (slave.lips <= 95 || (slave.lips <= 85 && V.seeExtreme !== 1)) { - drugOptions.push({text: `Lips`, updateSlave: {drugs: `lip injections`}}); + lips.push({text: `Enhancement`, updateSlave: {drugs: `lip injections`}}); } else { - drugOptions.push({text: `Lips`, disabled: `Lips are too large`}); + lips.push({text: `Enhancement`, disabled: `Lips are too large`}); } + + // Fertility + fertility.push({text: `Fertility`, updateSlave: {drugs: `fertility drugs`}}); + if (V.seeHyperPreg === 1 && slave.indentureRestrictions < 1 && V.superFertilityDrugs === 1 && (slave.breedingMark !== 1 || V.propOutcome === 0 || V.eugenicsFullControl === 1 || V.arcologies[0].FSRestart === "unset")) { + fertility.push({text: `Fertility+`, updateSlave: {drugs: `super fertility drugs`}}); + } + + // Dick/clit if (V.arcologies[0].FSSlimnessEnthusiastResearch === 1) { - if (slave.lips - slave.lipsImplant > 0) { - drugOptions.push({text: `Lip reducers`, updateSlave: {drugs: `lip atrophiers`}}); - } else { - drugOptions.push({text: `Lip reducers`, disabled: `Lips are too small`}); + if (slave.dick > 1) { + dick.push({text: `Reducers`, updateSlave: {drugs: `penis atrophiers`}}); + } else if (slave.dick === 1) { + dick.push({text: `Reducers`, disabled: `Dick is already at minimum size`}); } - } - if (V.growthStim === 1) { - if (canImproveHeight(slave)) { - drugOptions.push({text: `Growth Stimulants`, updateSlave: {drugs: `growth stimulants`}}); - } else { - drugOptions.push({text: `Growth Stimulants`, disabled: `Cannot increase height further`}); + if (slave.clit > 0) { + dick.push({text: `Reducers`, updateSlave: {drugs: `clitoris atrophiers`}}); } } - drugOptions.push({text: `Fertility`, updateSlave: {drugs: `fertility drugs`}}); - if (V.seeHyperPreg === 1 && slave.indentureRestrictions < 1 && V.superFertilityDrugs === 1 && (slave.breedingMark !== 1 || V.propOutcome === 0 || V.eugenicsFullControl === 1 || V.arcologies[0].FSRestart === "unset")) { - drugOptions.push({text: `Fertility+`, updateSlave: {drugs: `super fertility drugs`}}); - } if (slave.dick > 0) { if (slave.dick < 10) { - drugOptions.push({text: `Penis enhancement`, updateSlave: {drugs: `penis enhancement`}}); + dick.push({text: `Enhancement`, updateSlave: {drugs: `penis enhancement`}}); + dick.push({text: `Intensive enhancement`, updateSlave: {drugs: `intensive penis enhancement`}}); } else { - drugOptions.push({text: `Penis enhancement`, disabled: `Dick is too large`}); + dick.push({text: `Enhancement`, disabled: `Dick is too large`}); } } else { if (slave.clit < 5) { - drugOptions.push({text: `Clitoris enhancement`, updateSlave: {drugs: `penis enhancement`}}); + dick.push({text: `Enhancement`, updateSlave: {drugs: `penis enhancement`}}); + dick.push({text: `Intensive enhancement`, updateSlave: {drugs: `intensive penis enhancement`}}); } else { - drugOptions.push({text: `Clitoris enhancement`, disabled: `Clit is too large`}); + dick.push({text: `Enhancement`, disabled: `Clit is too large`}); } } if (V.arcologies[0].FSAssetExpansionistResearch === 1) { if (slave.dick > 0) { if (slave.dick < 31) { - drugOptions.push({text: `Hyper penis enhancement`, updateSlave: {drugs: `hyper penis enhancement`}}); + dick.push({text: `Hyper enhancement`, updateSlave: {drugs: `hyper penis enhancement`}}); } else { - drugOptions.push({text: `Hyper penis enhancement`, disabled: `Dick is too large`}); + dick.push({text: `Hyper enhancement`, disabled: `Dick is too large`}); } } else { if (slave.clit < 5) { - drugOptions.push({text: `Hyper clitoris enhancement`, updateSlave: {drugs: `penis enhancement`}}); + dick.push({text: `Hyper enhancement`, updateSlave: {drugs: `penis enhancement`}}); } else { - drugOptions.push({text: `Hyper clitoris enhancement`, disabled: `Clit is too large`}); + dick.push({text: `Hyper enhancement`, disabled: `Clit is too large`}); } } } + if (slave.dick > 0 && slave.dick < 11 && !canAchieveErection(slave) && slave.chastityPenis !== 1) { + dick.push({text: `Erectile dysfunction circumvention`, updateSlave: {drugs: `priapism agents`}}); + } + + // Balls if (V.arcologies[0].FSSlimnessEnthusiastResearch === 1) { - if (slave.dick > 1) { - drugOptions.push({text: `Penis reducers`, updateSlave: {drugs: `penis atrophiers`}}); - } else if (slave.dick === 1) { - drugOptions.push({text: `Penis reducers`, disabled: `Dick is already at minimum size`}); + if (slave.balls > 1) { + balls.push({text: `Reducers`, updateSlave: {drugs: `testicle atrophiers`}}); + } else if (slave.balls === 1) { + balls.push({text: `Reducers`, disabled: `Balls are already at minimum size`}); } } - if (slave.dick > 0 && slave.dick < 11 && !canAchieveErection(slave) && slave.chastityPenis !== 1) { - drugOptions.push({text: `Erectile dysfunction circumvention`, updateSlave: {drugs: `priapism agents`}}); - } if (slave.balls > 0) { - drugOptions.push({text: `Testicle enhancement`, updateSlave: {drugs: `testicle enhancement`}}); + balls.push({text: `Enhancement`, updateSlave: {drugs: `testicle enhancement`}}); + balls.push({text: `Intensive enhancement`, updateSlave: {drugs: `intensive testicle enhancement`}}); if (V.arcologies[0].FSAssetExpansionistResearch === 1) { - drugOptions.push({text: `Hyper testicle enhancement`, updateSlave: {drugs: `hyper testicle enhancement`}}); + balls.push({text: `Hyper enhancement`, updateSlave: {drugs: `hyper testicle enhancement`}}); } } - if (V.arcologies[0].FSSlimnessEnthusiastResearch === 1) { - if (slave.balls > 1) { - drugOptions.push({text: `Testicle reducers`, updateSlave: {drugs: `testicle atrophiers`}}); - } else if (slave.balls === 1) { - drugOptions.push({text: `Testicle reducers`, disabled: `Balls are already at minimum size`}); + + // Hormones + if (V.precociousPuberty === 1 && V.pubertyHormones === 1 && (slave.breedingMark !== 1 || V.propOutcome === 0 || V.eugenicsFullControl === 1 || V.arcologies[0].FSRestart === "unset")) { + if ((slave.ovaries === 1 || slave.mpreg === 1) && slave.pubertyXX === 0) { + hormones.push({text: `Female injections`, updateSlave: {drugs: `female hormone injections`}}); } - if (slave.clit > 0) { - drugOptions.push({text: `Clitoris reducers`, updateSlave: {drugs: `clitoris atrophiers`}}); + if (slave.balls > 0 && slave.pubertyXY === 0) { + hormones.push({text: `Male injections`, updateSlave: {drugs: `male hormone injections`}}); } + } + hormones.push({text: `Blockers`, updateSlave: {drugs: `hormone blockers`}}); + hormones.push({text: `Enhancement`, updateSlave: {drugs: `hormone enhancers`}}); + + // Misc + if (V.arcologies[0].FSSlimnessEnthusiastResearch === 1) { if (slave.labia > 0) { - drugOptions.push({text: `Labia reducers`, updateSlave: {drugs: `labia atrophiers`}}); + misc.push({text: `Labia reducers`, updateSlave: {drugs: `labia atrophiers`}}); } } - if (V.arcologies[0].FSYouthPreferentialistResearch === 1) { - if (slave.visualAge > 18) { - drugOptions.push({text: `Anti-aging cream`, updateSlave: {drugs: `anti-aging cream`}}); + if (V.growthStim === 1) { + if (canImproveHeight(slave)) { + misc.push({text: `Growth Stimulants`, updateSlave: {drugs: `growth stimulants`}}); } else { - drugOptions.push({text: `Anti-aging cream`, disabled: `Slave already looks young enough`}); + misc.push({text: `Growth Stimulants`, disabled: `Cannot increase height further`}); } } - drugOptions.push({text: `Steroids`, updateSlave: {drugs: `steroids`}}); if (V.arcologies[0].FSSlimnessEnthusiastResearch === 1) { if (slave.weight > -95) { - drugOptions.push({text: `Weight loss pills`, updateSlave: {drugs: `appetite suppressors`}}); + misc.push({text: `Weight loss pills`, updateSlave: {drugs: `appetite suppressors`}}); } else { - drugOptions.push({text: `Weight loss pills`, disabled: `Slave is already at low weight`}); - } - } - if (V.precociousPuberty === 1 && V.pubertyHormones === 1 && (slave.breedingMark !== 1 || V.propOutcome === 0 || V.eugenicsFullControl === 1 || V.arcologies[0].FSRestart === "unset")) { - if ((slave.ovaries === 1 || slave.mpreg === 1) && slave.pubertyXX === 0) { - drugOptions.push({text: `Female hormone injections`, updateSlave: {drugs: `female hormone injections`}}); - } - if (slave.balls > 0 && slave.pubertyXY === 0) { - drugOptions.push({text: `Male hormone injections`, updateSlave: {drugs: `male hormone injections`}}); + misc.push({text: `Weight loss pills`, disabled: `Slave is already at low weight`}); } } - drugOptions.push({text: `Hormone enhancers`, updateSlave: {drugs: `hormone enhancers`}}); - drugOptions.push({text: `Hormone blockers`, updateSlave: {drugs: `hormone blockers`}}); + misc.push({text: `Steroids`, updateSlave: {drugs: `steroids`}}); if (slave.boobs > 250 && slave.boobShape !== "saggy" && V.purchasedSagBGone === 1) { - drugOptions.push({text: `Sag-B-Gone breast lifting cream`, updateSlave: {drugs: `sag-B-gone`}}); + misc.push({text: `Sag-B-Gone breast lifting cream`, updateSlave: {drugs: `sag-B-gone`}}); + } + if (V.arcologies[0].FSYouthPreferentialistResearch === 1) { + if (slave.visualAge > 18) { + misc.push({text: `Anti-aging cream`, updateSlave: {drugs: `anti-aging cream`}}); + } else { + misc.push({text: `Anti-aging cream`, disabled: `Slave already looks young enough`}); + } } } @@ -523,10 +538,87 @@ App.UI.SlaveInteract.drugs = function(slave) { title.appendChild(App.UI.SlaveInteract.generateRows(drugLevelOptions, slave)); el.append(title); - let drugLinks = document.createElement('div'); - drugLinks.appendChild(App.UI.SlaveInteract.generateRows(drugOptions, slave)); - drugLinks.className = "choices"; - el.append(drugLinks); + let links; + if (lips.length) { + links = document.createElement('div'); + links.append(`Lips: `); + links.appendChild(App.UI.SlaveInteract.generateRows(lips, slave)); + links.className = "choices"; + el.append(links); + } + + if (breasts.length) { + links = document.createElement('div'); + links.append(`Breasts: `); + links.appendChild(App.UI.SlaveInteract.generateRows(breasts, slave)); + links.className = "choices"; + el.append(links); + } + + if (nipples.length) { + links = document.createElement('div'); + links.append(`Nipples: `); + links.appendChild(App.UI.SlaveInteract.generateRows(nipples, slave)); + links.className = "choices"; + el.append(links); + } + + if (butt.length) { + links = document.createElement('div'); + links.append(`Butt: `); + links.appendChild(App.UI.SlaveInteract.generateRows(butt, slave)); + links.className = "choices"; + el.append(links); + } + + if (dick.length) { + links = document.createElement('div'); + links.append(`Dick: `); + links.appendChild(App.UI.SlaveInteract.generateRows(dick, slave)); + links.className = "choices"; + el.append(links); + } + + if (balls.length) { + links = document.createElement('div'); + links.append(`Balls: `); + links.appendChild(App.UI.SlaveInteract.generateRows(balls, slave)); + links.className = "choices"; + el.append(links); + } + + if (fertility.length) { + links = document.createElement('div'); + links.append(`Fertility: `); + links.appendChild(App.UI.SlaveInteract.generateRows(fertility, slave)); + links.className = "choices"; + el.append(links); + } + + if (hormones.length) { + links = document.createElement('div'); + links.append(`Hormones: `); + links.appendChild(App.UI.SlaveInteract.generateRows(hormones, slave)); + links.className = "choices"; + el.append(links); + } + + if (psych.length) { + links = document.createElement('div'); + links.append(`Psych: `); + links.appendChild(App.UI.SlaveInteract.generateRows(psych, slave)); + links.className = "choices"; + el.append(links); + } + + if (misc.length) { + links = document.createElement('div'); + links.append(`Misc: `); + links.appendChild(App.UI.SlaveInteract.generateRows(misc, slave)); + links.className = "choices"; + el.append(links); + } + return jQuery('#drugs').empty().append(el); }; @@ -2006,6 +2098,223 @@ App.UI.SlaveInteract.nursery = function(slave) { return jQuery('#nursery').empty().append(el); }; +App.UI.SlaveInteract.smartSettings = function(slave) { + let el = document.createElement('div'); + + const { + // eslint-disable-next-line no-unused-vars + he, + him, + his, + hers, + himself, + boy, + He, + His + } = getPronouns(slave); + const bodyPart = []; + const BDSM = []; + const gender = []; + const level = []; + + if (slave.clitPiercing === 3 || slave.vaginalAccessory === "smart bullet vibrator") { + // Level + level.push({text: `No sex`, updateSlave: {clitSetting: `none`}}); + level.push({text: `All sex`, updateSlave: {clitSetting: `all`}}); + + // Body part + bodyPart.push({text: `Vanilla`, updateSlave: {clitSetting: `vanilla`}}); + bodyPart.push({text: `Oral`, updateSlave: {clitSetting: `oral`}}); + bodyPart.push({text: `Anal`, updateSlave: {clitSetting: `anal`}}); + bodyPart.push({text: `Boobs`, updateSlave: {clitSetting: `boobs`}}); + if (V.seePreg !== 0) { + bodyPart.push({text: `Preg`, updateSlave: {clitSetting: `pregnancy`}}); + } + // BDSM + BDSM.push({text: `Sub`, updateSlave: {clitSetting: `submissive`}}); + BDSM.push({text: `Dom`, updateSlave: {clitSetting: `dom`}}); + BDSM.push({text: `Masochism`, updateSlave: {clitSetting: `masochist`}}); + BDSM.push({text: `Sadism`, updateSlave: {clitSetting: `sadist`}}); + BDSM.push({text: `Humiliation`, updateSlave: {clitSetting: `humiliation`}}); + + // Gender + gender.push({text: `Men`, updateSlave: {clitSetting: `men`}}); + gender.push({text: `Women`, updateSlave: {clitSetting: `women`}}); + gender.push({text: `Anti-men`, updateSlave: {clitSetting: `anti-men`}}); + gender.push({text: `Anti-women`, updateSlave: {clitSetting: `anti-women`}}); + } + + let title = document.createElement('div'); + title.textContent = ``; + if (slave.clitPiercing === 3) { + if (slave.dick < 1) { + title.textContent = `${His} smart clit piercing `; + if (slave.vaginalAccessory === "smart bullet vibrator") { + title.textContent += `and smart bullet vibrator are `; + } else { + title.textContent += `is `; + } + title.textContent += `set to: `; + } else{ + title.textContent = `${His} smart frenulum piercing `; + if (slave.vaginalAccessory === "smart bullet vibrator") { + title.textContent += `and smart bullet vibrator are `; + } else { + title.textContent += `is `; + } + title.textContent += `set to: `; + } + } else { + title.textContent = `${His} smart bullet vibe is set to: `; + } + let selected = document.createElement('span'); + selected.style.fontWeight = "bold"; + selected.textContent = `${slave.clitSetting}. `; + title.append(selected); + el.append(title); + + let links; + if (level.length) { + links = document.createElement('div'); + links.append(`Level: `); + links.appendChild(App.UI.SlaveInteract.generateRows(level, slave)); + links.className = "choices"; + el.append(links); + } + + if (bodyPart.length) { + links = document.createElement('div'); + links.append(`Body part: `); + links.appendChild(App.UI.SlaveInteract.generateRows(bodyPart, slave)); + links.className = "choices"; + el.append(links); + } + + if (BDSM.length) { + links = document.createElement('div'); + links.append(`BDSM: `); + links.appendChild(App.UI.SlaveInteract.generateRows(BDSM, slave)); + links.className = "choices"; + el.append(links); + } + + if (gender.length) { + links = document.createElement('div'); + links.append(`Gender: `); + links.appendChild(App.UI.SlaveInteract.generateRows(gender, slave)); + links.className = "choices"; + el.append(links); + } + + return jQuery('#smartSettings').empty().append(el); +}; + +App.UI.SlaveInteract.orgasm = function(slave) { + let el = document.createElement('div'); + + const { + // eslint-disable-next-line no-unused-vars + he, + him, + his, + hers, + himself, + boy, + He, + His + } = getPronouns(slave); + const masturbation = []; + const partner = []; + const family = []; + const slaves = []; + const master = []; + + // Masturbation + masturbation.push({text: `Allow`, updateSlave: {"rules.release.masturbation": 1}}); + masturbation.push({text: `Forbid`, updateSlave: {"rules.release.masturbation": 0}}); + + // Partner + partner.push({text: `Allow`, updateSlave: {"rules.release.partner": 1}}); + partner.push({text: `Forbid`, updateSlave: {"rules.release.partner": 0}}); + + // Family + family.push({text: `Allow`, updateSlave: {"rules.release.family": 1}}); + family.push({text: `Forbid`, updateSlave: {"rules.release.family": 0}}); + + // Slaves + slaves.push({text: `Allow`, updateSlave: {"rules.release.slaves": 1}}); + slaves.push({text: `Forbid`, updateSlave: {"rules.release.slaves": 0}}); + + // Master + master.push({text: `Grant`, updateSlave: {"rules.release.master": 1}}); + master.push({text: `Deny`, updateSlave: {"rules.release.master": 0}}); + + let title = document.createElement('div'); + title.textContent = `Non-assignment orgasm rules: `; + el.append(title); + + let links; + if (masturbation.length) { + links = document.createElement('div'); + links.append(`Masturbation: `); + status(slave.rules.release.masturbation); + links.appendChild(App.UI.SlaveInteract.generateRows(masturbation, slave)); + links.className = "choices"; + el.append(links); + } + + if (partner.length) { + links = document.createElement('div'); + links.append(`Partner: `); + status(slave.rules.release.partner); + links.appendChild(App.UI.SlaveInteract.generateRows(partner, slave)); + links.className = "choices"; + el.append(links); + } + + if (family.length) { + links = document.createElement('div'); + links.append(`Family: `); + status(slave.rules.release.family); + links.appendChild(App.UI.SlaveInteract.generateRows(family, slave)); + links.className = "choices"; + el.append(links); + } + + if (slaves.length) { + links = document.createElement('div'); + links.append(`Other slaves: `); + status(slave.rules.release.slaves); + links.appendChild(App.UI.SlaveInteract.generateRows(slaves, slave)); + links.className = "choices"; + el.append(links); + } + + if (master.length) { + links = document.createElement('div'); + links.append(`Master: `); + status(slave.rules.release.master, true); + links.appendChild(App.UI.SlaveInteract.generateRows(master, slave)); + links.className = "choices"; + el.append(links); + } + + function status(setting, master) { + let selected = document.createElement('span'); + selected.style.fontWeight = "bold"; + let text; + if (master) { + text = setting ? "granted" : "denied"; + } else { + text = setting ? "allowed" : "denied"; + } + selected.textContent = `${text}. `; + links.append(selected); + } + + return jQuery('#orgasm').empty().append(el); +}; + App.UI.SlaveInteract.custom = (function() { let el; let label; @@ -2914,7 +3223,9 @@ App.UI.SlaveInteract.generateRows = function(array, slave, category, accessCheck function click(arrayOption) { if (arrayOption.updateSlave) { - Object.assign(slave, arrayOption.updateSlave); + for (const slaveProperty in arrayOption.updateSlave) { + setValue(slave, slaveProperty, arrayOption.updateSlave[slaveProperty]); + } } if (arrayOption.update) { Object.assign(V, arrayOption.update); @@ -2923,6 +3234,20 @@ App.UI.SlaveInteract.generateRows = function(array, slave, category, accessCheck App.UI.SlaveInteract.refreshAll(slave); return; } + + function setValue(obj, path, val) { + const ref = resolve(obj, path); + ref.obj[ref.prop] = val; + } + + function resolve(obj, path, separator = '.') { + const pathArray = Array.isArray(path) ? path : path.split(separator); + if (pathArray.length === 1) { + return {obj: obj, prop: pathArray[0]}; + } + const lastObj = pathArray.slice(0, pathArray.length - 1).reduce((prev, prop) => prev[prop], obj); + return {obj: lastObj, prop: pathArray.slice(pathArray.length - 1)}; + } }; App.UI.SlaveInteract.refreshAll = function(slave) { @@ -2941,4 +3266,6 @@ App.UI.SlaveInteract.refreshAll = function(slave) { App.UI.SlaveInteract.diet(slave); App.UI.SlaveInteract.dietBase(slave); App.UI.SlaveInteract.snacks(slave); + App.UI.SlaveInteract.smartSettings(slave); + App.UI.SlaveInteract.orgasm(slave); }; diff --git a/src/js/eventHandlers.js b/src/js/eventHandlers.js index 4099ad52566ebc2354161abf905e45bb84717516..4e3a7fc73e4e128f3d3566970828a0492aa020ef 100644 --- a/src/js/eventHandlers.js +++ b/src/js/eventHandlers.js @@ -3,6 +3,18 @@ App.EventHandlers = function() { * @param {SugarCubeLib.SaveObject} save */ function onLoad(save) { + const v = save.state.history[0].variables; + if (v.releaseID > App.Version.release) { + console.error("Save game version problem. Loaded : " + v.releaseID + ", above expected:" + App.Version.release); // eslint-disable-line no-console + throw new Error("The save you're attempting to load was created with the game version newer than one you are running. Please download the latest game version."); + } + // updating settings only for the same releaseID, otherwise user will run + // backwards compatibility and we update settings from there + if (v.releaseID === App.Version.release) { + App.UI.SlaveSummary.settingsChanged(v); + } + + App.UI.Theme.onLoad(v); } /** @@ -15,6 +27,7 @@ App.EventHandlers = function() { } function optionsChanged() { + App.UI.SlaveSummary.settingsChanged(); } return { diff --git a/src/js/slaveListing.js b/src/js/slaveListing.js index cc59d9a7aa1cc816a4f8890d42fe1b199dad2b90..86eb4a8010566738bd4aaf05a2b61444c40da656 100644 --- a/src/js/slaveListing.js +++ b/src/js/slaveListing.js @@ -328,7 +328,7 @@ App.UI.SlaveList.render = function() { res.appendChild(App.UI.jobLinks.transfersFragment(index, (slave, assignment) => { App.UI.SlaveList.ScrollPosition.record(); assignJob(slave, assignment); })); } - res.appendChild(SlaveSummary(slave)); + res.appendChild(App.UI.SlaveSummary.render(slave)); if (postNote !== undefined) { const pn = postNote(slave, index); diff --git a/src/js/slaveSummaryHelpers.js b/src/js/slaveSummaryHelpers.js new file mode 100644 index 0000000000000000000000000000000000000000..4340f9ada967832ee42e4b6139743c831a4e8921 --- /dev/null +++ b/src/js/slaveSummaryHelpers.js @@ -0,0 +1,1738 @@ +// WARNING This file defines objects referenced in slaveSummaryWidgets.js. +// Either keep this file above the slaveSummaryWidgets.js in the name ordered list +// (tweego process them in this order), or rework the references. + +/* eslint-disable camelcase */ + +App.UI.SlaveSummaryImpl = function() { + const data = App.Data.SlaveSummary; + + const helpers = function() { + /** + * @param {HTMLElement} element + * @param {string|string[]} [classNames] + */ + function _addClassNames(element, classNames) { + if (classNames != undefined) { /* eslint-disable-line eqeqeq */ + if (Array.isArray(classNames)) { + element.classList.add(...classNames); + } else { + element.classList.add(classNames); + } + } + } + + /** + * @param {Node} container + * @param {string} text + * @param {string|string[]} [classNames] + * @param {boolean} [stdDecor=false] + * @param {number} [value] + */ + function makeSpan(container, text, classNames, stdDecor = false, value) { + let r = document.createElement("span"); + r.classList.add("ssi"); + _addClassNames(r, classNames); + if (value != undefined && V.summaryStats) { /* eslint-disable-line eqeqeq */ + text += `[${value}]`; + } + r.textContent = stdDecor ? `${capFirstChar(text)}.` : text; + if (container) { + container.appendChild(r); + } + return r; + } + + /** + * @param {Node} container + * @param {string} text + * @returns {Text} + */ + function addText(container, text) { + const r = document.createTextNode(text); + if (container) { + container.appendChild(r); + } + return r; + } + + /** + * @param {Node} [container] + * @param {string|string[]} [classNames] + */ + function makeBlock(container, classNames) { + let r = document.createElement("span"); + r.classList.add("ssb"); + _addClassNames(r, classNames); + if (container) { + container.appendChild(r); + } + return r; + } + + /** + * @param {Node} container + * @param {string|string[]} [classNames] + * @returns {HTMLParagraphElement} + */ + function makeParagraph(container, classNames) { + let r = document.createElement("p"); + r.classList.add("si"); + _addClassNames(r, classNames); + if (container) { + container.appendChild(r); + } + return r; + } + + /** + * @param {object} dict + * @param {*} value + * @param {*} [defaultValue] + * @returns {*|null} + */ + function getExactRating(dict, value, defaultValue = null) { + const res = dict[value]; + return res ? res : defaultValue; + } + + /** + * @param {object} ratings + * @param {number} value + * @returns {*|null} + */ + function getNumericRating(ratings, value) { + for (const key in ratings) { + if (parseInt(key) >= value) { + return ratings[key]; + } + } + return null; + } + + /** + * @param {object} ratings + * @param {number[]} values + * @returns {*|null} + */ + function getMultiNumericRating(ratings, values) { + const firstRating = getNumericRating(ratings, values[0]); + if (firstRating === null || typeof firstRating === "string" || firstRating.hasOwnProperty("desc")) { + return firstRating; + } + return getMultiNumericRating(firstRating, values.slice(1)); + } + + /** + * @typedef {object} StyledDesc + * @property {string} desc + * @property {string|string[]} [style] + */ + + /** @typedef {Object.<string, StyledDesc>} StyledRatings */ + /** + * @param {Node} container + * @param {StyledRatings} ratings + * @param {number} [value] + * @param {number} [offset] value offset in the ratings dictionary (to eliminate negative values) + * @param {boolean} [stdDecor=false] + */ + function makeRatedStyledSpan(container, ratings, value, offset = 0, stdDecor = false) { + /** @type {StyledDesc} */ + const d = getNumericRating(ratings, value + offset); + if (d) { + makeSpan(container, d.desc, d.style, stdDecor, value); + } + } + + /** + * @param {Node} container + * @param {StyledDesc} styledDesc + * @param {number} [value] + * @param {boolean} [stdDecor] + */ + function makeStyledSpan(container, styledDesc, value, stdDecor = false) { + if (styledDesc) { + makeSpan(container, styledDesc.desc, styledDesc.style, stdDecor, value); + } + } + + /** + * @param {Node} container + * @param {StyledRatings} ratings + * @param {string|number} value + */ + function makeMappedStyledSpan(container, ratings, value) { + const d = ratings[value]; + if (d) { + makeSpan(container, d.desc, d.style); + } + } + + /** + * @param {Node} container + * @param {Object.<string, string>} ratings + * @param {string|number} value + * @param {string|string[]} [classNames] + */ + function makeMappedSpan(container, ratings, value, classNames) { + const d = ratings[value]; + if (d) { + makeSpan(container, d, classNames); + } + } + + /** + * Returns first three string characters with the first one uppercased (string -> Str) + * @param {string} s + * @returns {string} + */ + function firstThreeUc(s) { + return s.charAt(0).toUpperCase() + s.charAt(1) + s.charAt(2); + } + + function syncFSData(arcology) { + arcology = arcology || V.arcologies[0]; + for (const fsp of App.Data.misc.FutureSocieties) { + const policy = arcology[fsp]; + const p = fsp.slice(2); + FSData.policy[p] = { + active: policy === "unset" ? 0 : 1, + strength: Math.trunc(policy === "unset" ? 0: policy / 10) + }; + } + + const dislikeBigButts = (FSData.policy.TransformationFetishist.strength < 2) && (FSData.policy.HedonisticDecadence.strength < 2) && (FSData.policy.AssetExpansionist.strength < 2) && (arcology.FSIntellectualDependencyLawBeauty === 0); + FSData.bigButts = dislikeBigButts ? -1 : 0; + } + /** + * @typedef {Object} FSDatum + * @property {number} active FS policy is active (0,1) + * @property {number} strength FS decoration level divided by 10 + */ + const FSData = { + /** @type {Object.<string, FSDatum>} */ + policy: {}, + bigButts: 0, + }; + + return { + addText: addText, + makeSpan: makeSpan, + makeBlock: makeBlock, + makeParagraph: makeParagraph, + getExactRating: getExactRating, + getNumericRating: getNumericRating, + getMultiNumericRating: getMultiNumericRating, + makeStyledSpan: makeStyledSpan, + makeRatedStyledSpan: makeRatedStyledSpan, + makeMappedStyledSpan: makeMappedStyledSpan, + makeMappedSpan: makeMappedSpan, + firstThreeUc: firstThreeUc, + syncFSData: syncFSData, + FSData: FSData, + }; + }(); + + const bits = function() { + const addText = helpers.addText; + const makeSpan = helpers.makeSpan; + const makeBlock = helpers.makeBlock; + const makeRatedStyledSpan = helpers.makeRatedStyledSpan; + + /** + * Returns index in the hips-ass rating table + * @param {App.Entity.SlaveState} slave + * @returns {number} 0 if butt is considered too small, 1 is too big, -1 otherwise. + */ + function hipsAssRating(slave) { + const FSData = helpers.FSData; + if (slave.hips < -1) { + if (slave.butt > 2 && FSData.bigButts <= 0) { + return 1; + } + } else if (slave.hips < 0) { + if (slave.butt > 4 && FSData.bigButts <= 0) { + return 1; + } + } else if (slave.hips > 2) { + if (slave.butt <= 8) { + return 0; + } + } else if (slave.hips > 1) { + if (slave.butt <= 3 && (FSData.policy.SlimnessEnthusiast.active === 0 || (slave.boobs >= 500))) { + return 0; + } + } else if (slave.hips > 0) { + if (slave.butt > 8) { + if (FSData.bigButts <= 0) { + return 1; + } + } else if (slave.butt <= 2 && ((FSData.policy.SlimnessEnthusiast.active === 0) || (slave.boobs >= 500))) { + return 0; + } + } else { + if (slave.butt > 6) { + if (FSData.bigButts <= 0) { + return 1; + } + } else if (slave.butt <= 1 && (FSData.policy.SlimnessEnthusiast.active === 0 || (slave.boobs >= 500))) { + return 0; + } + } + return -1; + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {App.Data.SlaveSummary.SmartPiercing} spData + * @returns {string} + */ + function smartFetishStr(slave, spData) { + if (slave.fetishKnown === 1) { + if (slave.clitSetting === "off") { + return spData.setting.off; + } else if ((slave.energy <= 95) && (slave.clitSetting === "all")) { + return spData.setting.all; + } else if ((slave.energy > 5) && (slave.clitSetting === "none")) { + return spData.setting.none; + } else if (((slave.fetish !== "none") && (slave.clitSetting === "vanilla"))) { + return spData.setting.vanilla; + } else if (slave.fetishStrength <= 95 || slave.fetish !== slave.clitSetting) { + const s = spData.setting[slave.clitSetting]; + if (s) { + return s; + } + } + if (!["anti-men", "anti-women", "men", "women"].includes(slave.clitSetting)) { + return spData.setting.monitoring; + } + } else { + return spData.setting[slave.clitSetting]; + } + return null; + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {App.Data.SlaveSummary.SmartPiercing} spData + * @returns {string} + */ + function smartAttractionStr(slave, spData) { + const sps = spData.setting; + const cs = slave.clitSetting; + if (slave.attrKnown === 1) { + switch (cs) { + case "women": + if (slave.attrXX < 95) { + return sps.women; + } else { + return sps.monitoring; + } + case "men": + if (slave.attrXY < 95) { + return sps.men; + } else { + return sps.monitoring; + } + case "anti-women": + if (slave.attrXX > 0) { + return sps["anti-women"]; + } else { + return sps.monitoring; + } + case "anti-men": + if (slave.attrXY > 0) { + return sps["anti-men"]; + } else { + return sps.monitoring; + } + } + } else { + switch (cs){ + // fall-through + case "women": + case "men": + case "anti-women": + case "anti-men": + return sps[cs]; + } + } + return null; + } + + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function short_health(slave, c) { + if (slave.health.health < -20) { + makeSpan(c, "H", ["red", "strong"], true, slave.health.health); + } else if (slave.health.health <= 20) { + makeSpan(c, "H", ["yellow", "strong"], true, slave.health.health); + } else if (slave.health.health > 20) { + makeSpan(c, "H", ["green", "strong"], true, slave.health.health); + } + if (passage() === "Clinic" && V.clinicUpgradeScanner) { + if (slave.chem > 15) { + makeSpan(c, `C${Math.ceil(slave.chem / 10)}`, ["cyan", "strong"]); + } else if (slave.chem <= 15 && slave.assignment === "get treatment in the clinic") { + makeSpan(c, `CSafe`, ["green", "strong"]); + } + } + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function short_illness(slave, c) { + if (slave.health.illness > 2) { + makeSpan(c, `Ill${slave.health.illness}`, ["red", "strong"], true, slave.health.illness); + } else if (slave.health.illness > 0) { + makeSpan(c, `Ill${slave.health.illness}`, ["yellow", "strong"], true, slave.health.illness); + } + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function short_tired(slave, c) { + helpers.makeRatedStyledSpan(c, data.short.health.tiredness, slave.health.tired, 0, true); + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function long_health(slave, c) { + helpers.makeRatedStyledSpan(c, data.long.health.health, slave.health.health, 100, true); + if (passage() === "Clinic" && V.clinicUpgradeScanner) { + if (slave.chem > 15) { + makeSpan(c, `Carcinogen buildup: ${Math.ceil(slave.chem / 10)}.`, "cyan"); + } else if (slave.chem <= 15 && slave.assignment === "get treatment in the clinic") { + makeSpan(c, `Safe chem levels.`, "green"); + } + } + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function long_illness(slave, c) { + makeRatedStyledSpan(c, data.long.health.illness, slave.health.illness, 0, true); + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function long_tired(slave, c) { + makeRatedStyledSpan(c, data.long.health.tiredness, slave.health.tired, 0, true); + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function long_age(slave, c) { + let r = makeSpan(c, "", "pink"); + if (V.showAgeDetail === 1) { + r.textContent += `Age ` + `${slave.actualAge}` + `.`; + } else { + r.textContent += helpers.getNumericRating(data.long.body.age, slave.actualAge); + } + /* + ** No NCS, then do the standard, However because of the wrinkles of Incubators, as long as visual age is greater + ** than or equal to physical age, we do the old physical body/Looks for fresh out of the can NCS slaves. + */ + if (((slave.geneMods.NCS === 0) || (slave.visualAge >= slave.physicalAge))) { + if (slave.actualAge !== slave.physicalAge) { + r.textContent += ` ${slave.physicalAge}` + ` year old body.`; + } + if (slave.visualAge !== slave.physicalAge) { + r.textContent += ` Looks ` + `${slave.visualAge}` + `.`; + } + } else { + /* + ** Now the rub. The use of physical Age for the year old body above, basically conflicts with the changes + ** that NCS introduces, so here to *distinguish* the changes, we use visual age with the 'year old body' + ** and appears, for example: Slave release from incubator at age 10, Her summary would show, 'Age 0. 10 + ** year old body.' But if she's given NCS a few weeks after release, while she's still before her first + ** birthday, it'll appear the same. But once her birthday fires, if we ran with the above code it would + ** say: 'Age 1. 11 year old body.' -- this conflicts with the way NCS works though, because she hasn't + ** visually aged, so our change here makes it say 'Age 1. Appears to have a 10 year old body.' + */ + r.textContent += ` Appears to have a ` + `${slave.visualAge}` + ` year old body.`; + } + if (slave.geneMods.NCS === 1) { + makeSpan(r, "NCS", "orange"); + } + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function long_face(slave, c) { + const r = helpers.getNumericRating(data.long.body.face, slave.face + 100); + makeSpan(c, `${r.desc} ${slave.faceShape} face`, r.style, true, slave.face); + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function long_eyes(slave, c) { + if (!canSee(slave)) { + makeSpan(c, "Blind.", "red"); + } else if (!canSeePerfectly(slave)) { + makeSpan(c, "Nearsighted.", "yellow"); + } + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function long_ears(slave, c) { + if (slave.hears <= -2) { + makeSpan(c, "Deaf.", "red"); + } else if ((slave.hears === -1) && (slave.earwear !== "hearing aids")) { + makeSpan(c, "Hard of hearing.", "yellow"); + } + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function long_lips(slave, c) { + makeRatedStyledSpan(c, data.long.body.lips, slave.lips, 0, true); + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function long_teeth(slave, c) { + helpers.makeMappedStyledSpan(c, data.long.body.teeth, slave.teeth); + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function long_muscles(slave, c) { + const h = helpers; + h.makeStyledSpan(c, h.getMultiNumericRating(data.long.body.muscles, [slave.muscles + 100, h.FSData.policy.PhysicalIdealist.active])); + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function long_voice(slave, c) { + if (slave.voice === 0) { + makeSpan(c, "Mute.", "red"); + } else { + helpers.makeMappedStyledSpan(c, data.long.accent, slave.accent); + } + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function long_tits_ass(slave, c) { + const h = helpers; + h.makeStyledSpan(c, + h.getMultiNumericRating(data.long.body.titsAss, + [slave.boobs, slave.butt, h.FSData.policy.AssetExpansionist.active, slave.weight + 100, slave.muscles + 100])); + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function long_hips(slave, c) { + const di = hipsAssRating(slave); + if (di >= 0) { + helpers.makeMappedStyledSpan(c, data.long.body.hipsAss, di); + } + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function long_waist(slave, c) { + makeRatedStyledSpan(c, data.long.body.waist, slave.waist, 100, true); + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function long_implants(slave, c) { + const styles = "pink"; + if ((slave.boobsImplant !== 0) || (slave.buttImplant !== 0) || (slave.lipsImplant !== 0) || (slave.bellyImplant !== -1)) { + makeSpan(c, "Implants.", styles); + } else if ((slave.faceImplant >= 30) || (slave.waist < -95)) { + makeSpan(c, "Surgery enhanced.", styles); + } else { + makeSpan(c, "All natural.", styles); + } + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function long_lactation(slave, c) { + if (slave.lactation === 1) { + makeSpan(c, "Lactating naturally.", "pink"); + } else if (slave.lactation === 2) { + makeSpan(c, "Heavy lactation.", "pink"); + } + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function long_mods(slave, c) { + V.modScore = SlaveStatsChecker.modScore(slave); + if (slave.corsetPiercing === 0 && V.piercingScore < 3 && V.tatScore < 2) { + return; + } else if (V.modScore > 15 || (V.piercingScore > 8 && V.tatScore > 5)) { + makeSpan(c, "Extensive body mods."); + } else if (V.modScore > 7) { + makeSpan(c, "Noticeable body mods."); + } else { + makeSpan(c, "Light body mods."); + } + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function short_age(slave, c) { + let r = makeSpan(c, "", "pink"); + if (V.showAgeDetail === 1) { + r.textContent += slave.actualAge.toString(); + } else if (slave.actualAge >= 18) { + r.textContent += helpers.getNumericRating(data.short.body.age, slave.actualAge); + } + + if (slave.actualAge !== slave.physicalAge) { + r.textContent += ` w ${slave.physicalAge}y-bdy`; + } + if (slave.visualAge !== slave.physicalAge) { + r.textContent += ` Lks${slave.visualAge}`; + } + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function short_face(slave, c) { + makeRatedStyledSpan(c, data.short.body.face, slave.face, 100, true); + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function short_eyes(slave, c) { + if (!canSee(slave)) { + makeSpan(c, "Blind", "red"); + } else if (!canSeePerfectly(slave)) { + makeSpan(c, "Sight-", "yellow"); + } + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function short_ears(slave, c) { + if (slave.hears === -2) { + makeSpan(c, "Deaf", "red"); + } else if ((slave.hears === -1) && (slave.earwear !== "hearing aids")) { + makeSpan(c, "Hearing-", "yellow"); + } + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function short_lips(slave, c) { + makeRatedStyledSpan(c, data.short.body.lips, slave.lips, 0, true); + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function short_teeth(slave, c) { + helpers.makeMappedStyledSpan(c, data.short.body.teeth, slave.teeth); + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function short_muscles(slave, c) { + const h = helpers; + h.makeStyledSpan(c, h.getMultiNumericRating(data.short.body.muscles, [slave.muscles + 100, h.FSData.policy.PhysicalIdealist.active])); + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function short_voice(slave, c) { + if (slave.voice === 0) { + makeSpan(c, "Mute", "red"); + } else { + helpers.makeMappedStyledSpan(c, data.short.accent, slave.accent); + } + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function short_tits_ass(slave, c) { + const h = helpers; + h.makeStyledSpan(c, + h.getMultiNumericRating(data.short.body.titsAss, + [slave.boobs, slave.butt, h.FSData.policy.AssetExpansionist.active, slave.weight + 100, slave.muscles + 100])); + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function short_hips(slave, c) { + const di = hipsAssRating(slave); + if (di >= 0) { + helpers.makeMappedStyledSpan(c, data.short.body.hipsAss, di); + } + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function short_waist(slave, c) { + makeRatedStyledSpan(c, data.short.body.waist, slave.waist, 100, false); + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function short_implants(slave, c) { + if ((slave.boobsImplant === 0) && (slave.buttImplant === 0) && (slave.waist >= -95) && (slave.lipsImplant === 0) && (slave.faceImplant <= 5) && (slave.bellyImplant === -1)) { + makeSpan(c, "Natr", "pink"); + } else { + makeSpan(c, "Impl", "pink"); + } + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function short_lactation(slave, c) { + if (slave.lactation === 1) { + makeSpan(c, "Lact", "pink"); + } else if (slave.lactation === 2) { + makeSpan(c, "Lact", "pink"); + } + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function short_mods(slave, c) { + V.modScore = SlaveStatsChecker.modScore(slave); + if (slave.corsetPiercing === 0 && V.piercingScore < 3 && V.tatScore < 2) { + return; + } else if (V.modScore > 15 || (V.piercingScore > 8 && V.tatScore > 5)) { + makeSpan(c, "Mods++"); + } else if (V.modScore > 7) { + makeSpan(c, "Mods+"); + } else { + makeSpan(c, "Mods"); + } + if (!jQuery.isEmptyObject(slave.brand)) { + makeSpan(c, "Br"); + } + } + + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function short_intelligence(slave, c) { + if (slave.fetish === "mindbroken") { + return; + } + const intelligence = slave.intelligence + slave.intelligenceImplant; + const educationStr = helpers.getNumericRating(data.short.mental.education, slave.intelligenceImplant + 15); + const intelligenceRating = helpers.getNumericRating(data.short.mental.intelligence, intelligence + 100); + makeSpan(c, `${intelligenceRating.desc}${educationStr}`, intelligenceRating.style, true, intelligence); + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function short_skills(slave, c) { + const sd = data.short.skills; + let _SSkills = (slave.skill.anal + slave.skill.oral); + if (((_SSkills + slave.skill.whoring + slave.skill.entertainment) >= 400) && ((slave.vagina < 0) || (slave.skill.vaginal >= 100))) { + helpers.makeStyledSpan(c, sd.mss); + } else { + _SSkills += slave.skill.vaginal; + helpers.makeStyledSpan(c, helpers.getMultiNumericRating(sd.sex, [_SSkills, slave.vagina >= 0 ? 1 : 0]), Math.trunc(_SSkills), true); + helpers.makeRatedStyledSpan(c, sd.whoring, slave.skill.whoring, 0, true); + helpers.makeRatedStyledSpan(c, sd.entertainment, slave.skill.entertainment, 0, true); + } + if (slave.skill.combat > 0) { + helpers.makeStyledSpan(c, sd.fighter); + } + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function short_prestige(slave, c) { + helpers.makeMappedStyledSpan(c, data.short.prestige, slave.prestige); + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function short_porn_prestige(slave, c) { + helpers.makeMappedStyledSpan(c, data.short.pornPrestige, slave.porn.prestige); + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function long_intelligence(slave, c) { + if (slave.fetish === "mindbroken") { + return; + } + const intelligence = slave.intelligence + slave.intelligenceImplant; + const educationStr = helpers.getNumericRating(data.long.mental.education, slave.intelligenceImplant + 15); + const intelligenceRating = helpers.getNumericRating(data.long.mental.intelligence, intelligence + 100); + makeSpan(c, `${intelligenceRating.desc}${educationStr}`, intelligenceRating.style, true, intelligence); + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function long_skills(slave, c) { + const sd = data.long.skills; + let _SSkills = (slave.skill.anal + slave.skill.oral); + if (((_SSkills + slave.skill.whoring + slave.skill.entertainment) >= 400) && ((slave.vagina < 0) || (slave.skill.vaginal >= 100))) { + helpers.makeStyledSpan(c, sd.mss); + } else { + _SSkills += slave.skill.vaginal; + helpers.makeStyledSpan(c, helpers.getMultiNumericRating(sd.sex, [_SSkills, slave.vagina >= 0 ? 1 : 0]), Math.trunc(_SSkills), true); + helpers.makeRatedStyledSpan(c, sd.whoring, slave.skill.whoring, 0, true); + helpers.makeRatedStyledSpan(c, sd.entertainment, slave.skill.entertainment, 0, true); + } + if (slave.skill.combat > 0) { + helpers.makeStyledSpan(c, sd.fighter); + } + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function long_prestige(slave, c) { + helpers.makeMappedStyledSpan(c, data.long.prestige, slave.prestige); + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function long_porn_prestige(slave, c) { + helpers.makeMappedStyledSpan(c, data.long.pornPrestige, slave.porn.prestige); + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function long_clothes(slave, c) { + makeSpan(c, helpers.getExactRating(data.long.clothes, slave.clothes, 'Naked.')); + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function long_collar(slave, c) { + helpers.makeMappedSpan(c, data.long.accessory.collar, slave.collar); + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function long_belly(slave, c) { + helpers.makeMappedSpan(c, data.long.accessory.belly, slave.bellyAccessory); + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function long_arms(slave, c) { + if (["hand gloves", "elbow gloves"].includes(slave.armAccessory)) { + makeSpan(c, slave.armAccessory, undefined, true); + } + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function long_legs(slave, c) { + if (slave.legAccessory === "short stockings") { + makeSpan(c, "Short stockings."); + } else if (slave.legAccessory === "long stockings") { + makeSpan(c, "Long stockings."); + } + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function long_shoes(slave, c) { + if (["boots", "extreme heels", "extreme platform heels", "flats", "heels", "platform heels", "platform shoes", "pumps"].includes(slave.shoes)) { + makeSpan(c, slave.shoes, undefined, true); + } else if (slave.heels === 1) { + makeSpan(c, "Crawling.", "yellow"); + } + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function long_chastity(slave, c) { + if (slave.chastityAnus === 1 && slave.chastityPenis === 1 && slave.chastityVagina === 1) { + makeSpan(c, "Full chastity."); + } else if (slave.chastityPenis === 1 && slave.chastityVagina === 1) { + makeSpan(c, "Genital chastity."); + } else if ((slave.chastityAnus === 1 && slave.chastityVagina === 1) || (slave.chastityAnus === 1 && slave.chastityPenis === 1)) { + makeSpan(c, "Combined chastity."); + } else if (slave.chastityVagina === 1) { + makeSpan(c, "Vaginal chastity."); + } else if (slave.chastityPenis === 1) { + makeSpan(c, "Chastity cage."); + } else if (slave.chastityAnus === 1) { + makeSpan(c, "Anal chastity."); + } + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function long_vaginal_acc(slave, c) { + if (slave.vaginalAttachment !== "vibrator") { + helpers.makeMappedSpan(c, data.long.accessory.vaginal, slave.vaginalAccessory); + } + if (slave.vaginalAttachment !== "none") { + switch (slave.vaginalAttachment) { + case "vibrator": + makeSpan(c, "Vibrating dildo."); + break; + } + } + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function long_dick_acc(slave, c) { + switch (slave.dickAccessory) { + case "sock": + makeSpan(c, "Cock sock."); + break; + case "bullet vibrator": + makeSpan(c, "Frenulum bullet vibrator."); + break; + case "smart bullet vibrator": + makeSpan(c, "Smart frenulum bullet vibrator."); + break; + } + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function long_buttplug(slave, c) { + helpers.makeMappedSpan(c, data.long.accessory.buttplug, slave.buttplug); + helpers.makeMappedSpan(c, data.long.accessory.buttplugAttachment, slave.buttplugAttachment); + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function short_fetish(slave, c) { + function fetishStr(slave) { + const tbl = data.short.fetish[slave.fetish]; + if (!tbl || typeof tbl === 'string') { + return tbl; + } + return helpers.getNumericRating(tbl, slave.fetishStrength); + } + makeSpan(c, fetishStr(slave), "lightcoral", true, slave.fetishStrength); + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function short_attraction(slave, c) { + const sd = data.short.sexDrive; + // check for special cases first + if (slave.energy > 95 && slave.attrXX > 95 && slave.attrXY > 95) { + helpers.makeStyledSpan(c, sd.synergy.nymphomni); + } else if (slave.attrXX > 95 && slave.attrXY > 95) { + helpers.makeStyledSpan(c, sd.synergy.omni); + } else { + helpers.makeRatedStyledSpan(c, sd.XY, slave.attrXY, 0, true); + helpers.makeRatedStyledSpan(c, sd.XX, slave.attrXX, 0, true); + helpers.makeRatedStyledSpan(c, sd.energy, slave.energy, 0, true); + } + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function short_smart_fetish(slave, c) { + const s = smartFetishStr(slave, data.short.smartPiercing); + if (s) { + makeSpan(c, s); + } + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function short_smart_attraction(slave, c) { + /* + if (slave.attrKnown === 1) { + if ((slave.attrXX < 100) && (slave.clitSetting === "women")) { + makeSpan(c, `SP: women.`); + } else if ((slave.attrXY < 100) && (slave.clitSetting === "men")) { + makeSpan(c, `SP: men.`); + } + } else { + if (slave.clitSetting === "women") { + makeSpan(c, `SP: women.`); + } else if (slave.clitSetting === "men") { + makeSpan(c, `SP: men.`); + } + }*/ + const s = smartAttractionStr(slave, data.short.smartPiercing); + if (s) { + makeSpan(c, s); + } + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function short_behavior_flaw(slave, c) { + helpers.makeMappedSpan(c, data.short.mental.behavioralFlaw, slave.behavioralFlaw, "red"); + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function short_sex_flaw(slave, c) { + helpers.makeMappedStyledSpan(c, data.short.mental.sexualFlaw, slave.sexualFlaw); + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function short_behavior_quirk(slave, c) { + helpers.makeMappedSpan(c, data.short.mental.behavioralQuirk, slave.behavioralQuirk, "green"); + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function short_sex_quirk(slave, c) { + helpers.makeMappedSpan(c, data.short.mental.sexualQuirk, slave.sexualQuirk, "green"); + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function long_fetish(slave, c) { + function fetishStr(slave) { + const tbl = data.long.fetish[slave.fetish]; + if (!tbl || typeof tbl === 'string') { + return tbl; + } + return helpers.getNumericRating(tbl, slave.fetishStrength); + } + makeSpan(c, fetishStr(slave), "lightcoral", true, slave.fetishStrength); + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function long_attraction(slave, c) { + const sd = data.long.sexDrive; + + // check for special cases first + if (slave.energy > 95 && slave.attrXX > 95 && slave.attrXY > 95) { + helpers.makeStyledSpan(c, sd.synergy.nymphomni); + } else if (slave.attrXX > 95 && slave.attrXY > 95) { + helpers.makeStyledSpan(c, sd.synergy.omni); + } else { + helpers.makeRatedStyledSpan(c, sd.XY, slave.attrXY, 0, true); + helpers.makeRatedStyledSpan(c, sd.XX, slave.attrXX, 0, true); + helpers.makeRatedStyledSpan(c, sd.energy, slave.energy, 0, true); + } + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function long_smart_fetish(slave, c) { + const s = smartFetishStr(slave, data.long.smartPiercing); + if (s) { + makeSpan(c, s); + } + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function long_smart_attraction(slave, c) { + const s = smartAttractionStr(slave, data.long.smartPiercing); + if (s) { + makeSpan(c, s); + } + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function long_behavior_flaw(slave, c) { + helpers.makeMappedSpan(c, data.long.mental.behavioralFlaw, slave.behavioralFlaw, "red"); + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function long_sex_flaw(slave, c) { + switch (slave.sexualFlaw) { + case "hates oral": + case "hates anal": + case "hates penetration": + case "shamefast": + makeSpan(c, slave.sexualFlaw, "red", true); + break; + case "idealistic": + case "repressed": + case "apathetic": + case "crude": + case "judgemental": + makeSpan(c, `Sexually ${slave.sexualFlaw}.`, "red"); + break; + case "cum addict": + case "anal addict": + case "attention whore": + makeSpan(c, slave.sexualFlaw, "yellow", true); + break; + case "breast growth": + makeSpan(c, `Breast obsession.`, "yellow"); + break; + case "abusive": + case "malicious": + makeSpan(c, `Sexually ${slave.sexualFlaw}.`, "yellow"); + break; + case "self hating": + makeSpan(c, `Self hatred.`, "yellow"); + break; + case "neglectful": + makeSpan(c, `Self neglectful.`, "yellow"); + break; + case "breeder": + makeSpan(c, `Breeding obsession.`, "yellow"); + break; + default: + slave.sexualFlaw = "none"; + break; + } + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function long_behavior_quirk(slave, c) { + switch (slave.behavioralQuirk) { + case "confident": + case "cutting": + case "funny": + case "fitness": + case "adores women": + case "adores men": + case "insecure": + case "sinful": + case "advocate": + makeSpan(c, slave.behavioralQuirk, "green", true); + break; + default: + slave.behavioralQuirk = "none"; + break; + } + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function long_sex_quirk(slave, c) { + switch (slave.sexualQuirk) { + case "gagfuck queen": + case "painal queen": + case "strugglefuck queen": + case "tease": + case "romantic": + case "perverted": + case "caring": + case "unflinching": + case "size queen": + makeSpan(c, slave.sexualQuirk, "green", true); + break; + default: + slave.sexualQuirk = "none"; + break; + } + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function short_family(slave, c) { + let res = ""; + let handled = 0; + if (slave.mother > 0) { + const _ssj = V.slaves.findIndex(s => s.ID === slave.mother); + if (_ssj !== -1) { + res += `${SlaveFullName(V.slaves[_ssj])}'s ${getPronouns(slave).daughter}`; + if (slave.relationshipTarget === V.slaves[_ssj].ID) { + const friendShipShort = relationshipTermShort(slave); + res += ` & ${friendShipShort}`; + handled = 1; + } + } + res += " "; + } else if (slave.mother === -1) { + res += `Your ${getPronouns(slave).daughter}`; + if (slave.relationship < -1) { + res += ` & ${PCrelationshipTerm(slave)}`; + handled = 1; + } + res += " "; + } else if (slave.mother in V.missingTable && V.showMissingSlavesSD && V.showMissingSlaves) { + res += `${V.missingTable[slave.mother].fullName}'s ${getPronouns(slave).daughter} `; + } + if (slave.father > 0 && slave.father !== slave.mother) { + const _ssj = V.slaves.findIndex(s => s.ID === slave.father); + if (_ssj !== -1) { + res += `${SlaveFullName(V.slaves[_ssj])}'s ${getPronouns(slave).daughter}`; + if (slave.relationshipTarget === V.slaves[_ssj].ID && handled !== 1) { + const friendShipShort = relationshipTermShort(slave); + res += ` & ${friendShipShort}`; + handled = 1; + } + } + res += " "; + } else if (slave.father === -1 && slave.mother !== -1) { + res += `Your ${getPronouns(slave).daughter}`; + if (slave.relationship < -1) { + res += ` & ${PCrelationshipTerm(slave)}`; + handled = 1; + } + res += " "; + } else if (slave.father in V.missingTable && slave.father !== slave.mother && V.showMissingSlavesSD && V.showMissingSlaves) { + res += `${V.missingTable[slave.father].fullName}'s ${getPronouns(slave).daughter}`; + } + if (slave.daughters === 1) { + let _ssj = V.slaves.findIndex(s => s.mother === slave.ID); + if (_ssj !== -1) { + res += `${SlaveFullName(V.slaves[_ssj])}'s mother`; + if (slave.relationshipTarget === V.slaves[_ssj].ID) { + const friendShipShort = relationshipTermShort(slave); + res += ` & ${friendShipShort}`; + handled = 1; + } + } + res += " "; + _ssj = V.slaves.findIndex(s => s.father === slave.ID); + if (_ssj !== -1) { + res += `${SlaveFullName(V.slaves[_ssj])}'s father`; + if (slave.relationshipTarget === V.slaves[_ssj].ID && handled !== 1) { + const friendShipShort = relationshipTermShort(slave); + res += ` & ${friendShipShort}`; + handled = 1; + } + } + res += " "; + } else if (slave.daughters > 1) { + res += `multiple daughters `; + } + if (slave.sisters === 1) { + const _ssj = V.slaves.findIndex(s => areSisters(s, slave) > 0); + if (_ssj !== -1) { + res += `${SlaveFullName(V.slaves[_ssj])}'s ${getPronouns(slave).sister}`; + if (slave.relationshipTarget === V.slaves[_ssj].ID) { + const friendShipShort = relationshipTermShort(slave); + res += `& ${friendShipShort}`; + handled = 1; + } + } + res += " "; + } else if (slave.sisters > 1) { + res += `multiple sisters `; + } + if (slave.relationship > 0 && handled !== 1) { + const _ssj = V.slaves.findIndex(s => s.ID === slave.relationshipTarget); + if (_ssj !== -1) { + res += `${SlaveFullName(V.slaves[_ssj])}'s`; + const friendShipShort = relationshipTermShort(slave); + res += ` ${friendShipShort}`; + } + } else if (slave.relationship === -3 && slave.mother !== -1 && slave.father !== -1) { + res += `Your ${getPronouns(slave).wife}`; + } else if (slave.relationship === -2) { + res += `E Bonded`; + } else if (slave.relationship === -1) { + res += `E Slut`; + } + helpers.makeSpan(c, res); + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function short_clone(slave, c) { + if (slave.clone !== 0) { + makeSpan(c, "Clone"); + } + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function short_rival(slave, c) { + if (slave.rivalry !== 0) { + const block = makeBlock(c, "lightsalmon"); + const _ssj = V.slaves.findIndex(s => s.ID === slave.rivalryTarget); + if (_ssj !== -1) { + if (slave.rivalry <= 1) { + block.textContent = `Disl ${SlaveFullName(V.slaves[_ssj])}`; + } else if (slave.rivalry <= 2) { + block.textContent = `${SlaveFullName(V.slaves[_ssj])}'s rival`; + } else { + block.textContent = `Hates ${SlaveFullName(V.slaves[_ssj])}`; + } + } + } + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function long_family(slave, c) { + let handled = 0; + const block = makeBlock(); + if (slave.mother > 0) { + const _ssj = V.slaves.findIndex(s => s.ID === slave.mother); + if (_ssj !== -1) { + addText(block, `${SlaveFullName(V.slaves[_ssj])}'s `); + const tmpSpan = makeSpan(block, getPronouns(slave).daughter, "lightgreen"); + if (slave.relationshipTarget === V.slaves[_ssj].ID) { + const friendShipShort = relationshipTerm(slave); + tmpSpan.textContent += ` and ${friendShipShort}`; + handled = 1; + } + tmpSpan.textContent += '.'; + } + } else if (slave.mother === -1) { + addText(block, `Your `); + if (slave.relationship < -1) { + makeSpan(block, `${getPronouns(slave).daughter} and ${PCrelationshipTerm(slave)}.`, "lightgreen"); + handled = 1; + } else { + makeSpan(block, `${getPronouns(slave).daughter}.`, "lightgreen"); + } + } else if (slave.mother in V.missingTable && V.showMissingSlavesSD && V.showMissingSlaves) { + addText(block, `${V.missingTable[slave.mother].fullName}'s `); + makeSpan(block, `${getPronouns(slave).daughter}.`, "lightgreen"); + } + if (slave.father > 0 && slave.father !== slave.mother) { + const _ssj = V.slaves.findIndex(s => s.ID === slave.father); + if (_ssj !== -1) { + addText(block, `${SlaveFullName(V.slaves[_ssj])}'s `); + const tmpSpan = makeSpan(block, getPronouns(slave).daughter, "lightgreen"); + if (slave.relationshipTarget === V.slaves[_ssj].ID) { + const friendShipShort = relationshipTerm(slave); + tmpSpan.textContent += ` and ${friendShipShort}`; + handled = 1; + } + tmpSpan.textContent += '.'; + } + } else if (slave.father === -1 && slave.father !== slave.mother) { + addText(block, `Your `); + if (slave.relationship < -1) { + makeSpan(block, `${getPronouns(slave).daughter} and ${PCrelationshipTerm(slave)}.`, "lightgreen"); + handled = 1; + } else { + makeSpan(block, `${getPronouns(slave).daughter}.`, "lightgreen"); + } + } else if (slave.father in V.missingTable && slave.father !== slave.mother && V.showMissingSlavesSD && V.showMissingSlaves) { + addText(block, `${V.missingTable[slave.father].fullName}'s `); + makeSpan(block, `${getPronouns(slave).daughter}.`, "lightgreen"); + } + if (areSisters(V.PC, slave) > 0) { + addText(block, `Your `); + if (slave.relationship < -1) { + makeSpan(block, `${relativeTerm(V.PC, slave)} and ${PCrelationshipTerm(slave)}.`, "lightgreen"); + handled = 1; + } else { + makeSpan(block, `${relativeTerm(V.PC, slave)}.`, "lightgreen"); + } + } + if (slave.daughters === 1) { + let _ssj = V.slaves.findIndex(s => s.mother === slave.ID); + if (_ssj !== -1) { + addText(block, `${SlaveFullName(V.slaves[_ssj])}'s `); + const tmpSpan = makeSpan(block, "mother", "lightgreen"); + if (slave.relationshipTarget === V.slaves[_ssj].ID) { + const friendShipShort = relationshipTerm(slave); + tmpSpan.textContent += ` and ${friendShipShort}`; + handled = 1; + } + tmpSpan.textContent += '.'; + } + _ssj = V.slaves.findIndex(s => s.father === slave.ID); + if (_ssj !== -1) { + addText(block, `${SlaveFullName(V.slaves[_ssj])}'s `); + const tmpSpan = makeSpan(block, "father", "lightgreen"); + if (slave.relationshipTarget === V.slaves[_ssj].ID) { + const friendShipShort = relationshipTerm(slave); + tmpSpan.textContent += ` and ${friendShipShort}`; + handled = 1; + } + tmpSpan.textContent += '.'; + } + } else if (slave.daughters > 1) { + if (slave.daughters > 10) { + makeSpan(block, "Has tons of daughters.", "lightgreen"); + } else if (slave.daughters > 5) { + makeSpan(block, "Has many daughters.", "lightgreen"); + } else { + makeSpan(block, "Has several daughters.", "lightgreen"); + } + } + if (slave.sisters === 1) { + const _ssj = V.slaves.findIndex(s => areSisters(s, slave) > 0); + if (_ssj !== -1) { + addText(block, `${SlaveFullName(V.slaves[_ssj])}'s `); + const tmpSpan = makeSpan(block, getPronouns(slave).sister, "lightgreen"); + if (slave.relationshipTarget === V.slaves[_ssj].ID) { + const friendShipShort = relationshipTerm(slave); + tmpSpan.textContent += ` and ${friendShipShort}`; + handled = 1; + } + tmpSpan.textContent += '.'; + } + } else if (slave.sisters > 1) { + if (slave.sisters > 10) { + makeSpan(block, "One of many sisters.", "lightgreen"); + } else if (slave.sisters > 5) { + makeSpan(block, "Has many sisters.", "lightgreen"); + } else { + makeSpan(block, "Has several sisters.", "lightgreen"); + } + } + if (slave.relationship > 0 && handled !== 1) { + const _ssj = V.slaves.findIndex(s => s.ID === slave.relationshipTarget); + if (_ssj !== -1) { + const friendship = relationshipTerm(slave); + addText(block, `${SlaveFullName(V.slaves[_ssj])}'s `); + makeSpan(block, `${friendship}.`, "lightgreen"); + } + } else if (slave.relationship === -3 && slave.mother !== -1 && slave.father !== -1 && areSisters(V.PC, slave) === 0) { + makeSpan(block, `Your ${getPronouns(slave).wife}.`, "lightgreen"); + } else if (slave.relationship === -2) { + makeSpan(block, "Emotionally bonded to you.", "lightgreen"); + } else if (slave.relationship === -1) { + makeSpan(block, "Emotional slut.", "lightgreen"); + } + + if (block.textContent.length > 0) { + c.appendChild(block); + } + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function long_clone(slave, c) { + if (slave.clone !== 0) { + makeSpan(c, `Clone of ${slave.clone}.`, "skyblue"); + } + } + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + function long_rival(slave, c) { + if (slave.rivalry !== 0) { + const block = makeBlock(c); + const _ssj = V.slaves.findIndex(s => s.ID === slave.rivalryTarget); + if (_ssj !== -1) { + if (slave.rivalry <= 1) { + makeSpan(block, "Dislikes", "lightsalmon"); + block.appendChild(document.createTextNode(` ${SlaveFullName(V.slaves[_ssj])}.`)); + } else if (slave.rivalry <= 2) { + block.appendChild(document.createTextNode(`${SlaveFullName(V.slaves[_ssj])}'s `)); + makeSpan(block, "rival.", "lightsalmon"); + } else { + makeSpan(block, "Hates", "lightsalmon"); + block.appendChild(document.createTextNode(` ${SlaveFullName(V.slaves[_ssj])}.`)); + } + } + } + } + + return { + long: { + health: long_health, + illness: long_illness, + tired: long_tired, + age: long_age, + face: long_face, + eyes: long_eyes, + ears: long_ears, + lips: long_lips, + teeth: long_teeth, + muscles: long_muscles, + voice: long_voice, + tits_ass: long_tits_ass, + hips: long_hips, + waist: long_waist, + implants: long_implants, + lactation: long_lactation, + mods: long_mods, + intelligence: long_intelligence, + skills: long_skills, + prestige: long_prestige, + porn_prestige: long_porn_prestige, + clothes: long_clothes, + collar: long_collar, + belly: long_belly, + arms: long_arms, + legs: long_legs, + shoes: long_shoes, + chastity: long_chastity, + vaginal_acc: long_vaginal_acc, + dick_acc: long_dick_acc, + buttplug: long_buttplug, + fetish: long_fetish, + attraction: long_attraction, + smart_fetish: long_smart_fetish, + smart_attraction: long_smart_attraction, + behavior_flaw: long_behavior_flaw, + sex_flaw: long_sex_flaw, + behavior_quirk: long_behavior_quirk, + sex_quirk: long_sex_quirk, + family: long_family, + clone: long_clone, + rival: long_rival + }, + short: { + health: short_health, + illness: short_illness, + tired: short_tired, + age: short_age, + face: short_face, + eyes: short_eyes, + ears: short_ears, + lips: short_lips, + teeth: short_teeth, + muscles: short_muscles, + voice: short_voice, + tits_ass: short_tits_ass, + hips: short_hips, + waist: short_waist, + implants: short_implants, + lactation: short_lactation, + mods: short_mods, + intelligence: short_intelligence, + skills: short_skills, + prestige: short_prestige, + porn_prestige: short_porn_prestige, +/* clothes: short_clothes, + collar: short_collar, + belly: short_belly, + arms: short_arms, + legs: short_legs, + shoes: short_shoes, + chastity: short_chastity, + vaginal_acc: short_vaginal_acc, + dick_acc: short_dick_acc, + buttplug: short_buttplug, +*/ + fetish: short_fetish, + attraction: short_attraction, + smart_fetish: short_smart_fetish, + smart_attraction: short_smart_attraction, + behavior_flaw: short_behavior_flaw, + sex_flaw: short_sex_flaw, + behavior_quirk: short_behavior_quirk, + sex_quirk: short_sex_quirk, + family: short_family, + clone: short_clone, + rival: short_rival + } + }; + }(); + + return { + helpers: helpers, + bits: bits + }; +}(); diff --git a/src/js/slaveSummaryWidgets.js b/src/js/slaveSummaryWidgets.js index a5b28aa5561a5c738c92c4416a49d313a728868f..ad5e8a32fa9b911f97961fee7568951fa964107f 100644 --- a/src/js/slaveSummaryWidgets.js +++ b/src/js/slaveSummaryWidgets.js @@ -1,4514 +1,1008 @@ -/* eslint-disable camelcase */ +App.UI.SlaveSummaryRenderers = function() { -window.SlaveSummary = (function() { - /** @type {DocumentFragment} */ - let res; + const bits = App.UI.SlaveSummaryImpl.bits; + const helpers = App.UI.SlaveSummaryImpl.helpers; + const data = App.Data.SlaveSummary; - /** - * @param {App.Entity.SlaveState} slave - * @returns {DocumentFragment} */ - function SlaveSummary(slave) { - res = document.createDocumentFragment(); - let para = makeParagraph(res); - - if (V.abbreviateDevotion === 1) { - short_devotion(slave, para); - } else if (V.abbreviateDevotion === 2) { - long_devotion(slave, para); - } - if (slave.fuckdoll === 0) { - if (V.abbreviateRules === 1) { - short_rules(slave, para); - } else if (V.abbreviateRules === 2) { - long_rules(slave, para); + const shortRenderers = { + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + */ + devotion: function(slave, c) { + const makeSpan = helpers.makeSpan; + if (slave.fetish === "mindbroken") { + makeSpan(c, "MB", "mindbroken"); + } else { + helpers.makeRatedStyledSpan(c, data.short.mental.devotion, slave.devotion, 100, true); + helpers.makeStyledSpan(c, helpers.getMultiNumericRating(data.short.mental.trust, [slave.trust + 100, slave.devotion + 100]), + slave.trust, true); } - } - if (V.abbreviateDiet === 1) { - short_weight(slave, para); - } else if (V.abbreviateDiet === 2) { - long_weight(slave, para); - } - if (V.abbreviateDiet === 1) { - short_diet(slave, para); - } else if (V.abbreviateDiet === 2) { - long_diet(slave, para); - } - if (V.abbreviateHealth === 1) { - short_health(slave, para); - short_illness(slave, para); - short_tired(slave, para); - } else if (V.abbreviateHealth === 2) { - long_health(slave, para); - long_illness(slave, para); - long_tired(slave, para); - } - if (V.abbreviateDrugs === 1) { - short_drugs(slave, para); - } else if (V.abbreviateDrugs === 2) { - long_drugs(slave, para); - } - - para = makeParagraph(res); - para.classList.add("pink"); + }, - makeSpan(para, `${capFirstChar(SlaveTitle(slave))}${V.abbreviatePhysicals === 2 ? '.' : ''}`, ["coral", "strong"]); - if (V.seeRace === 1 && V.abbreviateRace !== 0) { - makeSpan(para, V.abbreviateRace === 1 ? short_race(slave) : long_race(slave), "tan"); - } - if (V.abbreviateNationality !== 0) { - makeSpan(para, V.abbreviateNationality === 1 ? short_nationality(slave) : long_nationality(slave), "tan"); - } - makeSpan(para, V.abbreviatePhysicals === 1 ? short_skin(slave) : `${capFirstChar(slave.skin)} skin.`); - if (V.abbreviateGenitalia === 1) { - short_genitals(slave, para); - } else if (V.abbreviateGenitalia === 2) { - long_genitals(slave, para); - } - if (V.abbreviatePhysicals === 1) { - short_age(slave, para); - short_face(slave, para); - short_eyes(slave, para); - short_ears(slave, para); - if (slave.markings !== "none") { - makeSpan(para, "Markings"); + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + rules: function(slave, c) { + const makeSpan = helpers.makeSpan; + const styles = "strong"; + switch (slave.rules.living) { + case "luxurious": + makeSpan(c, "LS:Lux", styles); + break; + case "normal": + makeSpan(c, "LS:Nor", styles); + break; + default: + makeSpan(c, "LS:Spa", styles); + break; } - short_lips(slave, para); - short_teeth(slave, para); - short_muscles(slave, para); - addText(para, App.Desc.shortLimbs(slave)); - short_voice(slave, para); - short_tits_ass(slave, para); - short_hips(slave, para); - short_waist(slave, para); - short_implants(slave, para); - short_lactation(slave, para); - short_mods(slave, para); - } else if (V.abbreviatePhysicals === 2) { - long_age(slave, para); - long_face(slave, para); - long_eyes(slave, para); - long_ears(slave, para); - long_lips(slave, para); - long_teeth(slave, para); - long_muscles(slave, para); - makeSpan(para, App.Desc.longLimbs(slave)); - long_voice(slave, para); - long_tits_ass(slave, para); - long_hips(slave, para); - long_waist(slave, para); - long_implants(slave, para); - long_lactation(slave, para); - long_mods(slave, para); - if (!jQuery.isEmptyObject(slave.brand)) { - makeSpan(para, `Branded.`); + if (canTalk(slave, false)) { + switch (slave.rules.speech) { + case "permissive": + makeSpan(c, "SpR:P", styles); + break; + case "accent elimination": + makeSpan(c, "SpR:NoAcc", styles); + break; + case "language lessons": + makeSpan(c, "SpR:LL", styles); + break; + default: + makeSpan(c, "SpR:R", styles); + break; + } + } + switch (slave.rules.relationship) { + case "permissive": + makeSpan(c, "ReR:P", styles); + break; + case "just friends": + makeSpan(c, "ReR:Fr", styles); + break; + default: + makeSpan(c, "ReR:R", styles); + break; } - } - if (V.abbreviateHormoneBalance === 1) { - if (slave.hormoneBalance <= -21) { - makeSpan(para, "HB:M", ["deepskyblue", "strong"]); - } else if (slave.hormoneBalance <= 20) { - makeSpan(para, "HB:N", ["pink", "strong"]); - } else if (slave.hormoneBalance <= 500) { - makeSpan(para, "HB:F", ["pink", "strong"]); + switch (slave.rules.punishment) { + case "confinement": + makeSpan(c, "Pun:Conf", styles); + break; + case "whipping": + makeSpan(c, "Pun:Whip", styles); + break; + case "chastity": + makeSpan(c, "Pun:Chas", styles); + break; + default: + makeSpan(c, "Pun:Situ", styles); + break; } - } else if (V.abbreviateHormoneBalance === 2) { - const colorClass = slave.hormoneBalance <= -21 ? "deepskyblue" : "pink"; - let desc = ""; - if (slave.hormoneBalance < -400) { - desc = `Overwhelmingly masculine`; - } else if (slave.hormoneBalance <= -300) { - desc = `Extremely masculine`; - } else if (slave.hormoneBalance <= -200) { - desc = `Heavily masculine`; - } else if (slave.hormoneBalance <= -100) { - desc = `Very masculine`; - } else if (slave.hormoneBalance <= -21) { - desc = `Masculine`; - } else if (slave.hormoneBalance <= 20) { - desc = `Neutral`; - } else if (slave.hormoneBalance <= 99) { - desc = `Feminine`; - } else if (slave.hormoneBalance <= 199) { - desc = `Very feminine`; - } else if (slave.hormoneBalance <= 299) { - desc = `Heavily feminine`; - } else if (slave.hormoneBalance <= 399) { - desc = `Extremely feminine`; - } else if (slave.hormoneBalance <= 500) { - desc = `Overwhelmingly feminine`; + switch (slave.rules.reward) { + case "relaxation": + makeSpan(c, "Rew:Relx", styles); + break; + case "drugs": + makeSpan(c, "Rew:Drug", styles); + break; + case "orgasm": + makeSpan(c, "Rew:Orga", styles); + break; + default: + makeSpan(c, "Rew:Situ", styles); + break; } - makeSpan(para, desc + " hormone balance.", colorClass); - } + makeSpan(c, "MaR:" + App.Utils.releaseSummaryShort(slave), styles); + }, - para = makeParagraph(res); - if (V.abbreviateSkills === 1) { - short_intelligence(slave, para); - short_skills(slave, para); - short_prestige(slave, para); - short_porn_prestige(slave, para); - } else if (V.abbreviateSkills === 2) { - long_intelligence(slave, para); - long_skills(slave, para); - long_prestige(slave, para); - long_porn_prestige(slave, para); - } - if (V.abbreviateMental === 1) { - if (slave.fetish !== "mindbroken") { - if (slave.fetishKnown === 1) { - short_fetish(slave, para); + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + health: function(slave, c) { + const b = bits.short; + b.health(slave, c); + b.illness(slave, c); + b.tired(slave, c); + }, + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + weight: function(slave, c) { + helpers.makeStyledSpan(c, helpers.getMultiNumericRating(data.short.body.weight, + [slave.weight + 100, helpers.FSData.policy.HedonisticDecadence.active, slave.hips + 2]), slave.weight, true); + }, + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + genitalia: function(slave, c) { + const makeSpan = helpers.makeSpan; + if (slave.dick > 0) { + let dickDesc = slave.balls === 0 ? "Geld" : ""; + const dickBallsDesc = helpers.getMultiNumericRating(data.short.body.genitalia.dickBalls, [slave.dick, slave.balls]); + if (dickBallsDesc) { + dickDesc += ` ${dickBallsDesc}`; } - if (slave.attrKnown === 1) { - short_attraction(slave, para); + if (dickDesc) { + helpers.makeSpan(c, dickDesc, "pink"); } } - if (slave.clitPiercing === 3) { - short_smart_fetish(slave, para); - short_smart_attraction(slave, para); + if (slave.vagina === 0) { + makeSpan(c, "VV", "lime"); + } else if ((slave.pregKnown === 1) && canWalk(slave) && (slave.clothes === "no clothing" || slave.clothes === "body oil") && (slave.shoes === "none")) { + makeSpan(c, "NBP", "pink"); + } + if (slave.anus === 0) { + makeSpan(c, "AV", "lime"); + } + const holesDesc = helpers.getMultiNumericRating(data.long.body.genitalia.holes, [slave.vagina, slave.anus]); + if (holesDesc) { + makeSpan(c, holesDesc, "pink"); + } + }, + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + diet: function(slave, c) { + const style = ["teal", "strong"]; + helpers.makeMappedSpan(c, data.short.diet, slave.diet, style); + helpers.makeMappedSpan(c, data.short.specialDiet, slave.dietCum + 3 * slave.dietMilk, style); + }, + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + drugs: function(slave, c) { + let drugDesc = data.short.drugs[slave.drugs]; + const makeSpan = helpers.makeSpan; + if (drugDesc) { + makeSpan(c, "Dr:" + drugDesc, ["tan", "strong"]); + } + if (slave.curatives === 2) { + makeSpan(c, "Cura", ["lightgreen", "strong"]); + } else if (slave.curatives === 1) { + makeSpan(c, "Prev", ["lightgreen", "strong"]); + } + if (slave.aphrodisiacs !== 0) { + if (slave.aphrodisiacs === 1) { + makeSpan(c, "Aph", ["lightblue", "strong"]); + } else if (slave.aphrodisiacs === 2) { + makeSpan(c, "Aph++", ["lightblue", "strong"]); + } else { + makeSpan(c, "Anaph", ["lightblue", "strong"]); + } + } + if (slave.addict !== 0) { + makeSpan(c, "Add", "cyan"); + } + let styles = ["lightsalmon", "strong"]; + if (slave.hormones > 1) { + makeSpan(c, "Ho:F+", styles); + } else if (slave.hormones > 0) { + makeSpan(c, "Ho:F", styles); + } else if (slave.hormones < -1) { + makeSpan(c, "Ho:M+", styles); + } else if (slave.hormones < 0) { + makeSpan(c, "Ho:M", styles); + } + + styles = ["mediumseagreen", "strong"]; + if ((slave.bellyImplant > -1)) { + makeSpan(c, "Belly Imp", styles); + } else if (((slave.preg <= -2) || (slave.ovaries === 0)) && (slave.vagina !== -1)) { + makeSpan(c, "Barr", styles); + } else if (slave.pubertyXX === 0 && (slave.ovaries === 1 || slave.mpreg === 1)) { + makeSpan(c, "Prepub", styles); + } else if (slave.ovaryAge >= 47 && (slave.ovaries === 1 || slave.mpreg === 1)) { + makeSpan(c, "Meno", styles); + } else if (slave.pregWeek < 0) { + makeSpan(c, "Postpartum", styles); + } else if (slave.preg === -1) { + makeSpan(c, "CC", styles); + } else if (slave.preg === 0 && (slave.ovaries === 1 || slave.mpreg === 1)) { + makeSpan(c, "Fert+", styles); + } else if (((slave.preg < slave.pregData.normalBirth / 10) && (slave.preg > 0) && slave.pregKnown === 0) || slave.pregWeek === 1) { + makeSpan(c, "Preg?", styles); + } else if ((slave.preg >= 36) && (slave.broodmother > 0)) { + makeSpan(c, "Perm preg", styles); + } else if (slave.pregKnown === 1) { + makeSpan(c, `${slave.pregWeek} wks preg`, styles); + } + if (slave.induce === 1) { + makeSpan(c, "Early Labor", ["orange", "strong"]); + } + if (slave.pubertyXY === 0 && slave.balls > 0) { + makeSpan(c, "Prepub balls", "strong"); + } + if (slave.balls > 0 && slave.vasectomy === 1) { + makeSpan(c, "Vasect", "strong"); + } + styles = ["springgreen", "strong"]; + if (slave.inflation === 3) { + makeSpan(c, `8 ltr ${slave.inflationType}`, styles); + } else if (slave.inflation === 2) { + makeSpan(c, `4 ltr ${slave.inflationType}`, styles); + } else if (slave.inflation === 1) { + makeSpan(c, `2 ltr ${slave.inflationType}`, styles); + } else if (slave.bellyFluid > 0) { + makeSpan(c, `${slave.bellyFluid}ccs ${slave.inflationType}`, styles); + } + }, + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + physicals: function(slave, c) { + const b = bits.short; + b.age(slave, c); + b.face(slave, c); + b.eyes(slave, c); + b.ears(slave, c); + if (slave.markings !== "none") { + helpers.makeSpan(c, "Markings"); + } + b.lips(slave, c); + b.teeth(slave, c); + b.muscles(slave, c); + helpers.addText(c, App.Desc.shortLimbs(slave)); + b.voice(slave, c); + b.tits_ass(slave, c); + b.hips(slave, c); + b.waist(slave, c); + b.implants(slave, c); + b.lactation(slave, c); + b.mods(slave, c); + }, + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + hormoneBalance: function(slave, c) { + if (slave.hormoneBalance <= -21) { + helpers.makeSpan(c, "HB:M", ["deepskyblue", "strong"]); + } else if (slave.hormoneBalance <= 20) { + helpers.makeSpan(c, "HB:N", ["pink", "strong"]); + } else if (slave.hormoneBalance <= 500) { + helpers.makeSpan(c, "HB:F", ["pink", "strong"]); } - short_behavior_flaw(slave, para); - short_sex_flaw(slave, para); - short_behavior_quirk(slave, para); - short_sex_quirk(slave, para); - } else if (V.abbreviateMental === 2) { + }, + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + mental: function(slave, c) { + const b = bits.short; if (slave.fetish !== "mindbroken") { if (slave.fetishKnown === 1) { - long_fetish(slave, para); + b.fetish(slave, c); } if (slave.attrKnown === 1) { - long_attraction(slave, para); + b.attraction(slave, c); } } if (slave.clitPiercing === 3) { - long_smart_fetish(slave, para); - long_smart_attraction(slave, para); - } - long_behavior_flaw(slave, para); - long_sex_flaw(slave, para); - long_behavior_quirk(slave, para); - long_sex_quirk(slave, para); - } - if (slave.custom.label) { - makeSpan(res, `${capFirstChar(slave.custom.label)}.`, ["yellow", "strong"]); - } - if ((slave.relationship !== 0) || (V.abbreviateClothes === 2) || (V.abbreviateRulesets === 2)) { - para = makeParagraph(res); - } - if (V.abbreviateMental === 1) { - makeSpan(para, short_extended_family(slave), "lightgreen"); - short_clone(slave, para); - short_rival(slave, para); - } else if (V.abbreviateMental === 2) { - long_extended_family(slave, para); - long_clone(slave, para); - long_rival(slave, para); - } - if (slave.fuckdoll === 0) { - if (V.abbreviateClothes === 2) { - const dressingBlock = makeBlock(para); - if (slave.choosesOwnClothes === 1) { - makeSpan(dressingBlock, `Dressing ${getPronouns(slave).himself}.`); - } - long_clothes(slave, dressingBlock); - long_collar(slave, dressingBlock); - long_belly(slave, dressingBlock); - if (hasAnyArms(slave)) { - long_arms(slave, dressingBlock); - } - if (hasAnyLegs(slave)) { - long_legs(slave, dressingBlock); - long_shoes(slave, dressingBlock); + b.smart_fetish(slave, c); + b.smart_attraction(slave, c); + } + b.behavior_flaw(slave, c); + b.sex_flaw(slave, c); + b.behavior_quirk(slave, c); + b.sex_quirk(slave, c); + }, + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + skills: function(slave, c) { + const b = bits.short; + b.intelligence(slave, c); + b.skills(slave, c); + b.prestige(slave, c); + b.porn_prestige(slave, c); + }, + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + race: function(slave, c) { + const s = data.short.race[slave.race]; + helpers.makeSpan(c, s ? s : helpers.firstThreeUc(slave.race), "tan"); + }, + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + nationality: function(slave, c) { + let tmp = data.short.nationality[slave.nationality]; + if (!tmp && slave.nationality === "Zimbabwean") { + if (slave.race === "white") { + tmp = 'Rhodesian.'; + } else { + tmp = `${slave.nationality}.`; } - long_chastity(slave, dressingBlock); - long_vaginal_acc(slave, dressingBlock); - long_dick_acc(slave, dressingBlock); - long_buttplug(slave, dressingBlock); } - } - const RABlock = makeBlock(para); - rules_assistant(slave, RABlock); - if (V.abbreviateOrigins === 2 && slave.origin !== 0) { - origins(slave, res); - } - return res; - } + helpers.makeSpan(c, tmp ? tmp : slave.nationality.substr(0, 3), "tan"); + }, - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - */ - function short_devotion(slave, c) { - if (slave.fetish === "mindbroken") { - makeSpan(c, "MB", "mindbroken"); - } else if (slave.devotion < -95) { - makeSpan(c, "Hate", ["devotion", "hateful"], true, slave.devotion); - } else if (slave.devotion < -50) { - makeSpan(c, "Hate", ["devotion", "hateful"], true, slave.devotion); - } else if (slave.devotion < -20) { - makeSpan(c, "Res", ["devotion", "resistant"], true, slave.devotion); - } else if (slave.devotion <= 20) { - makeSpan(c, "Ambiv", ["devotion", "ambivalent"], true, slave.devotion); - } else if (slave.devotion <= 50) { - makeSpan(c, "Accept", ["devotion", "accept"], true, slave.devotion); - } else if (slave.devotion <= 95) { - makeSpan(c, "Devo", ["devotion", "devoted"], true, slave.devotion); - } else { - makeSpan(c, "Wor", ["devotion", "worship"], true, slave.devotion); - } - if (slave.fetish === "mindbroken") { - return; - } else if (slave.trust < -95) { - makeSpan(c, "ETerr", ["trust", "extremely-terrified"], true, slave.trust); - } else if (slave.trust < -50) { - makeSpan(c, "Terr", ["trust", "terrified"], true, slave.trust); - } else if (slave.trust < -20) { - makeSpan(c, "Fright", ["trust", "frightened"], true, slave.trust); - } else if (slave.trust <= 20) { - makeSpan(c, "Fear", ["trust", "fearful"], true, slave.trust); - } else if (slave.trust <= 50) { - if (slave.devotion < -20) { - makeSpan(c, "Caref", ["defiant", "careful"], true, slave.trust); - } else { - makeSpan(c, "Caref", ["trust", "careful"], true, slave.trust); - } - } else if (slave.trust < 95) { - if (slave.devotion < -20) { - makeSpan(c, "Bold", ["defiant", "bold"], true, slave.trust); - } else { - makeSpan(c, "Trust", ["trust", "trusting"], true, slave.trust); - } - } else { - if (slave.devotion < -20) { - makeSpan(c, "Defiant", ["defiant", "full"], true, slave.trust); - } else { - makeSpan(c, "VTrust", ["trust", "prof-trusting"], true, slave.trust); - } - } - } + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + skin: function(slave, c) { + const s = data.short.skin[slave.skin]; + helpers.makeSpan(c, s ? s : helpers.firstThreeUc(slave.skin)); + }, - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function long_devotion(slave, c) { - if (slave.fetish === "mindbroken") { - makeSpan(c, "Mindbroken.", "mindbroken"); - } else if (slave.devotion < -95) { - makeSpan(c, "Very hateful", ["devotion", "hateful"], true, slave.devotion); - } else if (slave.devotion < -50) { - makeSpan(c, "Hateful", ["devotion", "hateful"], true, slave.devotion); - } else if (slave.devotion < -20) { - makeSpan(c, "Resistant", ["devotion", "resistant"], true, slave.devotion); - } else if (slave.devotion <= 20) { - makeSpan(c, "Ambivalent", ["devotion", "ambivalent"], true, slave.devotion); - } else if (slave.devotion <= 50) { - makeSpan(c, "Accepting", ["devotion", "accept"], true, slave.devotion); - } else if (slave.devotion <= 95) { - makeSpan(c, "Devoted", ["devotion", "devoted"], true, slave.devotion); - } else { - makeSpan(c, "Worshipful", ["devotion", "worship"], true, slave.devotion); - } - if (slave.fetish === "mindbroken") { - return; - } else if (slave.trust < -95) { - makeSpan(c, "Extremely terrified", ["trust", "extremely-terrified"], true, slave.trust); - } else if (slave.trust < -50) { - makeSpan(c, "Terrified", ["trust", "terrified"], true, slave.trust); - } else if (slave.trust < -20) { - makeSpan(c, "Frightened", ["trust", "frightened"], true, slave.trust); - } else if (slave.trust <= 20) { - makeSpan(c, "Fearful", ["trust", "fearful"], true, slave.trust); - } else if (slave.trust <= 50) { - if (slave.devotion < -20) { - makeSpan(c, "Careful", ["defiant", "careful"], true, slave.trust); - } else { - makeSpan(c, "Careful", ["trust", "careful"], true, slave.trust); - } - } else if (slave.trust <= 95) { - if (slave.devotion < -20) { - makeSpan(c, "Bold", ["defiant", "bold"], true, slave.trust); - } else { - makeSpan(c, "Trusting", ["trust", "trusting"], true, slave.trust); - } - } else { - if (slave.devotion < -20) { - makeSpan(c, "Defiant", ["defiant", "full"], true, slave.trust); - } else { - makeSpan(c, "Profoundly trusting", ["trust", "prof-trusting"], true, slave.trust); - } - } - } + clothes: function() { }, - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function short_rules(slave, c) { - const styles = "strong"; - switch (slave.rules.living) { - case "luxurious": - makeSpan(c, "LS:Lux", styles); - break; - case "normal": - makeSpan(c, "LS:Nor", styles); - break; - default: - makeSpan(c, "LS:Spa", styles); - break; - } - if (canTalk(slave, false)) { - switch (slave.rules.speech) { - case "permissive": - makeSpan(c, "SpR:P", styles); - break; - case "accent elimination": - makeSpan(c, "SpR:NoAcc", styles); - break; - case "language lessons": - makeSpan(c, "SpR:LL", styles); - break; - default: - makeSpan(c, "SpR:R", styles); - break; + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + rulesets: function(slave, c) { + if (slave.useRulesAssistant === 0) { + helpers.makeSpan(c, "RA-Exmt", "lightgreen"); } - } - switch (slave.rules.relationship) { - case "permissive": - makeSpan(c, "ReR:P", styles); - break; - case "just friends": - makeSpan(c, "ReR:Fr", styles); - break; - default: - makeSpan(c, "ReR:R", styles); - break; - } - switch (slave.rules.punishment) { - case "confinement": - makeSpan(c, "Pun:Conf", styles); - break; - case "whipping": - makeSpan(c, "Pun:Whip", styles); - break; - case "chastity": - makeSpan(c, "Pun:Chas", styles); - break; - default: - makeSpan(c, "Pun:Situ", styles); - break; - } - switch (slave.rules.reward) { - case "relaxation": - makeSpan(c, "Rew:Relx", styles); - break; - case "drugs": - makeSpan(c, "Rew:Drug", styles); - break; - case "orgasm": - makeSpan(c, "Rew:Orga", styles); - break; - default: - makeSpan(c, "Rew:Situ", styles); - break; - } - makeSpan(c, "MaR:" + App.Utils.releaseSummaryShort(slave), styles); - } + }, - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function long_rules(slave, c) { - addText(c, `Living standard: ${slave.rules.living}. `); - if (canTalk(slave, false)) { - addText(c, `Speech rules: ${slave.rules.speech}. `); + origins: function() { }, + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + relations: function(slave, c) { + const b = bits.short; + b.family(slave, c); + b.clone(slave, c); + b.rival(slave, c); } - addText(c, `Relationship rules: ${slave.rules.relationship}. `); - addText(c, `Typical punishment: ${slave.rules.punishment}. `); - addText(c, `Typical reward: ${slave.rules.reward}. `); - addText(c, `Release rules: ${App.Utils.releaseSummaryLong(slave)}. `); - } - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function short_weight(slave, c) { - if (slave.weight < -95) { - makeSpan(c, "W---", ["red", "strong"], true, slave.weight); - } else if (slave.weight < -30) { - if (slave.hips < -1) { - makeSpan(c, "W--", "strong", true, slave.weight); - } else { - makeSpan(c, "W--", ["red", "strong>"], true, slave.weight); - } - } else if (slave.weight < -10) { - makeSpan(c, "W-", "strong", true, slave.weight); - } else if (slave.weight <= 10) { - makeSpan(c, "W", "strong", true, slave.weight); - } else if (slave.weight <= 30) { - makeSpan(c, "W+", "strong", true, slave.weight); - } else if (slave.weight <= 95) { - if (slave.hips > 1 || V.arcologies[0].FSHedonisticDecadence !== "unset") { - makeSpan(c, "W++", "strong", true, slave.weight); - } else { - makeSpan(c, "W++", ["red", "strong"], true, slave.weight); - } - } else if (slave.weight <= 130) { - if (slave.hips > 2 || V.arcologies[0].FSHedonisticDecadence !== "unset") { - makeSpan(c, "W+++", "strong", true, slave.weight); - } else { - makeSpan(c, "W+++", ["red", "strong"], true, slave.weight); + }; + + const longRenderers = { + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + devotion: function(slave, c) { + const makeSpan = helpers.makeSpan; + if (slave.fetish === "mindbroken") { + makeSpan(c, "Mindbroken.", "mindbroken"); + } else { + helpers.makeRatedStyledSpan(c, data.long.mental.devotion, slave.devotion, 100, true); + helpers.makeStyledSpan(c, helpers.getMultiNumericRating(data.long.mental.trust, [slave.trust + 100, slave.devotion + 100]), + slave.trust, true); + } + }, + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + health: function(slave, c) { + const b = bits.long; + b.health(slave, c); + b.illness(slave, c); + b.tired(slave, c); + }, + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + weight: function(slave, c) { + helpers.makeStyledSpan(c, helpers.getMultiNumericRating(data.long.body.weight, + [slave.weight + 100, helpers.FSData.policy.HedonisticDecadence.active, slave.hips + 2]), slave.weight, true); + }, + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + rules: function(slave, c) { + const addText = helpers.addText; + addText(c, `Living standard: ${slave.rules.living}. `); + if (canTalk(slave, false)) { + addText(c, `Speech rules: ${slave.rules.speech}. `); + } + addText(c, `Relationship rules: ${slave.rules.relationship}. `); + addText(c, `Typical punishment: ${slave.rules.punishment}. `); + addText(c, `Typical reward: ${slave.rules.reward}. `); + addText(c, `Release rules: ${App.Utils.releaseSummaryLong(slave)}. `); + }, + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + genitalia: function(slave, c) { + if (slave.dick > 0) { + let dickDesc = slave.balls === 0 ? "Gelded." : ""; + const dickBallsDesc = helpers.getMultiNumericRating(data.long.body.genitalia.dickBalls, [slave.dick, slave.balls]); + if (dickBallsDesc) { + dickDesc += ` ${dickBallsDesc}`; + } + if (dickDesc) { + helpers.makeSpan(c, dickDesc, "pink"); + } } - } else if (slave.weight <= 160) { - if (V.arcologies[0].FSHedonisticDecadence !== "unset") { - makeSpan(c, "W++++", "strong", true, slave.weight); - } else { - makeSpan(c, "W++++", ["red", "strong"], true, slave.weight); + if (slave.vagina === 0) { + helpers.makeSpan(c, "Virgin.", "lime"); + } else if ((slave.pregKnown === 1) && canWalk(slave) && (slave.clothes === "no clothing" || slave.clothes === "body oil") && (slave.shoes === "none")) { + helpers.makeSpan(c, "Naked, barefoot, and pregnant.", "pink"); } - } else if (slave.weight <= 190) { - if (V.arcologies[0].FSHedonisticDecadence !== "unset") { - makeSpan(c, "W+++++", "strong", true, slave.weight); - } else { - makeSpan(c, "W+++++", ["red", "strong"], true, slave.weight); + if (slave.anus === 0) { + helpers.makeSpan(c, "Anal virgin.", "lime"); } - } else { - if (V.arcologies[0].FSHedonisticDecadence !== "unset") { - makeSpan(c, "W++++++", "strong", true, slave.weight); - } else { - makeSpan(c, "W++++++", ["red", "strong"], true, slave.weight); + const holesDesc = helpers.getMultiNumericRating(data.long.body.genitalia.holes, [slave.vagina, slave.anus]); + if (holesDesc) { + helpers.makeSpan(c, holesDesc, "pink"); } - } - } + }, - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function long_weight(slave, c) { - if (slave.weight < -95) { - makeSpan(c, "Emaciated", "red", true, slave.weight); - } else if (slave.weight < -30) { - if (slave.hips < -1) { - makeSpan(c, "Model-thin", null, true, slave.weight); - } else { - makeSpan(c, "Very thin", "red", true, slave.weight); - } - } else if (slave.weight < -10) { - makeSpan(c, "Thin", null, true, slave.weight); - } else if (slave.weight <= 10) { - makeSpan(c, "Trim", null, true, slave.weight); - } else if (slave.weight <= 30) { - makeSpan(c, "Plush", null, true, slave.weight); - } else if (slave.weight <= 95) { - if (slave.hips > 1 || V.arcologies[0].FSHedonisticDecadence !== "unset") { - makeSpan(c, "Nicely chubby", null, true, slave.weight); - } else { - makeSpan(c, "Overweight", "red", true, slave.weight); + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + diet: function(slave, c) { + helpers.makeMappedSpan(c, data.long.diet, slave.diet, "teal"); + const sd = data.long.specialDiet[slave.dietCum + 3 * slave.dietMilk]; + if (sd) { + helpers.addText(c, "Diet base: "); + helpers.makeSpan(c, sd, "cyan"); + } + }, + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + drugs: function(slave, c) { + const makeSpan = helpers.makeSpan; + let swd = WombGetLittersData(slave); + if ((slave.drugs !== "no drugs") && (slave.drugs !== "none")) { + makeSpan(c, `On ${slave.drugs}.`, "tan"); + } + if (slave.curatives === 2) { + makeSpan(c, "On curatives.", "lightgreen"); + } else if (slave.curatives === 1) { + makeSpan(c, "On preventatives.", "lightgreen"); + } + if (slave.aphrodisiacs > 0) { + makeSpan(c, `On ${slave.aphrodisiacs > 1 ? 'extreme' : ''} aphrodisiacs.`, "lightblue"); + } else if (slave.aphrodisiacs === -1) { + makeSpan(c, "On anaphrodisiacs.", "lightblue"); + } + if (slave.addict !== 0) { + makeSpan(c, "Addict.", "cyan"); + } + if (slave.hormones > 1) { + makeSpan(c, "Heavy female hormones.", "lightsalmon"); + } else if (slave.hormones > 0) { + makeSpan(c, "Female hormones.", "lightsalmon"); + } else if (slave.hormones < -1) { + makeSpan(c, "Heavy male hormones.", "lightsalmon"); + } else if (slave.hormones < 0) { + makeSpan(c, "Male hormones.", "lightsalmon"); + } + let styles = "mediumseagreen"; + if ((slave.bellyImplant > -1)) { + makeSpan(c, "Belly Implant.", styles); + } else if ((slave.preg <= -2) && (slave.ovaries === 1 || slave.mpreg === 1)) { + makeSpan(c, "Barren.", styles); + } else if ((slave.ovaries === 0) && (slave.vagina !== -1) && (slave.genes === "XX")) { + makeSpan(c, "Barren.", styles); + } else if (slave.pubertyXX === 0 && (slave.ovaries === 1 || slave.mpreg === 1)) { + makeSpan(c, "Not ovulating yet.", styles); + } else if (slave.ovaryAge >= 47 && (slave.ovaries === 1 || slave.mpreg === 1)) { + makeSpan(c, "Menopausal.", styles); + } else if (slave.pregWeek < 0) { + makeSpan(c, "Postpartum.", styles); + } else if (slave.preg === -1) { + makeSpan(c, "On contraceptives.", styles); + } else if (slave.preg === 0 && (slave.ovaries === 1 || slave.mpreg === 1)) { + makeSpan(c, "Fertile.", styles); + } else if ((slave.preg >= 36) && (slave.broodmother > 0)) { + makeSpan(c, "Permanently pregnant.", styles); + } else if (swd.litters.length > 1) { + let pregTxt = `Concurrent pregnancies: (${swd.litters.length} sets).`; + pregTxt += ` Max:${swd.litters[0]} / Min:${swd.litters[swd.litters.length - 1]} week(s).`; + makeSpan(c, pregTxt, "lime"); + } else if (((slave.preg < slave.pregData.normalBirth / 10) && (slave.preg > 0) && slave.pregKnown === 0) || slave.pregWeek === 1) { + makeSpan(c, "May be pregnant."); + } else if (slave.pregKnown === 1) { + if (slave.pregType < 2 || slave.broodmother > 0) { + makeSpan(c, `${slave.pregWeek} weeks pregnant.`); + } else { + let desc = `${slave.pregWeek} weeks pregnant with `; + if (slave.pregType >= 40) { + desc += `a tremendous brood of offspring.`; + } else if (slave.pregType >= 20) { + desc += `a brood of offspring.`; + } else if (slave.pregType >= 10) { + desc += `${slave.pregType} babies.`; + } else if (slave.pregType === 9) { + desc += `nonuplets.`; + } else if (slave.pregType === 8) { + desc += `octuplets.`; + } else if (slave.pregType === 7) { + desc += `septuplets.`; + } else if (slave.pregType === 6) { + desc += `sextuplets.`; + } else if (slave.pregType === 5) { + desc += `quintuplets.`; + } else if (slave.pregType === 4) { + desc += `quadruplets.`; + } else if (slave.pregType === 3) { + desc += `triplets.`; + } else { + desc += `twins.`; + } + makeSpan(c, desc); + } + if (slave.preg > slave.pregData.normalBirth && slave.broodmother === 0) { + makeSpan(c, "(Overdue.)"); + } } - } else if (slave.weight <= 130) { - if (slave.hips > 2 || V.arcologies[0].FSHedonisticDecadence !== "unset") { - makeSpan(c, "Pleasantly soft and shapely", null, true, slave.weight); - } else { - makeSpan(c, "Fat", "red", true, slave.weight); + if (slave.induce === 1) { + makeSpan(c, "Showing signs of early labor.", "orange"); } - } else if (slave.weight <= 160) { - if (V.arcologies[0].FSHedonisticDecadence !== "unset") { - makeSpan(c, "Amazingly voluptuous", null, true, slave.weight); - } else { - makeSpan(c, "Obese", "red", true, slave.weight); + if (slave.pubertyXY === 0 && slave.balls > 0) { + makeSpan(c, "Has not had first ejaculation."); } - } else if (slave.weight <= 190) { - if (V.arcologies[0].FSHedonisticDecadence !== "unset") { - makeSpan(c, "SSBBW", null, true, slave.weight); - } else { - makeSpan(c, "Super Obese", "red", true, slave.weight); + if (slave.balls > 0 && slave.vasectomy === 1) { + makeSpan(c, "Vasectomy."); } - } else { - if (V.arcologies[0].FSHedonisticDecadence !== "unset") { - makeSpan(c, "Perfectly massive", null, true, slave.weight); - } else { - makeSpan(c, "Dangerously Obese", "red", true, slave.weight); + if (slave.inflation === 3) { + makeSpan(c, `Filled with 8 liters of ${slave.inflationType}.`, "springgreen"); + } else if (slave.inflation === 2) { + makeSpan(c, `Filled with 4 liters of ${slave.inflationType}.`, "springgreen"); + } else if (slave.inflation === 1) { + makeSpan(c, `Filled with 2 liters of ${slave.inflationType}.`, "springgreen"); + } else if (slave.bellyFluid > 0) { + makeSpan(c, `Stuffed with ${slave.bellyFluid}ccs of ${slave.inflationType}.`, "springgreen"); } - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function short_diet(slave, c) { - let diet = makeSpan(null, "", ["teal", "strong"]); - switch (slave.diet) { - case "restricted": - diet.textContent = "Di:W-"; - break; - case "fattening": - diet.textContent = "Di:W+"; - break; - case "corrective": - diet.textContent = "Di:W="; - break; - case "XX": - diet.textContent = "Di:XX+"; - break; - case "XY": - diet.textContent = "Di:XY+"; - break; - case "XXY": - diet.textContent = "Di:XXY+"; - break; - case "muscle building": - diet.textContent = "Di:M+"; - break; - case "slimming": - diet.textContent = "Di:M-"; - break; - case "cum production": - diet.textContent = "Di:C+"; - break; - case "cleansing": - diet.textContent = "Di:H+"; - break; - case "fertility": - diet.textContent = "Di:F+"; - break; - } - if (diet.textContent.length > 0) { - c.appendChild(diet); - } - let specialDiet = makeSpan(null, "", ["cyan", "strong"]); - if (slave.dietCum === 2) { - specialDiet.textContent = "Cum++"; - } else if (((slave.dietCum === 1) && (slave.dietMilk === 0))) { - specialDiet.textContent = "Cum+"; - } else if (((slave.dietCum === 1) && (slave.dietMilk === 1))) { - specialDiet.textContent = "Cum+ Milk+"; - } else if (((slave.dietCum === 0) && (slave.dietMilk === 1))) { - specialDiet.textContent = "Milk+"; - } else if ((slave.dietMilk === 2)) { - specialDiet.textContent = ">Milk++"; - } - if (specialDiet.textContent.length > 0) { - c.appendChild(specialDiet); - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function long_diet(slave, c) { - let dietDesc = ""; - switch (slave.diet) { - case "restricted": - dietDesc = `Dieting.`; - break; - case "fattening": - dietDesc = `Gaining weight.`; - break; - case "corrective": - dietDesc = `Corrective.`; - break; - case "XX": - dietDesc = `Estrogen rich.`; - break; - case "XY": - dietDesc = `Testosterone rich.`; - break; - case "XXY": - dietDesc = `Futanari mix.`; - break; - case "muscle building": - dietDesc = `Pumping iron.`; - break; - case "slimming": - dietDesc = `Slimming down.`; - break; - case "cum production": - dietDesc = `Cum production.`; - break; - case "cleansing": - dietDesc = `Cleansing.`; - break; - case "fertility": - dietDesc = `Fertility.`; - break; - } - - if (dietDesc) { - makeSpan(c, dietDesc, "teal"); - } - - function specialDiet(text) { - addText(c, "Diet base: "); - makeSpan(c, text, "cyan"); - } - - if (slave.dietCum === 2) { - specialDiet("Cum Based."); - } else if (((slave.dietCum === 1) && (slave.dietMilk === 0))) { - specialDiet("Cum Added."); - } else if (((slave.dietCum === 1) && (slave.dietMilk === 1))) { - specialDiet("Milk & Cum Added."); - } else if (((slave.dietCum === 0) && (slave.dietMilk === 1))) { - specialDiet("Milk Added."); - } else if ((slave.dietMilk === 2)) { - specialDiet("Milk Based."); - } - } + }, - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function short_health(slave, c) { - if (slave.health.health < -20) { - makeSpan(c, "H", ["red", "strong"], true, slave.health.health); - } else if (slave.health.health <= 20) { - makeSpan(c, "H", ["yellow", "strong"], true, slave.health.health); - } else if (slave.health.health > 20) { - makeSpan(c, "H", ["green", "strong"], true, slave.health.health); - } - if (passage() === "Clinic" && V.clinicUpgradeScanner) { - if (slave.chem > 15) { - makeSpan(c, `C${Math.ceil(slave.chem/10)}`, ["cyan", "strong"]); - } else if (slave.chem <= 15 && slave.assignment === "get treatment in the clinic") { - makeSpan(c, `CSafe`, ["green", "strong"]); + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + physicals: function(slave, c) { + const b = bits.long; + b.age(slave, c); + b.face(slave, c); + b.eyes(slave, c); + b.ears(slave, c); + b.lips(slave, c); + b.teeth(slave, c); + b.muscles(slave, c); + helpers.makeSpan(c, App.Desc.longLimbs(slave)); + b.voice(slave, c); + b.tits_ass(slave, c); + b.hips(slave, c); + b.waist(slave, c); + b.implants(slave, c); + b.lactation(slave, c); + b.mods(slave, c); + if (!jQuery.isEmptyObject(slave.brand)) { + helpers.makeSpan(c, 'Branded.'); } - } - } + }, - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function long_health(slave, c) { - if (slave.health.health < -90) { - makeSpan(c, "On the edge of death", ["red", "strong"], true, slave.health.health); - } else if (slave.health.health < -50) { - makeSpan(c, "Extremely unhealthy", ["red", "strong"], true, slave.health.health); - } else if (slave.health.health < -20) { - makeSpan(c, "Unhealthy", ["red", "strong"], true, slave.health.health); - } else if (slave.health.health <= 20) { - makeSpan(c, "healthy", "yellow", true, slave.health.health); - } else if (slave.health.health <= 50) { - makeSpan(c, "Very healthy", "green", true, slave.health.health); - } else if (slave.health.health <= 90) { - makeSpan(c, "Extremely healthy", "green", true, slave.health.health); - } else { - makeSpan(c, "Unnaturally healthy", "green", true, slave.health.health); - } - if (passage() === "Clinic" && V.clinicUpgradeScanner) { - if (slave.chem > 15) { - makeSpan(c, `Carcinogen buildup: ${Math.ceil(slave.chem/10)}.`, "cyan"); - } else if (slave.chem <= 15 && slave.assignment === "get treatment in the clinic") { - makeSpan(c, `Safe chem levels.`, "green"); + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + hormoneBalance: function(slave, c) { + const colorClass = slave.hormoneBalance <= -21 ? "deepskyblue" : "pink"; + const desc = helpers.getNumericRating(data.long.hormoneBalance, slave.hormoneBalance + 500); + helpers.makeSpan(c, desc + " hormone balance.", colorClass); + }, + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + skills: function(slave, c) { + const b = bits.long; + b.intelligence(slave, c); + b.skills(slave, c); + b.prestige(slave, c); + b.porn_prestige(slave, c); + }, + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + clothes: function(slave, c) { + const b = bits.long; + const dressingBlock = helpers.makeBlock(c); + if (slave.choosesOwnClothes === 1) { + helpers.makeSpan(dressingBlock, `Dressing ${getPronouns(slave).himself}.`); + } + b.clothes(slave, dressingBlock); + b.collar(slave, dressingBlock); + b.belly(slave, dressingBlock); + if (hasAnyArms(slave)) { + b.arms(slave, dressingBlock); + } + if (hasAnyLegs(slave)) { + b.legs(slave, dressingBlock); + b.shoes(slave, dressingBlock); + } + b.chastity(slave, dressingBlock); + b.vaginal_acc(slave, dressingBlock); + b.dick_acc(slave, dressingBlock); + b.buttplug(slave, dressingBlock); + }, + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + mental: function(slave, c) { + const b = bits.long; + if (slave.fetish !== "mindbroken") { + if (slave.fetishKnown === 1) { + b.fetish(slave, c); + } + if (slave.attrKnown === 1) { + b.attraction(slave, c); + } + } + if (slave.clitPiercing === 3) { + b.smart_fetish(slave, c); + b.smart_attraction(slave, c); + } + b.behavior_flaw(slave, c); + b.sex_flaw(slave, c); + b.behavior_quirk(slave, c); + b.sex_quirk(slave, c); + }, + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + race: function(slave, c) { + let raceStr = App.Data.SlaveSummary.long.race[slave.race]; + if (!raceStr) { + raceStr = capFirstChar(slave.race); + } + helpers.makeSpan(c, raceStr + '.', "tan"); + }, + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + nationality: function(slave, c) { + function descStr(slave) { + switch (slave.nationality) { + case "a Cook Islander": + return `Cook Islander.`; + case "a Liechtensteiner": + return `Liechtensteiner.`; + case "a New Zealander": + return `New Zealander.`; + case "a Solomon Islander": + return `Solomon Islander.`; + case "Zimbabwean": + if (slave.race === "white") { + return `Rhodesian.`; + } else { + return `${slave.nationality}.`; + } + case "slave": + case "none": + case "": + case "Stateless": + return `Stateless.`; + default: + return `${slave.nationality}.`; + } } - } - } - - function short_illness(slave, c) { - if (slave.health.illness > 4) { - makeSpan(c, `Ill${slave.health.illness}`, ["red", "strong"], true, slave.health.illness); - } else if (slave.health.illness > 3) { - makeSpan(c, `Ill${slave.health.illness}`, ["red", "strong"], true, slave.health.illness); - } else if (slave.health.illness > 2) { - makeSpan(c, `Ill${slave.health.illness}`, ["red", "strong"], true, slave.health.illness); - } else if (slave.health.illness > 0) { - makeSpan(c, `Ill${slave.health.illness}`, ["yellow", "strong"], true, slave.health.illness); - } - } - - function long_illness(slave, c) { - if (slave.health.illness > 4) { - makeSpan(c, "Terribly ill", ["red", "strong"], true, slave.health.illness); - } else if (slave.health.illness > 3) { - makeSpan(c, "Very ill", ["red", "strong"], true, slave.health.illness); - } else if (slave.health.illness > 2) { - makeSpan(c, "Ill", ["red", "strong"], true, slave.health.illness); - } else if (slave.health.illness > 0) { - makeSpan(c, "Sick", "yellow", true, slave.health.illness); - } - } - function short_tired(slave, c) { - if (slave.health.tired > 90) { - makeSpan(c, "Exh", ["red", "strong"], true, slave.health.tired); - } else if (slave.health.tired > 60) { - makeSpan(c, "Tir+", "orange", true, slave.health.tired); - } else if (slave.health.tired > 30) { - makeSpan(c, "Tir", "yellow", true, slave.health.tired); - } else if (slave.health.tired < 0) { - makeSpan(c, "Ene", "green", true, slave.health.tired); - } - } + helpers.makeSpan(c, descStr(slave), "tan"); + }, - function long_tired(slave, c) { - if (slave.health.tired > 90) { - makeSpan(c, "Exhausted", ["red", "strong"], true, slave.health.tired); - } else if (slave.health.tired > 60) { - makeSpan(c, "Fatigued", "orange", true, slave.health.tired); - } else if (slave.health.tired > 30) { - makeSpan(c, "Tired", "yellow", true, slave.health.tired); - } else if (slave.health.tired < 0) { - makeSpan(c, "Energetic", "green", true, slave.health.tired); - } - } + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + skin: function(slave, c) { + helpers.makeSpan(c, `${capFirstChar(slave.skin)} skin.`); + }, + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + rulesets: function(slave, c) { + if (slave.useRulesAssistant === 0) { + helpers.makeSpan(c, "RA-Exempt", "lightgreen"); + } else if ((slave.currentRules !== undefined) && (slave.currentRules.length > 0)) { + c.innerHTML = `Rules: ${V.defaultRules.filter(x => ruleApplied(slave, x)).map(x => x.name).join(", ")}`; + } + }, + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + origins: function(slave, c) { + const para = helpers.makeParagraph(c); + para.classList.add("gray"); + const pronouns = getPronouns(slave); + para.textContent = `${slave.origin.replace(/\$([A-Z]?[a-z]+)/g, (match, cap1) => pronouns[cap1] || match)}`; + }, + + /** + * @param {App.Entity.SlaveState} slave + * @param {Node} c + * @returns {void} + */ + relations: function(slave, c) { + const b = bits.long; + b.family(slave, c); + b.clone(slave, c); + b.rival(slave, c); + } + }; + + return { + short: shortRenderers, + long: longRenderers, + empty: function(){} + }; +}(); + +App.UI.SlaveSummary = function() { + const emptyRenderer = (/** @type {App.Entity.SlaveState} */ slave, /** @type {Node} */ c) => { }; + const delegates = { + clothes: emptyRenderer, + devotion: emptyRenderer, + rules: emptyRenderer, + height: emptyRenderer, + diet: emptyRenderer, + health: emptyRenderer, + drugs: emptyRenderer, + race: emptyRenderer, + nationality: emptyRenderer, + genitalia: emptyRenderer, + physicals: emptyRenderer, + hormoneBalance: emptyRenderer, + skills: emptyRenderer, + mental: emptyRenderer, + weight: emptyRenderer, + skin: emptyRenderer, + origins: emptyRenderer, + rulesets: emptyRenderer, + relations: emptyRenderer + }; + + /** + * @returns {App.UI.SlaveSummary.State} + */ + function makeNewState() { + return { + abbreviation: { + clothes: 2, + devotion: 2, + diet: 2, + drugs: 2, + genitalia: 2, + health: 2, + mental: 2, + nationality: 2, + origins: 2, + physicals: 2, + race: 2, + rules: 2, + rulesets: 2, + skills: 2, + } + }; + } + + function delegateForSetting(name, setting) { + switch (setting) { + case 0: return App.UI.SlaveSummaryRenderers.empty; + case 1: return App.UI.SlaveSummaryRenderers.short[name]; + case 2: return App.UI.SlaveSummaryRenderers.long[name]; + } + } + + function initDelegates(settingsObj) { + try { + settingsObj = settingsObj || V.UI.slaveSummary; + /** @type {App.UI.SlaveSummary.AbbreviationState} */ + const abbrSettings = settingsObj.abbreviation; + for (const setting in abbrSettings) { + delegates[setting] = delegateForSetting(setting, abbrSettings[setting]); + } + delegates.weight = delegateForSetting("weight", abbrSettings.diet); + delegates.skin = delegateForSetting("skin", abbrSettings.physicals); + delegates.relations = delegateForSetting("relations", abbrSettings.mental); + // special settings + if (!V.seeRace) { + delegates.race = emptyRenderer; + } + } catch (ex){ + console.error(ex); + } + } + + function settingsChanged(newState) { + try { + const newStateIsOK = newState && newState.hasOwnProperty("UI") && newState.UI.hasOwnProperty("slaveSummary"); + const settingsObj = newStateIsOK ? newState.UI.slaveSummary : V.UI.slaveSummary; + + initDelegates(settingsObj); + App.UI.SlaveSummaryImpl.helpers.syncFSData(newState ? newState.arcologies[0] : V.arcologies[0]); + } catch (ex) { + console.error(ex); + } + } + + function societyChanged(arcology) { + App.UI.SlaveSummaryImpl.helpers.syncFSData(arcology); + } + + /** + * @param {App.Entity.SlaveState} slave + * @returns {DocumentFragment} + */ + function render(slave) { + /** @type {App.UI.SlaveSummary.AbbreviationState} */ + const abbrSettings = V.UI.slaveSummary.abbreviation; + const helpers = App.UI.SlaveSummaryImpl.helpers; + + const res = document.createDocumentFragment(); + let para = helpers.makeParagraph(res); - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function short_drugs(slave, c) { - let drugDesc = ""; - switch (slave.drugs) { - case "breast injections": - drugDesc = "Boobs+"; - break; - case "intensive breast injections": - drugDesc = "Boobs++"; - break; - case "hyper breast injections": - drugDesc = "Boobs+++"; - break; - case "nipple enhancers": - drugDesc = "Nipple+"; - break; - case "butt injections": - drugDesc = "Butt+"; - break; - case "intensive butt injections": - drugDesc = "Butt++"; - break; - case "hyper butt injections": - drugDesc = "Butt+++"; - break; - case "lip injections": - drugDesc = "Lip+"; - break; - case "fertility drugs": - drugDesc = "Fert+"; - break; - case "super fertility drugs": - drugDesc = "Fert++"; - break; - case "penis enhancement": - drugDesc = "Dick+"; - break; - case "intensive penis enhancement": - drugDesc = "Dick++"; - break; - case "hyper penis enhancement": - drugDesc = "Dick+++"; - break; - case "testicle enhancement": - drugDesc = "Balls+"; - break; - case "intensive testicle enhancement": - drugDesc = "Balls++"; - break; - case "hyper testicle enhancement": - drugDesc = "Balls+++"; - break; - case "psychosuppressants": - drugDesc = "Psych-"; - break; - case "psychostimulants": - drugDesc = "Psych+"; - break; - case "steroids": - drugDesc = "Ster"; - break; - case "female hormone injections": - drugDesc = "HormXX++"; - break; - case "male hormone injections": - drugDesc = "HormXY++"; - break; - case "hormone enhancers": - drugDesc = "Horm+"; - break; - case "hormone blockers": - drugDesc = "Horm-"; - break; - case "anti-aging cream": - drugDesc = "Age-"; - break; - case "appetite suppressors": - drugDesc = "ApSup"; - break; - case "penis atrophiers": - drugDesc = "Dick-"; - break; - case "testicle atrophiers": - drugDesc = "Balls-"; - break; - case "clitoris atrophiers": - drugDesc = "Clit-"; - break; - case "labia atrophiers": - drugDesc = "Labia-"; - break; - case "nipple atrophiers": - drugDesc = "Nipple-"; - break; - case "lip atrophiers": - drugDesc = "Lip-"; - break; - case "breast redistributors": - drugDesc = "Breast-"; - break; - case "butt redistributors": - drugDesc = "Butt-"; - break; - case "sag-B-gone": - drugDesc = "AntiSag"; - break; - case "growth stimulants": - drugDesc = "GroStim"; - break; - case "priapism agents": - drugDesc = "Erection"; - break; - } - if (drugDesc) { - makeSpan(c, "Dr:" + drugDesc, ["tan", "strong"]); - } - if (slave.curatives === 2) { - makeSpan(c, "Cura", ["lightgreen", "strong"]); - } else if (slave.curatives === 1) { - makeSpan(c, "Prev", ["lightgreen", "strong"]); - } - if (slave.aphrodisiacs !== 0) { - if (slave.aphrodisiacs === 1) { - makeSpan(c, "Aph", ["lightblue", "strong"]); - } else if (slave.aphrodisiacs === 2) { - makeSpan(c, "Aph++", ["lightblue", "strong"]); - } else { - makeSpan(c, "Anaph", ["lightblue", "strong"]); - } - } - if (slave.addict !== 0) { - makeSpan(c, "Add", "cyan"); - } - let styles = ["lightsalmon", "strong"]; - if (slave.hormones > 1) { - makeSpan(c, "Ho:F+", styles); - } else if (slave.hormones > 0) { - makeSpan(c, "Ho:F", styles); - } else if (slave.hormones < -1) { - makeSpan(c, "Ho:M+", styles); - } else if (slave.hormones < 0) { - makeSpan(c, "Ho:M", styles); + delegates.devotion(slave, para); + if (!slave.fuckdoll) { + delegates.rules(slave, para); } + delegates.weight(slave, para); + delegates.diet(slave, para); + delegates.health(slave, para); + delegates.drugs(slave, para); - styles = ["mediumseagreen", "strong"]; - if ((slave.bellyImplant > -1)) { - makeSpan(c, "Belly Imp", styles); - } else if (((slave.preg <= -2) || (slave.ovaries === 0)) && (slave.vagina !== -1)) { - makeSpan(c, "Barr", styles); - } else if (slave.pubertyXX === 0 && (slave.ovaries === 1 || slave.mpreg === 1)) { - makeSpan(c, "Prepub", styles); - } else if (slave.ovaryAge >= 47 && (slave.ovaries === 1 || slave.mpreg === 1)) { - makeSpan(c, "Meno", styles); - } else if (slave.pregWeek < 0) { - makeSpan(c, "Postpartum", styles); - } else if (slave.preg === -1) { - makeSpan(c, "CC", styles); - } else if (slave.preg === 0 && (slave.ovaries === 1 || slave.mpreg === 1)) { - makeSpan(c, "Fert+", styles); - } else if (((slave.preg < slave.pregData.normalBirth / 10) && (slave.preg > 0) && slave.pregKnown === 0) || slave.pregWeek === 1) { - makeSpan(c, "Preg?", styles); - } else if ((slave.preg >= 36) && (slave.broodmother > 0)) { - makeSpan(c, "Perm preg", styles); - } else if (slave.pregKnown === 1) { - makeSpan(c, `${slave.pregWeek} wks preg`, styles); - } - if (slave.induce === 1) { - makeSpan(c, "Early Labor", ["orange", "strong"]); + para = helpers.makeParagraph(res); + para.classList.add("pink"); + helpers.makeSpan(para, `${capFirstChar(SlaveTitle(slave))}${abbrSettings.physicals === 2 ? '.' : ''}`, ["coral", "strong"]); + delegates.race(slave, para); + delegates.nationality(slave, para); + delegates.skin(slave, para); + delegates.genitalia(slave, para); + delegates.physicals(slave, para); + delegates.hormoneBalance(slave, para); + + para = helpers.makeParagraph(res); + delegates.skills(slave, para); + delegates.mental(slave, para); + if (slave.custom.label) { + helpers.makeSpan(res, `${capFirstChar(slave.custom.label)}.`, ["yellow", "strong"]); } - if (slave.pubertyXY === 0 && slave.balls > 0) { - makeSpan(c, "Prepub balls", "strong"); + if ((slave.relationship !== 0) || (slave.relation !== 0) || (abbrSettings.clothes === 2) || (abbrSettings.rulesets === 2)) { + para = helpers.makeParagraph(res); } - if (slave.balls > 0 && slave.vasectomy === 1) { - makeSpan(c, "Vasect", "strong"); + delegates.relations(slave, para); + if (!slave.fuckdoll) { + delegates.clothes(slave, para); } - styles = ["springgreen", "strong"]; - if (slave.inflation === 3) { - makeSpan(c, `8 ltr ${slave.inflationType}`, styles); - } else if (slave.inflation === 2) { - makeSpan(c, `4 ltr ${slave.inflationType}`, styles); - } else if (slave.inflation === 1) { - makeSpan(c, `2 ltr ${slave.inflationType}`, styles); - } else if (slave.bellyFluid > 0) { - makeSpan(c, `${slave.bellyFluid}ccs ${slave.inflationType}`, styles); + const RABlock = helpers.makeBlock(para); + delegates.rulesets(slave, RABlock); + if (slave.origin !== 0) { + delegates.origins(slave, res); } + return res; } - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function long_drugs(slave, c) { - let swd = WombGetLittersData(slave); - if ((slave.drugs !== "no drugs") && (slave.drugs !== "none")) { - makeSpan(c, `On ${slave.drugs}.`, "tan"); - } - if (slave.curatives === 2) { - makeSpan(c, "On curatives.", "lightgreen"); - } else if (slave.curatives === 1) { - makeSpan(c, "On preventatives.", "lightgreen"); - } - if (slave.aphrodisiacs > 0) { - makeSpan(c, `On ${slave.aphrodisiacs > 1 ? 'extreme' : ''} aphrodisiacs.`, "lightblue"); - } else if (slave.aphrodisiacs === -1) { - makeSpan(c, "On anaphrodisiacs.", "lightblue"); - } - if (slave.addict !== 0) { - makeSpan(c, "Addict.", "cyan"); - } - if (slave.hormones > 1) { - makeSpan(c, "Heavy female hormones.", "lightsalmon"); - } else if (slave.hormones > 0) { - makeSpan(c, "Female hormones.", "lightsalmon"); - } else if (slave.hormones < -1) { - makeSpan(c, "Heavy male hormones.", "lightsalmon"); - } else if (slave.hormones < 0) { - makeSpan(c, "Male hormones.", "lightsalmon"); - } - let styles = "mediumseagreen"; - if ((slave.bellyImplant > -1)) { - makeSpan(c, "Belly Implant.", styles); - } else if ((slave.preg <= -2) && (slave.ovaries === 1 || slave.mpreg === 1)) { - makeSpan(c, "Barren.", styles); - } else if ((slave.ovaries === 0) && (slave.vagina !== -1) && (slave.genes === "XX")) { - makeSpan(c, "Barren.", styles); - } else if (slave.pubertyXX === 0 && (slave.ovaries === 1 || slave.mpreg === 1)) { - makeSpan(c, "Not ovulating yet.", styles); - } else if (slave.ovaryAge >= 47 && (slave.ovaries === 1 || slave.mpreg === 1)) { - makeSpan(c, "Menopausal.", styles); - } else if (slave.pregWeek < 0) { - makeSpan(c, "Postpartum.", styles); - } else if (slave.preg === -1) { - makeSpan(c, "On contraceptives.", styles); - } else if (slave.preg === 0 && (slave.ovaries === 1 || slave.mpreg === 1)) { - makeSpan(c, "Fertile.", styles); - } else if ((slave.preg >= 36) && (slave.broodmother > 0)) { - makeSpan(c, "Permanently pregnant.", styles); - } else if (swd.litters.length > 1) { - let pregTxt = `Concurrent pregnancies: (${swd.litters.length} sets).`; - pregTxt += ` Max:${swd.litters[0]} / Min:${swd.litters[swd.litters.length-1]} week(s).`; - makeSpan(c, pregTxt, "lime"); - } else if (((slave.preg < slave.pregData.normalBirth / 10) && (slave.preg > 0) && slave.pregKnown === 0) || slave.pregWeek === 1) { - makeSpan(c, "May be pregnant."); - } else if (slave.pregKnown === 1) { - if (slave.pregType < 2 || slave.broodmother > 0) { - makeSpan(c, `${slave.pregWeek} weeks pregnant.`); - } else { - let desc = `${slave.pregWeek} weeks pregnant with `; - if (slave.pregType >= 40) { - desc += `a tremendous brood of offspring.`; - } else if (slave.pregType >= 20) { - desc += `a brood of offspring.`; - } else if (slave.pregType >= 10) { - desc += `${slave.pregType} babies.`; - } else if (slave.pregType === 9) { - desc += `nonuplets.`; - } else if (slave.pregType === 8) { - desc += `octuplets.`; - } else if (slave.pregType === 7) { - desc += `septuplets.`; - } else if (slave.pregType === 6) { - desc += `sextuplets.`; - } else if (slave.pregType === 5) { - desc += `quintuplets.`; - } else if (slave.pregType === 4) { - desc += `quadruplets.`; - } else if (slave.pregType === 3) { - desc += `triplets.`; - } else { - desc += `twins.`; - } - makeSpan(c, desc); - } - if (slave.preg > slave.pregData.normalBirth && slave.broodmother === 0) { - makeSpan(c, "(Overdue.)"); - } - } - if (slave.induce === 1) { - makeSpan(c, "Showing signs of early labor.", "orange"); - } - if (slave.pubertyXY === 0 && slave.balls > 0) { - makeSpan(c, "Has not had first ejaculation."); - } - if (slave.balls > 0 && slave.vasectomy === 1) { - makeSpan(c, "Vasectomy."); - } - if (slave.inflation === 3) { - makeSpan(c, `Filled with 8 liters of ${slave.inflationType}.`, "springgreen"); - } else if (slave.inflation === 2) { - makeSpan(c, `Filled with 4 liters of ${slave.inflationType}.`, "springgreen"); - } else if (slave.inflation === 1) { - makeSpan(c, `Filled with 2 liters of ${slave.inflationType}.`, "springgreen"); - } else if (slave.bellyFluid > 0) { - makeSpan(c, `Stuffed with ${slave.bellyFluid}ccs of ${slave.inflationType}.`, "springgreen"); - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @returns {string} - */ - function long_race(slave) { - switch (slave.race) { - case "white": - return `Caucasian.`; - case "asian": - return `Asian.`; - case "indo-aryan": - return `Indo-aryan.`; - case "latina": - return `Latina.`; - case "middle eastern": - return `Middle Eastern.`; - case "black": - return `Black.`; - case "pacific islander": - return `Pacific Islander.`; - case "malay": - return `Malay.`; - case "amerindian": - return `Amerindian.`; - case "semitic": - return `Semitic.`; - case "southern european": - return `Southern European.`; - case "mixed race": - return `Mixed race.`; - default: - return `${slave.race.charAt(0).toUpperCase() + slave.race.slice(1)}.`; - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @returns {string} - */ - function short_race(slave) { - switch (slave.race) { - case "white": - return `C`; - case "asian": - return `A`; - case "indo-aryan": - return `I`; - case "latina": - return `L`; - case "middle eastern": - return `ME`; - case "black": - return `B`; - case "pacific islander": - return `PI`; - case "malay": - return `M`; - case "amerindian": - return `AI`; - case "semitic": - return `S`; - case "southern european": - return `SE`; - case "mixed race": - return `MR`; - default: - return `${slave.race.charAt(0).toUpperCase() + slave.race.charAt(1) + slave.race.charAt(2)}`; - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @returns {string} - */ - function short_nationality(slave) { - switch (slave.nationality) { - case "Afghan": - return "Afg"; - case "Albanian": - return "Alb"; - case "Algerian": - return "Alg"; - case "American": - return "USA"; - case "Andorran": - return "And"; - case "Angolan": - return "Ang"; - case "Antiguan": - return "AB"; - case "Argentinian": - return "Arg"; - case "Armenian": - return "Arm"; - case "Aruban": - return "Aru"; - case "Australian": - return "Aus"; - case "Austrian": - return "Aut"; - case "Azerbaijani": - return "Aze"; - case "Bahamian": - return "Bah"; - case "Bahraini": - return "Bah"; - case "Bangladeshi": - return "Bgd"; - case "Barbadian": - return "Bar"; - case "Belarusian": - return "Ber"; - case "Belgian": - return "Bel"; - case "Belizean": - return "Blz"; - case "Beninese": - return "Ben"; - case "Bermudian": - return "Bmd"; - case "Bhutanese": - return "Bhu"; - case "Bissau-Guinean": - return "GB"; - case "Bolivian": - return "Bol"; - case "Bosnian": - return "Bos"; - case "Brazilian": - return "Bra"; - case "British": - return "UK"; - case "Bruneian": - return "Bru"; - case "Bulgarian": - return "Bul"; - case "Burkinabé": - return "BF"; - case "Burmese": - return "Bur"; - case "Burundian": - return "Bnd"; - case "Cambodian": - return "Kam"; - case "Cameroonian": - return "Cam"; - case "Canadian": - return "Can"; - case "Cape Verdean": - return "CV"; - case "Catalan": - return "Cat"; - case "Central African": - return "CAR"; - case "Chadian": - return "Cha"; - case "Chilean": - return "Chl"; - case "Chinese": - return "Chi"; - case "Colombian": - return "Col"; - case "Comorian": - return "Com"; - case "Congolese": - return "RC"; - case "a Cook Islander": - return "CI"; - case "Costa Rican": - return "CR"; - case "Croatian": - return "Cro"; - case "Cuban": - return "Cub"; - case "Curaçaoan": - return "Cur"; - case "Cypriot": - return "Cyp"; - case "Czech": - return "Cze"; - case "Danish": - return "Den"; - case "Djiboutian": - return "Dji"; - case "Dominican": - return "DR"; - case "Dominiquais": - return "Dom"; - case "Dutch": - return "Nld"; - case "East Timorese": - return "ET"; - case "Ecuadorian": - return "Ecu"; - case "Egyptian": - return "Egy"; - case "Emirati": - return "UAE"; - case "Equatoguinean": - return "EG"; - case "Eritrean": - return "Eri"; - case "Estonian": - return "Est"; - case "Ethiopian": - return "Eth"; - case "Fijian": - return "Fij"; - case "Filipina": - return "Phl"; - case "Finnish": - return "Fin"; - case "French": - return "Fra"; - case "French Guianan": - return "FG"; - case "French Polynesian": - return "FP"; - case "Gabonese": - return "Gab"; - case "Gambian": - return "Gam"; - case "Georgian": - return "Geo"; - case "German": - return "Ger"; - case "Ghanan": - return "Gha"; - case "Greek": - return "Gre"; - case "Greenlandic": - return "Grn"; - case "Grenadian": - return "Gda"; - case "Guamanian": - return "Gua"; - case "Guatemalan": - return "Gtm"; - case "Guinean": - return "Gui"; - case "Guyanese": - return "Guy"; - case "Haitian": - return "Hai"; - case "Honduran": - return "Hon"; - case "Hungarian": - return "Hun"; - case "I-Kiribati": - return "Kir"; - case "Icelandic": - return "Ice"; - case "Indian": - return "Ind"; - case "Indonesian": - return "Idn"; - case "Iranian": - return "Irn"; - case "Iraqi": - return "Irq"; - case "Irish": - return "Irl"; - case "Israeli": - return "Isr"; - case "Italian": - return "Ita"; - case "Ivorian": - return "IC"; - case "Jamaican": - return "Jam"; - case "Japanese": - return "Jpn"; - case "Jordanian": - return "Jor"; - case "Kazakh": - return "Kaz"; - case "Kenyan": - return "Ken"; - case "Kittitian": - return "SKN"; - case "Korean": - return "Kor"; - case "Kosovan": - return "Kos"; - case "Kurdish": - return "Kur"; - case "Kuwaiti": - return "Kuw"; - case "Kyrgyz": - return "Kyr"; - case "Laotian": - return "Lao"; - case "Latvian": - return "Lat"; - case "Lebanese": - return "Lbn"; - case "Liberian": - return "Lib"; - case "Libyan": - return "Lby"; - case "a Liechtensteiner": - return "Lie"; - case "Lithuanian": - return "Lit"; - case "Luxembourgian": - return "Lux"; - case "Macedonian": - return "Mac"; - case "Malagasy": - return "Mad"; - case "Malawian": - return "Mwi"; - case "Malaysian": - return "Mys"; - case "Maldivian": - return "Mdv"; - case "Malian": - return "Mal"; - case "Maltese": - return "Mlt"; - case "Marshallese": - return "MI"; - case "Mauritanian": - return "Mta"; - case "Mauritian": - return "Mts"; - case "Mexican": - return "Mex"; - case "Micronesian": - return "FSM"; - case "Moldovan": - return "Mol"; - case "Monégasque": - return "Mnc"; - case "Mongolian": - return "Mon"; - case "Montenegrin": - return "Mng"; - case "Moroccan": - return "Mor"; - case "Mosotho": - return "Les"; - case "Motswana": - return "Bot"; - case "Mozambican": - return "Moz"; - case "Namibian": - return "Nam"; - case "Nauruan": - return "Nau"; - case "Nepalese": - return "Npl"; - case "New Caledonian": - return "NC"; - case "a New Zealander": - return "NZ"; - case "Ni-Vanuatu": - return "Van"; - case "Nicaraguan": - return "Nic"; - case "Nigerian": - return "Nga"; - case "Nigerien": - return "Ngr"; - case "Niuean": - return "Niu"; - case "Norwegian": - return "Nor"; - case "Omani": - return "Omn"; - case "Pakistani": - return "Pak"; - case "Palauan": - return "Plu"; - case "Palestinian": - return "Pal"; - case "Panamanian": - return "Pan"; - case "Papua New Guinean": - return "PNG"; - case "Paraguayan": - return "Par"; - case "Peruvian": - return "Per"; - case "Polish": - return "Pol"; - case "Portuguese": - return "Por"; - case "Puerto Rican": - return "PR"; - case "Qatari": - return "Qat"; - case "Romanian": - return "Rom"; - case "Russian": - return "Rus"; - case "Rwandan": - return "Rwa"; - case "Sahrawi": - return "Sah"; - case "Saint Lucian": - return "SL"; - case "Salvadoran": - return "ES"; - case "Sammarinese": - return "SM"; - case "Samoan": - return "Sam"; - case "São Toméan": - return "STP"; - case "Saudi": - return "Sau"; - case "Scottish": - return "Sco"; - case "Senegalese": - return "Sen"; - case "Serbian": - return "Srb"; - case "Seychellois": - return "Sey"; - case "Sierra Leonean": - return "Sie"; - case "Singaporean": - return "Sng"; - case "Slovak": - return "Svk"; - case "Slovene": - return "Svn"; - case "a Solomon Islander": - return "SI"; - case "Somali": - return "Som"; - case "South African": - return "RSA"; - case "South Sudanese": - return "SS"; - case "Spanish": - return "Spa"; - case "Sri Lankan": - return "Sri"; - case "Sudanese": - return "Sud"; - case "Surinamese": - return "Sur"; - case "Swazi": - return "Swa"; - case "Swedish": - return "Swe"; - case "Swiss": - return "Swi"; - case "Syrian": - return "Syr"; - case "Taiwanese": - return "Tai"; - case "Tajik": - return "Taj"; - case "Tanzanian": - return "Tza"; - case "Thai": - return "Tha"; - case "Tibetan": - return "Tib"; - case "Togolese": - return "Tog"; - case "Tongan": - return "Ton"; - case "Trinidadian": - return "TT"; - case "Tunisian": - return "Tun"; - case "Turkish": - return "Tur"; - case "Turkmen": - return "Tkm"; - case "Tuvaluan": - return "Tuv"; - case "Ugandan": - return "Uga"; - case "Ukrainian": - return "Ukr"; - case "Uruguayan": - return "Uru"; - case "Uzbek": - return "Uzb"; - case "Vatican": - return "VC"; - case "Venezuelan": - return "Ven"; - case "Vietnamese": - return "Vnm"; - case "Vincentian": - return "SVG"; - case "Yemeni": - return "Yem"; - case "Zairian": - return "DRC"; - case "Zambian": - return "Zam"; - case "Zimbabwean": - if (slave.race === "white") { - return `Rho`; - } else { - return `Zwe`; - } - case "Ancient Chinese Revivalist": - return `Chi Rev`; - case "Ancient Egyptian Revivalist": - return `Egy Rev`; - case "Arabian Revivalist": - return `Ara Rev`; - case "Aztec Revivalist": - return `Azt Rev`; - case "Edo Revivalist": - return `Edo Rev`; - case "Roman Revivalist": - return `Rom Rev`; - case "": - case "none": - case "slave": - case "Stateless": - return "None"; - default: - return `${slave.nationality.charAt(0) + slave.nationality.charAt(1) + slave.nationality.charAt(2)}`; - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @returns {string} - */ - function long_nationality(slave) { - switch (slave.nationality) { - case "a Cook Islander": - return `Cook Islander.`; - case "a Liechtensteiner": - return `Liechtensteiner.`; - case "a New Zealander": - return `New Zealander.`; - case "a Solomon Islander": - return `Solomon Islander.`; - case "Zimbabwean": - if (slave.race === "white") { - return `Rhodesian.`; - } else { - return `${slave.nationality}.`; - } - case "slave": - case "none": - case "": - case "Stateless": - return `Stateless.`; - default: - return `${slave.nationality}.`; - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @returns {string} - */ - function short_skin(slave) { - switch (slave.skin) { - case "pure white": - return `P. Whi`; - case "extremely fair": - return `E. Fai`; - case "very fair": - return `V. Fai`; - case "extremely pale": - return `E. Pal`; - case "very pale": - return `V. Pal`; - case "light brown": - return `L. Br`; - case "dark brown": - return `D. Br`; - case "light olive": - return `L. Oli`; - case "dark olive": - return `D. Oli`; - case "light beige": - return `L. Bei`; - case "dark beige": - return `D. Bei`; - case "tan": - return `Tan`; - case "bronze": - return `Bron`; - case "ebony": - return `Ebon`; - case "pure black": - return `P. Bla`; - case "dark": - case "fair": - case "pale": - return `${slave.skin.charAt(0).toUpperCase() + slave.skin.slice(1)}`; - default: - return `${slave.skin.charAt(0).toUpperCase() + slave.skin.charAt(1) + slave.skin.charAt(2)}`; - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function short_genitals(slave, c) { - if (slave.dick > 0) { - let dickDesc = ""; - if (slave.balls === 0) { - dickDesc += `Geld`; - } - if ((slave.dick > 8) && (slave.balls > 8)) { - dickDesc += `Junk+++`; - } else if ((slave.dick > 5) && (slave.balls > 5)) { - dickDesc += `Junk++`; - } else if ((slave.dick > 4) && (slave.balls > 4)) { - dickDesc += `Junk+`; - } else if ((slave.dick > 3) && (slave.balls > 3)) { - dickDesc += `Junk`; - } else if (slave.dick > 8) { - dickDesc += `Dick+++`; - } else if (slave.dick > 5) { - dickDesc += `Dick++`; - } else if (slave.dick > 4) { - dickDesc += `Dick+`; - } else if (slave.dick > 3) { - dickDesc += `Dick`; - } else if (slave.balls > 10) { - dickDesc += `Balls+++`; - } else if (slave.balls > 5) { - dickDesc += `Balls++`; - } else if (slave.balls > 4) { - dickDesc += `Balls+`; - } else if (slave.balls > 3) { - dickDesc += `Balls`; - } - if (dickDesc.length) { - makeSpan(c, dickDesc, "pink"); - } - } - if (slave.vagina === 0) { - makeSpan(c, "VV", "lime"); - } else if ((slave.pregKnown === 1) && canWalk(slave) && (slave.clothes === "no clothing" || slave.clothes === "body oil") && (slave.shoes === "none")) { - makeSpan(c, "NBP", "pink"); - } - if (slave.anus === 0) { - makeSpan(c, "AV", "lime"); - } - let holesDesc = ""; - if ((slave.vagina > 3) && (slave.anus > 3)) { - holesDesc += `V++A++`; - } else if ((slave.vagina > 2) && (slave.anus > 2)) { - holesDesc += `V+A+`; - } else if (slave.vagina > 3) { - holesDesc += `V++`; - } else if (slave.vagina > 2) { - holesDesc += `V+`; - } else if (slave.anus > 3) { - holesDesc += `A++`; - } else if (slave.anus > 2) { - holesDesc += `A+`; - } - if (holesDesc.length) { - makeSpan(c, holesDesc, "pink"); - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function long_genitals(slave, c) { - if (slave.dick > 0) { - let dickDesc = ""; - if (slave.balls === 0) { - dickDesc += 'Gelded.'; - } - if ((slave.dick > 8) && (slave.balls > 8)) { - dickDesc += `Hyper dick & balls.`; - } else if ((slave.dick > 5) && (slave.balls > 5)) { - dickDesc += `Monster dick & balls.`; - } else if ((slave.dick > 4) && (slave.balls > 4)) { - dickDesc = `Huge dick & balls.`; - } else if ((slave.dick > 3) && (slave.balls > 3)) { - dickDesc = `Big dick & balls.`; - } else if (slave.dick > 8) { - dickDesc = `Hyper dong.`; - } else if (slave.dick > 5) { - dickDesc = `Monster dong.`; - } else if (slave.dick > 4) { - dickDesc = `Huge dick.`; - } else if (slave.dick > 3) { - dickDesc = `Big dick.`; - } else if (slave.balls > 8) { - dickDesc = `Hyper balls.`; - } else if (slave.balls > 5) { - dickDesc = `Monstrous balls.`; - } else if (slave.balls > 4) { - dickDesc = `Huge balls.`; - } else if (slave.balls > 3) { - dickDesc = `Big balls.`; - } - if (dickDesc.length) { - makeSpan(c, dickDesc, "pink"); - } - } - if (slave.vagina === 0) { - makeSpan(c, "Virgin.", "lime"); - } else if ((slave.pregKnown === 1) && canWalk(slave) && (slave.clothes === "no clothing" || slave.clothes === "body oil") && (slave.shoes === "none")) { - makeSpan(c, "Naked, barefoot, and pregnant.", "pink"); - } - if (slave.anus === 0) { - makeSpan(c, "Anal virgin.", "lime"); - } - let holesDesc = ""; - if ((slave.vagina > 3) && (slave.anus > 3)) { - holesDesc += `Blown out holes.`; - } else if ((slave.vagina > 2) && (slave.anus > 2)) { - holesDesc += `High mileage.`; - } else if (slave.vagina > 3) { - holesDesc += `Cavernous pussy.`; - } else if (slave.vagina > 2) { - holesDesc += `Loose pussy.`; - } else if (slave.anus > 3) { - holesDesc += `Permagaped anus.`; - } else if (slave.anus > 2) { - holesDesc += `Gaping anus.`; - } - if (holesDesc.length) { - makeSpan(c, holesDesc, "pink"); - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function short_age(slave, c) { - let r = makeSpan(c, "", "pink"); - if (V.showAgeDetail === 1) { - r.textContent += slave.actualAge.toString(); - } else if (slave.actualAge >= 40) { - r.textContent += "40s"; - } else if (slave.actualAge >= 35) { - r.textContent += "Lt30s"; - } else if (slave.actualAge >= 30) { - r.textContent += "Ea30s"; - } else if (slave.actualAge >= 25) { - r.textContent += "Lt20s"; - } else if (slave.actualAge >= 20) { - r.textContent += "Ea20s"; - } else if (slave.actualAge >= 18) { - r.textContent += slave.actualAge.toString(); - } - if (slave.actualAge !== slave.physicalAge) { - r.textContent += ` w ${slave.physicalAge}y-bdy`; - } - if (slave.visualAge !== slave.physicalAge) { - r.textContent += ` Lks${slave.visualAge}`; - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function short_face(slave, c) { - if (slave.face < -95) { - makeSpan(c, "Face---", "red", true, slave.face); - } else if (slave.face < -40) { - makeSpan(c, "Face--", "red", true, slave.face); - } else if (slave.face < -10) { - makeSpan(c, "Face-", "red", true, slave.face); - } else if (slave.face <= 10) { - makeSpan(c, "Face", null, true, slave.face); - } else if (slave.face <= 40) { - makeSpan(c, "Face+", "pink", true, slave.face); - } else if (slave.face <= 95) { - makeSpan(c, "Face++", "pink", true, slave.face); - } else { - makeSpan(c, "Face+++", "pink", true, slave.face); - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function short_eyes(slave, c) { - if (!canSee(slave)) { - makeSpan(c, "Blind", "red"); - } else if (!canSeePerfectly(slave)) { - makeSpan(c, "Sight-", "yellow"); - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function short_ears(slave, c) { - if (slave.hears === -2) { - makeSpan(c, "Deaf", "red"); - } else if ((slave.hears === -1) && (slave.earwear !== "hearing aids")) { - makeSpan(c, "Hearing-", "yellow"); - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function short_lips(slave, c) { - if (slave.lips > 95) { - makeSpan(c, "Facepussy"); - } else if (slave.lips > 70) { - makeSpan(c, "Lips+++", null, true, slave.lips); - } else if (slave.lips > 40) { - makeSpan(c, "Lips++", null, true, slave.lips); - } else if (slave.lips > 20) { - makeSpan(c, "Lips+", null, true, slave.lips); - } else if (slave.lips > 10) { - makeSpan(c, "Lips", null, true, slave.lips); - } else { - makeSpan(c, "Lips-", "red", true, slave.lips); - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function short_teeth(slave, c) { - if (slave.teeth === "crooked") { - makeSpan(c, "Cr Teeth", "yellow"); - } else if (slave.teeth === "gapped") { - makeSpan(c, "Gap", "yellow"); - } else if (slave.teeth === "cosmetic braces") { - makeSpan(c, `Cos Braces`); - } else if (slave.teeth === "straightening braces") { - makeSpan(c, `Braces`); - } else if (slave.teeth === "removable") { - makeSpan(c, `Rem Teeth`); - } else if (slave.teeth === "pointy") { - makeSpan(c, `Fangs`); - } else if (slave.teeth === "baby") { - makeSpan(c, `Baby`); - } else if (slave.teeth === "mixed") { - makeSpan(c, `Mixed`); - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function short_muscles(slave, c) { - if (slave.muscles > 95) { - makeSpan(c, "Musc++", undefined, true, slave.muscles); - } else if (slave.muscles > 50) { - makeSpan(c, "Musc+", undefined, true, slave.muscles); - } else if (slave.muscles > 30) { - makeSpan(c, "Fit", undefined, true, slave.muscles); - } else if (slave.muscles > 5) { - makeSpan(c, "Toned", undefined, true, slave.muscles); - } else if (slave.muscles > -6) { - makeSpan(c, "Soft", undefined, true, slave.muscles); - } else if (slave.muscles > -31) { - if (V.arcologies[0].FSPhysicalIdealist === "unset") { - makeSpan(c, "Weak", "red", true, slave.muscles); - } else { - makeSpan(c, "Soft", undefined, true, slave.muscles); - } - } else if (slave.muscles > -96) { - if (V.arcologies[0].FSPhysicalIdealist === "unset") { - makeSpan(c, "Weak+", "red", true, slave.muscles); - } else { - makeSpan(c, "Soft+", undefined, true, slave.muscles); - } - } else { - makeSpan(c, "Weak++", "red", true, slave.muscles); - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function short_voice(slave, c) { - if (slave.voice === 0) { - makeSpan(c, "Mute", "red"); - } else { - if (slave.accent === 3) { - makeSpan(c, "Acc--", "red"); - } else if (slave.accent === 2) { - makeSpan(c, "Acc-"); - } else if (slave.accent === 4) { - makeSpan(c, "Acc--"); - } else if (slave.accent === 1) { - makeSpan(c, "Acc", "pink"); - } - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function short_tits_ass(slave, c) { - let styles = "pink"; - if ((slave.boobs >= 12000) && (slave.butt > 9)) { - makeSpan(c, "T&A+++", styles); - } else if ((slave.boobs > 4000) && (slave.butt > 8)) { - makeSpan(c, "T&A++", styles); - } else if ((slave.boobs > 2000) && (slave.butt > 6)) { - makeSpan(c, "T&A+", styles); - } else if ((slave.boobs > 800) && (slave.butt > 4)) { - makeSpan(c, "T&A", styles); - } else if ((slave.boobs < 500) && (slave.butt < 3) && (slave.weight <= 10) && (slave.muscles <= 30)) { - makeSpan(c, "Girlish", styles); - } else if (slave.boobs >= 12000) { - makeSpan(c, "Boobs+++", styles); - } else if (slave.boobs > 4000) { - makeSpan(c, "Boobs++", styles); - } else if (slave.boobs > 2000) { - makeSpan(c, "Boobs+", styles); - } else if (slave.boobs > 800) { - makeSpan(c, "Boobs", styles); - } else if (slave.butt > 9) { - makeSpan(c, "Ass+++", styles); - } else if (slave.butt > 8) { - makeSpan(c, "Ass++", styles); - } else if (slave.butt > 6) { - makeSpan(c, "Ass+", styles); - } else if (slave.butt > 4) { - makeSpan(c, "Ass", styles); - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function short_hips(slave, c) { - let desc = ""; - if (slave.hips < -1) { - if (slave.butt > 2 && (V.arcologies[0].FSTransformationFetishist < 20 || V.arcologies[0].FSTransformationFetishist === "unset") && (V.arcologies[0].FSHedonisticDecadence < 20 || V.arcologies[0].FSHedonisticDecadence === "unset")) { - desc = `Disp+`; - } - } else if (slave.hips < 0) { - if (slave.butt > 4 && (V.arcologies[0].FSTransformationFetishist < 20 || V.arcologies[0].FSTransformationFetishist === "unset") && (V.arcologies[0].FSHedonisticDecadence < 20 || V.arcologies[0].FSHedonisticDecadence === "unset")) { - desc = `Disp+`; - } - } else if (slave.hips > 2) { - if (slave.butt <= 8) { - desc = `Disp-`; - } - } else if (slave.hips > 1) { - if (slave.butt <= 3 && (V.arcologies[0].FSSlimnessEnthusiast === "unset" || (slave.boobs >= 500))) { - desc = `Disp-`; - } - } else if (slave.hips > 0) { - if (slave.butt > 8) { - if ((V.arcologies[0].FSTransformationFetishist < 20 || V.arcologies[0].FSTransformationFetishist === "unset") && (V.arcologies[0].FSHedonisticDecadence < 20 || V.arcologies[0].FSHedonisticDecadence === "unset")) { - desc = `Disp+`; - } - } else if (slave.butt <= 2 && (V.arcologies[0].FSSlimnessEnthusiast === "unset" || (slave.boobs >= 500))) { - desc = `Disp-`; - } - } else { - if (slave.butt > 6) { - if ((V.arcologies[0].FSTransformationFetishist < 20 || V.arcologies[0].FSTransformationFetishist === "unset") && (V.arcologies[0].FSHedonisticDecadence < 20 || V.arcologies[0].FSHedonisticDecadence === "unset")) { - desc = `Disp+`; - } - } else if (slave.butt <= 1 && (V.arcologies[0].FSSlimnessEnthusiast === "unset" || (slave.boobs >= 500))) { - desc = `Disp-`; - } - } - if (desc) { - makeSpan(c, desc, "red"); - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function short_waist(slave, c) { - if (slave.waist > 95) { - makeSpan(c, "Wst---", "red", false, slave.waist); - } else if (slave.waist > 40) { - makeSpan(c, "Wst--", "red", false, slave.waist); - } else if (slave.waist > 10) { - makeSpan(c, "Wst-", "red", false, slave.waist); - } else if (slave.waist >= -10) { - makeSpan(c, "Wst", undefined, false, slave.waist); - } else if (slave.waist >= -40) { - makeSpan(c, "Wst+", "pink", false, slave.waist); - } else if (slave.waist >= -95) { - makeSpan(c, "Wst++", "pink", false, slave.waist); - } else { - makeSpan(c, "Wst+++", "pink", false, slave.waist); - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function short_implants(slave, c) { - if ((slave.boobsImplant === 0) && (slave.buttImplant === 0) && (slave.waist >= -95) && (slave.lipsImplant === 0) && (slave.faceImplant <= 5) && (slave.bellyImplant === -1)) { - makeSpan(c, "Natr", "pink"); - } else { - makeSpan(c, "Impl", "pink"); - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function short_lactation(slave, c) { - if (slave.lactation === 1) { - makeSpan(c, "Lact", "pink"); - } else if (slave.lactation === 2) { - makeSpan(c, "Lact", "pink"); - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function short_mods(slave, c) { - V.modScore = SlaveStatsChecker.modScore(slave); - if (slave.corsetPiercing === 0 && V.piercingScore < 3 && V.tatScore < 2) { - return; - } else if (V.modScore > 15 || (V.piercingScore > 8 && V.tatScore > 5)) { - makeSpan(c, "Mods++"); - } else if (V.modScore > 7) { - makeSpan(c, "Mods+"); - } else { - makeSpan(c, "Mods"); - } - if (!jQuery.isEmptyObject(slave.brand)) { - makeSpan(c, "Br"); - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function long_age(slave, c) { - let r = makeSpan(c, "", "pink"); - if (V.showAgeDetail === 1) { - r.textContent += `Age ` + `${slave.actualAge}` + `.`; - } else if (slave.actualAge >= 40) { - r.textContent += `Forties.`; - } else if (slave.actualAge >= 35) { - r.textContent += `Late thirties.`; - } else if (slave.actualAge >= 30) { - r.textContent += `Early thirties.`; - } else if (slave.actualAge >= 25) { - r.textContent += `Late twenties.`; - } else if (slave.actualAge >= 20) { - r.textContent += `Early twenties.`; - } else if (slave.actualAge >= 19) { - r.textContent += `Nineteen.`; - } else if (slave.actualAge >= 18) { - r.textContent += `Eighteen.`; - } else { - r.textContent += `Underage.`; - } - /* - ** No NCS, then do the standard, However because of the wrinkles of Incubators, as long as visual age is greater - ** than or equal to physical age, we do the old physical body/Looks for fresh out of the can NCS slaves. - */ - if (((slave.geneMods.NCS === 0) || (slave.visualAge >= slave.physicalAge))) { - if (slave.actualAge !== slave.physicalAge) { - r.textContent += ` ${slave.physicalAge}` + ` year old body.`; - } - if (slave.visualAge !== slave.physicalAge) { - r.textContent += ` Looks ` + `${slave.visualAge}` + `.`; - } - } else { - /* - ** Now the rub. The use of physical Age for the year old body above, basically conflicts with the changes - ** that NCS introduces, so here to *distinguish* the changes, we use visual age with the 'year old body' - ** and appears, for example: Slave release from incubator at age 10, Her summary would show, 'Age 0. 10 - ** year old body.' But if she's given NCS a few weeks after release, while she's still before her first - ** birthday, it'll appear the same. But once her birthday fires, if we ran with the above code it would - ** say: 'Age 1. 11 year old body.' -- this conflicts with the way NCS works though, because she hasn't - ** visually aged, so our change here makes it say 'Age 1. Appears to have a 10 year old body.' - */ - r.textContent += ` Appears to have a ` + `${slave.visualAge}` + ` year old body.`; - } - if (slave.geneMods.NCS === 1) { - makeSpan(r, "NCS", "orange"); - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function long_face(slave, c) { - if (slave.face < -95) { - makeSpan(c, `Very ugly ${slave.faceShape} face`, "red", true, slave.face); - } else if (slave.face < -40) { - makeSpan(c, `Ugly ${slave.faceShape} face`, "red", true, slave.face); - } else if (slave.face < -10) { - makeSpan(c, `Unattractive ${slave.faceShape} face`, "red", true, slave.face); - } else if (slave.face <= 10) { - makeSpan(c, `Average ${slave.faceShape} face`, null, true, slave.face); - } else if (slave.face <= 40) { - makeSpan(c, `Attractive ${slave.faceShape} face`, "pink", true, slave.face); - } else if (slave.face <= 95) { - makeSpan(c, `Beautiful ${slave.faceShape} face`, "pink", true, slave.face); - } else { - makeSpan(c, `Very beautiful ${slave.faceShape} face`, "pink", true, slave.face); - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function long_eyes(slave, c) { - if (!canSee(slave)) { - makeSpan(c, "Blind.", "red"); - } else if (!canSeePerfectly(slave)) { - makeSpan(c, "Nearsighted.", "yellow"); - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function long_ears(slave, c) { - if (slave.hears <= -2) { - makeSpan(c, "Deaf.", "red"); - } else if ((slave.hears === -1) && (slave.earwear !== "hearing aids")) { - makeSpan(c, "Hard of hearing.", "yellow"); - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function long_lips(slave, c) { - if (slave.lips > 95) { - makeSpan(c, "Facepussy", undefined, true, slave.lips); - } else if (slave.lips > 70) { - makeSpan(c, "Huge lips", undefined, true, slave.lips); - } else if (slave.lips > 40) { - makeSpan(c, "Big lips", undefined, true, slave.lips); - } else if (slave.lips > 20) { - makeSpan(c, "Pretty lips", undefined, true, slave.lips); - } else if (slave.lips > 10) { - makeSpan(c, "Normal lips", undefined, true, slave.lips); - } else { - makeSpan(c, "Thin lips", "red", true, slave.lips); - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function long_teeth(slave, c) { - if (slave.teeth === "crooked") { - makeSpan(c, "Crooked teeth.", "yellow"); - } else if (slave.teeth === "gapped") { - makeSpan(c, "Tooth gap.", "yellow"); - } else if (slave.teeth === "cosmetic braces") { - makeSpan(c, "Cosmetic braces."); - } else if (slave.teeth === "straightening braces") { - makeSpan(c, "Braces."); - } else if (slave.teeth === "removable") { - makeSpan(c, "Removable teeth."); - } else if (slave.teeth === "pointy") { - makeSpan(c, "Sharp fangs."); - } else if (slave.teeth === "baby") { - makeSpan(c, "Baby teeth."); - } else if (slave.teeth === "mixed") { - makeSpan(c, "Mixed teeth."); - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function long_muscles(slave, c) { - if (slave.muscles > 95) { - makeSpan(c, "Hugely muscular", undefined, true, slave.muscles); - } else if (slave.muscles > 50) { - makeSpan(c, "Muscular", undefined, true, slave.muscles); - } else if (slave.muscles > 30) { - makeSpan(c, "Fit", undefined, true, slave.muscles); - } else if (slave.muscles > 5) { - makeSpan(c, "Toned", undefined, true, slave.muscles); - } else if (slave.muscles > -6) { - makeSpan(c, "Soft", undefined, true, slave.muscles); - } else if (slave.muscles > -31) { - if (V.arcologies[0].FSPhysicalIdealist === "unset") { - makeSpan(c, "Weak", "red", true, slave.muscles); - } else { - makeSpan(c, "Weak", undefined, true, slave.muscles); - } - } else if (slave.muscles > -96) { - if (V.arcologies[0].FSPhysicalIdealist === "unset") { - makeSpan(c, "Very weak", "red", true, slave.muscles); - } else { - makeSpan(c, "Very weak", undefined, true, slave.muscles); - } - } else { - makeSpan(c, "Frail", "red", true, slave.muscles); - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function long_voice(slave, c) { - if (slave.voice === 0) { - makeSpan(c, "Mute.", "red"); - } else { - if (slave.accent === 3) { - makeSpan(c, "Bad accent.", "red"); - } else if (slave.accent === 4) { - makeSpan(c, "No language skills.", "red"); - } else if (slave.accent === 2) { - makeSpan(c, "Accent."); - } else if (slave.accent === 1) { - makeSpan(c, "Cute accent.", "pink"); - } - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function long_tits_ass(slave, c) { - const styles = "pink"; - if ((slave.boobs >= 12000) && (slave.butt > 9)) { - makeSpan(c, "Hyper T&A.", styles); - } else if ((slave.boobs > 4000) && (slave.butt > 8)) { - makeSpan(c, "Enormous T&A.", styles); - } else if ((slave.boobs > 2000) && (slave.butt > 6)) { - makeSpan(c, "Huge T&A.", styles); - } else if ((slave.boobs > 800) && (slave.butt > 4)) { - makeSpan(c, "Big T&A.", styles); - } else if ((slave.boobs < 500) && (slave.butt < 3) && (slave.weight <= 10) && (slave.muscles <= 30)) { - makeSpan(c, "Girlish figure.", styles); - } else if (slave.boobs >= 12000) { - makeSpan(c, "Immobilizing tits.", styles); - } else if (slave.boobs > 4000) { - makeSpan(c, "Monstrous tits.", styles); - } else if (slave.boobs > 2000) { - makeSpan(c, "Huge tits.", styles); - } else if (slave.boobs > 800) { - makeSpan(c, "Big tits.", styles); - } else if (slave.butt > 9) { - makeSpan(c, "Hyper ass.", styles); - } else if (slave.butt > 8) { - makeSpan(c, "Titanic ass.", styles); - } else if (slave.butt > 6) { - makeSpan(c, "Huge ass.", styles); - } else if (slave.butt > 4) { - makeSpan(c, "Big ass.", styles); - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function long_hips(slave, c) { - const styles = "red"; - if (slave.hips < -1) { - if (slave.butt > 2 && (V.arcologies[0].FSTransformationFetishist < 20 || V.arcologies[0].FSTransformationFetishist === "unset") && (V.arcologies[0].FSHedonisticDecadence < 20 || V.arcologies[0].FSHedonisticDecadence === "unset") && (V.arcologies[0].FSAssetExpansionist < 20 || V.arcologies[0].FSAssetExpansionist === "unset") && (V.arcologies[0].FSIntellectualDependencyLawBeauty === 0)) { - makeSpan(c, "Disproportionately big butt.", styles); - } - } else if (slave.hips < 0) { - if (slave.butt > 4 && (V.arcologies[0].FSTransformationFetishist < 20 || V.arcologies[0].FSTransformationFetishist === "unset") && (V.arcologies[0].FSHedonisticDecadence < 20 || V.arcologies[0].FSHedonisticDecadence === "unset") && (V.arcologies[0].FSAssetExpansionist < 20 || V.arcologies[0].FSAssetExpansionist === "unset") && (V.arcologies[0].FSIntellectualDependencyLawBeauty === 0)) { - makeSpan(c, "Disproportionately big butt.", styles); - } - } else if (slave.hips > 2) { - if (slave.butt <= 8) { - makeSpan(c, "Disproportionately small butt.", styles); - } - } else if (slave.hips > 1) { - if (slave.butt <= 3 && ((V.arcologies[0].FSSlimnessEnthusiast === "unset") || (slave.boobs >= 500))) { - makeSpan(c, "Disproportionately small butt.", styles); - } - } else if (slave.hips > 0) { - if (slave.butt > 8) { - if ((V.arcologies[0].FSTransformationFetishist < 20 || V.arcologies[0].FSTransformationFetishist === "unset") && (V.arcologies[0].FSHedonisticDecadence < 20 || V.arcologies[0].FSHedonisticDecadence === "unset") && (V.arcologies[0].FSAssetExpansionist < 20 || V.arcologies[0].FSAssetExpansionist === "unset") && (V.arcologies[0].FSIntellectualDependencyLawBeauty === 0)) { - makeSpan(c, "Disproportionately big butt.", styles); - } - } else if (slave.butt <= 2 && ((V.arcologies[0].FSSlimnessEnthusiast === "unset") || (slave.boobs >= 500))) { - makeSpan(c, "Disproportionately small butt.", styles); - } - } else { - if (slave.butt > 6) { - if ((V.arcologies[0].FSTransformationFetishist < 20 || V.arcologies[0].FSTransformationFetishist === "unset") && (V.arcologies[0].FSHedonisticDecadence < 20 || V.arcologies[0].FSHedonisticDecadence === "unset") && (V.arcologies[0].FSAssetExpansionist < 20 || V.arcologies[0].FSAssetExpansionist === "unset") && (V.arcologies[0].FSIntellectualDependencyLawBeauty === 0)) { - makeSpan(c, "Disproportionately big butt.", styles); - } - } else if (slave.butt <= 1 && ((V.arcologies[0].FSSlimnessEnthusiast === "unset") || (slave.boobs >= 500))) { - makeSpan(c, "Disproportionately small butt.", styles); - } - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function long_waist(slave, c) { - if (slave.waist > 95) { - makeSpan(c, "Masculine waist", "red", true, slave.waist); - } else if (slave.waist > 40) { - makeSpan(c, "Ugly waist", "red", true, slave.waist); - } else if (slave.waist > 10) { - makeSpan(c, "Unattractive waist", "red", true, slave.waist); - } else if (slave.waist >= -10) { - makeSpan(c, "Average waist", null, true, slave.waist); - } else if (slave.waist >= -40) { - makeSpan(c, "Feminine waist", "pink", true, slave.waist); - } else if (slave.waist >= -95) { - makeSpan(c, "Hourglass waist", "pink", true, slave.waist); - } else { - makeSpan(c, "Absurdly narrow waist", "pink", true, slave.waist); - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function long_implants(slave, c) { - const styles = "pink"; - if ((slave.boobsImplant !== 0) || (slave.buttImplant !== 0) || (slave.lipsImplant !== 0) || (slave.bellyImplant !== -1)) { - makeSpan(c, "Implants.", styles); - } else if ((slave.faceImplant >= 30) || (slave.waist < -95)) { - makeSpan(c, "Surgery enhanced.", styles); - } else { - makeSpan(c, "All natural.", styles); - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function long_lactation(slave, c) { - if (slave.lactation === 1) { - makeSpan(c, "Lactating naturally.", "pink"); - } else if (slave.lactation === 2) { - makeSpan(c, "Heavy lactation.", "pink"); - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function long_mods(slave, c) { - V.modScore = SlaveStatsChecker.modScore(slave); - if (slave.corsetPiercing === 0 && V.piercingScore < 3 && V.tatScore < 2) { - return; - } else if (V.modScore > 15 || (V.piercingScore > 8 && V.tatScore > 5)) { - makeSpan(c, "Extensive body mods."); - } else if (V.modScore > 7) { - makeSpan(c, "Noticeable body mods."); - } else { - makeSpan(c, "Light body mods."); - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function short_intelligence(slave, c) { - const intelligence = slave.intelligence + slave.intelligenceImplant; - if (slave.fetish === "mindbroken") { - return; - } - let education = ""; - let naturalIntelligence = ""; - let styles = undefined; - if (slave.intelligenceImplant >= 30) { - education = "(e+)"; - } else if (slave.intelligenceImplant >= 15) { - education = "(e)"; - } else if (slave.intelligenceImplant <= -15) { - education = "(e-)"; - } - if (intelligence >= 130) { - naturalIntelligence = "I++++"; - styles = "deepskyblue"; - } else if (intelligence > 95) { - naturalIntelligence = "I+++"; - styles = "deepskyblue"; - } else if (intelligence > 50) { - naturalIntelligence = "I++"; - styles = "deepskyblue"; - } else if (intelligence > 15) { - naturalIntelligence = "I+"; - styles = "deepskyblue"; - } else if (intelligence >= -15) { - naturalIntelligence = "I"; - } else if (intelligence >= -50) { - naturalIntelligence = "I-"; - styles = "orangered"; - } else if (intelligence >= -95) { - naturalIntelligence = "I--"; - styles = "orangered"; - } else { - naturalIntelligence = "I---"; - styles = "orangered"; - } - makeSpan(c, `${naturalIntelligence}${education}`, styles, true, intelligence); - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function short_skills(slave, c) { - let _SSkills = slave.skill.anal + slave.skill.oral; - let r = makeSpan(c, "", "aquamarine"); - if (((_SSkills + slave.skill.whoring + slave.skill.entertainment) >= 400) && ((slave.vagina < 0) || (slave.skill.vaginal >= 100))) { - r.textContent += `MSS`; - } else { - _SSkills += slave.skill.vaginal; - _SSkills = Math.trunc(_SSkills); - if (_SSkills > 180) { - r.textContent += `S++`; - } else if ((_SSkills > 120) && (slave.vagina < 0)) { - r.textContent += `Sh++`; - } else if (_SSkills > 90) { - r.textContent += `S+`; - } else if (_SSkills > 30) { - r.textContent += `S`; - } else { - r.textContent += `S-`; - } - if (V.summaryStats) { - r.textContent += `[${_SSkills}]`; - } - r.textContent += " "; - if (slave.skill.whoring >= 100) { - r.textContent += `W+++`; - } else if (slave.skill.whoring > 60) { - r.textContent += `W++`; - } else if (slave.skill.whoring > 30) { - r.textContent += `W+`; - } else if (slave.skill.whoring > 10) { - r.textContent += `W`; - } - if (slave.skill.whoring > 10) { - if (V.summaryStats) { - r.textContent += `[${slave.skill.whoring}]`; - } - } - r.textContent += " "; - if (slave.skill.entertainment >= 100) { - r.textContent += `E+++`; - } else if (slave.skill.entertainment > 60) { - r.textContent += `E++`; - } else if (slave.skill.entertainment > 30) { - r.textContent += `E+`; - } else if (slave.skill.entertainment > 10) { - r.textContent += `E`; - } - if (slave.skill.entertainment > 10) { - if (V.summaryStats) { - r.textContent += `[${slave.skill.entertainment}]`; - } - } - } - if (slave.skill.combat > 0) { - r.textContent += " C"; - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function short_prestige(slave, c) { - if (slave.prestige > 0) { - const styles = "green"; - if (slave.prestige > 2) { - makeSpan(c, "Prest++", styles); - } else if (slave.prestige === 2) { - makeSpan(c, "Prest+", styles); - } else if (slave.prestige === 1) { - makeSpan(c, "Prest", styles); - } - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function short_porn_prestige(slave, c) { - if (slave.porn.prestige > 0) { - const styles = "green"; - if (slave.porn.prestige > 2) { - makeSpan(c, "PPrest++", styles); - } else if (slave.porn.prestige === 2) { - makeSpan(c, "PPrest+", styles); - } else if (slave.porn.prestige === 1) { - makeSpan(c, "PPrest", styles); - } - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function long_intelligence(slave, c) { - const intelligence = slave.intelligence + slave.intelligenceImplant; - if (slave.fetish === "mindbroken") { - return; - } - let education = ""; - let naturalIntelligence = ""; - let styles = undefined; - if (slave.intelligenceImplant >= 30) { - education = ", well educated"; - } else if (slave.intelligenceImplant >= 15) { - education = ", educated"; - } else if (slave.intelligenceImplant <= -15) { - education = ", hindered"; - } - if (intelligence >= 130) { - naturalIntelligence = "Genius"; - styles = "deepskyblue"; - } else if (intelligence > 95) { - naturalIntelligence = "Brilliant"; - styles = "deepskyblue"; - } else if (intelligence > 50) { - naturalIntelligence = "Very smart"; - styles = "deepskyblue"; - } else if (intelligence > 15) { - naturalIntelligence = "Smart"; - styles = "deepskyblue"; - } else if (intelligence >= -15) { - naturalIntelligence = "Average intelligence"; - } else if (intelligence >= -50) { - naturalIntelligence = "Slow"; - styles = "orangered"; - } else if (intelligence >= -95) { - naturalIntelligence = "Very slow"; - styles = "orangered"; - } else { - naturalIntelligence = "Moronic"; - styles = "orangered"; - } - makeSpan(c, `${naturalIntelligence}${education}`, styles, true, intelligence); - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function long_skills(slave, c) { - let _SSkills = (slave.skill.anal + slave.skill.oral); - if (((_SSkills + slave.skill.whoring + slave.skill.entertainment) >= 400) && ((slave.vagina < 0) || (slave.skill.vaginal >= 100))) { - makeSpan(c, "Masterful Sex Slave.", "aquamarine"); - } else { - let desc; - _SSkills += slave.skill.vaginal; - if (_SSkills > 180) { - desc = "Sex master"; - } else if ((_SSkills > 120) && (slave.vagina < 0)) { - desc = "Masterful shemale"; - } else if (_SSkills > 90) { - desc = "Sexual expert"; - } else if (_SSkills > 30) { - desc = "Sexually skilled"; - } else { - desc = "Sexually unskilled"; - } - if (desc) { - makeSpan(c, desc, "aquamarine", true, Math.trunc(_SSkills)); - desc = ""; - } - if (slave.skill.whoring >= 100) { - desc = "Masterful whore"; - } else if (slave.skill.whoring >= 60) { - desc = "Expert whore"; - } else if (slave.skill.whoring >= 30) { - desc = "Skilled whore"; - } else if (slave.skill.whoring >= 10) { - desc = "Basic whore"; - } - if (desc) { - makeSpan(c, desc, "aquamarine", true, slave.skill.whoring); - desc = ""; - } - if (slave.skill.entertainment >= 100) { - desc = "Masterful entertainer"; - } else if (slave.skill.entertainment >= 60) { - desc = "Expert entertainer"; - } else if (slave.skill.entertainment >= 30) { - desc = "Skilled entertainer"; - } else if (slave.skill.entertainment >= 10) { - desc = "Basic entertainer"; - } - if (desc) { - makeSpan(c, desc, "aquamarine", true, slave.skill.entertainment); - desc = ""; - } - } - if (slave.skill.combat > 0) { - makeSpan(c, "Trained fighter.", "aquamarine"); - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function long_prestige(slave, c) { - if (slave.prestige > 0) { - const styles = "green"; - if (slave.prestige > 2) { - makeSpan(c, "Extremely prestigious.", styles); - } else if (slave.prestige === 2) { - makeSpan(c, "Very prestigious.", styles); - } else if (slave.prestige === 1) { - makeSpan(c, "Prestigious.", styles); - } - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function long_porn_prestige(slave, c) { - if (slave.porn.prestige > 0) { - const styles = "green"; - if (slave.porn.prestige > 2) { - makeSpan(c, "Porn star.", styles); - } else if (slave.porn.prestige === 2) { - makeSpan(c, "Porn slut.", styles); - } else if (slave.porn.prestige === 1) { - makeSpan(c, "Porn amateur.", styles); - } - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function short_fetish(slave, c) { - let descStr = ""; - switch (slave.fetish) { - case "submissive": - if (slave.fetishStrength > 95) { - descStr = `Sub++`; - } else if (slave.fetishStrength > 60) { - descStr = `Sub+`; - } else { - descStr = `Sub`; - } - break; - case "cumslut": - if (slave.fetishStrength > 95) { - descStr = `Oral++`; - } else if (slave.fetishStrength > 60) { - descStr = `Oral+`; - } else { - descStr = `Oral`; - } - break; - case "humiliation": - if (slave.fetishStrength > 95) { - descStr = `Humil++`; - } else if (slave.fetishStrength > 60) { - descStr = `Humil+`; - } else { - descStr = `Humil`; - } - break; - case "buttslut": - if (slave.fetishStrength > 95) { - descStr = `Anal++`; - } else if (slave.fetishStrength > 60) { - descStr = `Anal+`; - } else { - descStr = `Anal`; - } - break; - case "boobs": - if (slave.fetishStrength > 95) { - descStr = `Boobs++`; - } else if (slave.fetishStrength > 60) { - descStr = `Boobs+`; - } else { - descStr = `Boobs`; - } - break; - case "sadist": - if (slave.fetishStrength > 95) { - descStr = `Sadist++`; - } else if (slave.fetishStrength > 60) { - descStr = `Sadist+`; - } else { - descStr = `Sadist`; - } - break; - case "masochist": - if (slave.fetishStrength > 95) { - descStr = `Pain++`; - } else if (slave.fetishStrength > 60) { - descStr = `Pain+`; - } else { - descStr = `Pain`; - } - break; - case "dom": - if (slave.fetishStrength > 95) { - descStr = `Dom++`; - } else if (slave.fetishStrength > 60) { - descStr = `Dom+`; - } else { - descStr = `Dom`; - } - break; - case "pregnancy": - if (slave.fetishStrength > 95) { - descStr = `Preg++`; - } else if (slave.fetishStrength > 60) { - descStr = `Preg+`; - } else { - descStr = `Preg`; - } - break; - default: - descStr = `Vanilla`; - break; - } - if (V.summaryStats) { - descStr += `[${slave.fetishStrength}]`; - } - makeSpan(c, descStr, "lightcoral"); - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function short_attraction(slave, c) { - if (slave.attrXY <= 5) { - makeSpan(c, "XY---", "red", false, slave.attrXY); - } else if (slave.attrXY <= 15) { - makeSpan(c, "XY--", "red", false, slave.attrXY); - } else if (slave.attrXY <= 35) { - makeSpan(c, "XY---", "red", false, slave.attrXY); - } else if (slave.attrXY <= 65) { - makeSpan(c, "XY", undefined, false, slave.attrXY); - } else if (slave.attrXY <= 85) { - makeSpan(c, "XY+", "green", false, slave.attrXY); - } else if (slave.attrXY <= 95) { - makeSpan(c, "XY++", "green", false, slave.attrXY); - } else if (slave.attrXX > 95) { - if (slave.energy <= 95) { - makeSpan(c, "Omni!", "green"); - } else { - makeSpan(c, "Omni+Nympho!!", "green"); - } - } else { - makeSpan(c, "XY+++", "green", false, slave.attrXY); - } - if (slave.attrXX <= 5) { - makeSpan(c, "XX---", "red", false, slave.attrXX); - } else if (slave.attrXX <= 15) { - makeSpan(c, "XX--", "red", false, slave.attrXX); - } else if (slave.attrXX <= 35) { - makeSpan(c, "XX-", "red", false, slave.attrXX); - } else if (slave.attrXX <= 65) { - makeSpan(c, "XX", undefined, false, slave.attrXX); - } else if (slave.attrXX <= 85) { - makeSpan(c, "XX+", "green", false, slave.attrXX); - } else if (slave.attrXX <= 95) { - makeSpan(c, "XX++", "green", false, slave.attrXX); - } else if (slave.attrXY <= 95) { - makeSpan(c, "XX+++", "green", false, slave.attrXX); - } - if (slave.energy > 95) { - if ((slave.attrXY <= 95) || (slave.attrXX <= 95)) { - makeSpan(c, "Nympho!", "green"); - } - } else if (slave.energy > 80) { - makeSpan(c, "SD++", "green", false, slave.energy); - } else if (slave.energy > 60) { - makeSpan(c, "SD+", "green", false, slave.energy); - } else if (slave.energy > 40) { - makeSpan(c, "SD", "green", false, slave.energy); - } else if (slave.energy > 20) { - makeSpan(c, "SD-", "red", false, slave.energy); - } else { - makeSpan(c, "SD--", "red", false, slave.energy); - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function short_smart_fetish(slave, c) { - function settingStr() { - if (slave.fetishKnown === 1) { - if (slave.clitSetting === "off") { - return `SP-`; - } else if (((slave.fetish !== "submissive") || (slave.fetishStrength <= 95)) && (slave.clitSetting === "submissive")) { - return `SP:sub`; - } else if (((slave.fetish !== "cumslut") || (slave.fetishStrength <= 95)) && (slave.clitSetting === "oral")) { - return `SP:oral`; - } else if (((slave.fetish !== "humiliation") || (slave.fetishStrength <= 95)) && (slave.clitSetting === "humiliation")) { - return `SP:humil`; - } else if (((slave.fetish !== "buttslut") || (slave.fetishStrength <= 95)) && (slave.clitSetting === "anal")) { - return `SP:anal`; - } else if (((slave.fetish !== "boobs") || (slave.fetishStrength <= 95)) && (slave.clitSetting === "boobs")) { - return `SP:boobs`; - } else if (((slave.fetish !== "sadist") || (slave.fetishStrength <= 95)) && (slave.clitSetting === "sadist")) { - return `SP:sade`; - } else if (((slave.fetish !== "masochist") || (slave.fetishStrength <= 95)) && (slave.clitSetting === "masochist")) { - return `SP:pain`; - } else if (((slave.fetish !== "dom") || (slave.fetishStrength <= 95)) && (slave.clitSetting === "dom")) { - return `SP:dom`; - } else if (((slave.fetish !== "pregnancy") || (slave.fetishStrength <= 95)) && (slave.clitSetting === "pregnancy")) { - return `SP:preg`; - } else if (((slave.fetish !== "none") && (slave.clitSetting === "vanilla"))) { - return `SP:vanilla`; - } else if ((slave.energy <= 95) && (slave.clitSetting === "all")) { - return `SP:all`; - } else if ((slave.energy > 5) && (slave.clitSetting === "none")) { - return `SP:none`; - } else if (!["anti-men", "anti-women", "men", "women"].includes(slave.clitSetting)) { - return `SP:monitoring`; - } - } else { - switch (slave.clitSetting) { - case "off": - return `SP-`; - case "submissive": - return `SP:sub`; - case "lesbian": - return `SP:les`; - case "oral": - return `SP:oral`; - case "humiliation": - return `SP:humil`; - case "anal": - return `SP:anal`; - case "boobs": - return `SP:boobs`; - case "sadist": - return `SP:sade`; - case "masochist": - return `SP:pain`; - case "dom": - return `SP:dom`; - case "pregnancy": - return `SP:pregnancy`; - case "vanilla": - return `SP:vanilla`; - case "all": - return `SP:all`; - case "none": - return `SP:none`; - } - } - return null; - } - const s = settingStr(); - if (s) { - makeSpan(c, settingStr()); - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function short_smart_attraction(slave, c) { - function settingStr() { - if (slave.attrKnown === 1) { - if (slave.clitSetting === "women") { - if (slave.attrXX < 95) { - return `SP:women`; - } else { - return `SP:monitoring`; - } - } else if (slave.clitSetting === "men") { - if (slave.attrXY < 95) { - return `SP:men`; - } else { - return `SP:monitoring`; - } - } else if (slave.clitSetting === "anti-women") { - if (slave.attrXX > 0) { - return `SP:anti-women`; - } else { - return `SP:monitoring`; - } - } else if (slave.clitSetting === "anti-men") { - if (slave.attrXY > 0) { - return `SP:anti-men`; - } else { - return `SP:monitoring`; - } - } - } else { - if (slave.clitSetting === "women") { - return `SP:women`; - } else if (slave.clitSetting === "men") { - return `SP:men`; - } else if (slave.clitSetting === "anti-women") { - return `SP:anti-women`; - } else if (slave.clitSetting === "anti-men") { - return `SP:anti-men`; - } - } - return null; - } - const s = settingStr(); - if (s) { - makeSpan(c, settingStr()); - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function short_behavior_flaw(slave, c) { - function descStr() { - switch (slave.behavioralFlaw) { - case "arrogant": - return `Arrog`; - case "bitchy": - return `Bitchy`; - case "odd": - return `Odd`; - case "hates men": - return `Men-`; - case "hates women": - return `Women-`; - case "gluttonous": - return `Glut`; - case "anorexic": - return `Ano`; - case "devout": - return `Dev`; - case "liberated": - return `Lib`; - default: - slave.behavioralFlaw = "none"; - return null; - } - } - const s = descStr(); - if (s) { - makeSpan(c, descStr(), "red"); - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function short_sex_flaw(slave, c) { - switch (slave.sexualFlaw) { - case "hates oral": - makeSpan(c, "Oral-", "red"); - break; - case "hates anal": - makeSpan(c, "Anal-", "red"); - break; - case "hates penetration": - makeSpan(c, "Fuck-", "red"); - break; - case "shamefast": - makeSpan(c, "Shame", "red"); - break; - case "idealistic": - makeSpan(c, "Ideal", "red"); - break; - case "repressed": - makeSpan(c, "Repre", "red"); - break; - case "apathetic": - makeSpan(c, "Apath", "red"); - break; - case "crude": - makeSpan(c, "Crude", "red"); - break; - case "judgemental": - makeSpan(c, "Judge", "red"); - break; - case "cum addict": - makeSpan(c, "CumAdd", "yellow"); - break; - case "anal addict": - makeSpan(c, "AnalAdd", "yellow"); - break; - case "attention whore": - makeSpan(c, "Attention", "yellow"); - break; - case "breast growth": - makeSpan(c, "BoobObsess", "yellow"); - break; - case "abusive": - makeSpan(c, "Abusive", "yellow"); - break; - case "malicious": - makeSpan(c, "Malice", "yellow"); - break; - case "self hating": - makeSpan(c, "SelfHatr", "yellow"); - break; - case "neglectful": - makeSpan(c, "SelfNeglect", "yellow"); - break; - case "breeder": - makeSpan(c, "BreedObsess", "yellow"); - break; - default: - slave.sexualFlaw = "none"; - break; - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function short_behavior_quirk(slave, c) { - function descStr() { - switch (slave.behavioralQuirk) { - case "confident": - return `Confid`; - case "cutting": - return `Cutting`; - case "funny": - return `Funny`; - case "fitness": - return `Fit`; - case "adores women": - return `Women+`; - case "adores men": - return `Men+`; - case "insecure": - return `Insec`; - case "sinful": - return `Sinf`; - case "advocate": - return `Advoc`; - default: - slave.behavioralQuirk = "none"; - return null; - } - } - const s = descStr(); - if (s) { - makeSpan(c, s, "green"); - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function short_sex_quirk(slave, c) { - function descStr() { - switch (slave.sexualQuirk) { - case "gagfuck queen": - return `Gagfuck`; - case "painal queen": - return `Painal`; - case "strugglefuck queen": - return `Struggle`; - case "tease": - return `Tease`; - case "romantic": - return `Romantic`; - case "perverted": - return `Perverted`; - case "caring": - return `Caring`; - case "unflinching": - return `Unflinch`; - case "size queen": - return `SizeQ`; - default: - slave.sexualQuirk = "none"; - return null; - } - } - const s = descStr(); - if (s) { - makeSpan(c, s, "green"); - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function long_fetish(slave, c) { - function fetishStr() { - switch (slave.fetish) { - case "submissive": - if (slave.fetishStrength > 95) { - return "Complete submissive"; - } else if (slave.fetishStrength > 60) { - return "Submissive"; - } else { - return "Submissive tendencies"; - } - case "cumslut": - if (slave.fetishStrength > 95) { - return "Cumslut"; - } else if (slave.fetishStrength > 60) { - return "Oral fixation"; - } else { - return "Prefers oral"; - } - case "humiliation": - if (slave.fetishStrength > 95) { - return "Humiliation slut"; - } else if (slave.fetishStrength > 60) { - return "Exhibitionist"; - } else { - return "Interest in humiliation"; - } - case "buttslut": - if (slave.fetishStrength > 95) { - return "Buttslut"; - } else if (slave.fetishStrength > 60) { - return "Anal fixation"; - } else { - return "Prefers anal"; - } - case "boobs": - if (slave.fetishStrength > 95) { - return "Boobslut"; - } else if (slave.fetishStrength > 60) { - return "Breast fixation"; - } else { - return "Loves boobs"; - } - case "sadist": - if (slave.fetishStrength > 95) { - return "Complete sadist"; - } else if (slave.fetishStrength > 60) { - return "Sadist"; - } else { - return "Sadistic tendencies"; - } - case "masochist": - if (slave.fetishStrength > 95) { - return "Complete masochist"; - } else if (slave.fetishStrength > 60) { - return "Masochist"; - } else { - return "Masochistic tendencies"; - } - case "dom": - if (slave.fetishStrength > 95) { - return "Complete dom"; - } else if (slave.fetishStrength > 60) { - return "Dominant"; - } else { - return "Dominant tendencies"; - } - case "pregnancy": - if (slave.fetishStrength > 95) { - return "Pregnancy fetish"; - } else if (slave.fetishStrength > 60) { - return "Pregnancy kink"; - } else { - return "Interest in impregnation"; - } - default: - return "Sexually vanilla"; - } - } - makeSpan(c, fetishStr(), "lightcoral", true, slave.fetishStrength); - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function long_attraction(slave, c) { - if (slave.attrXY <= 5) { - makeSpan(c, "Disgusted by men", "red", true, slave.attrXY); - } else if (slave.attrXY <= 15) { - makeSpan(c, "Turned off by men", "red", true, slave.attrXY); - } else if (slave.attrXY <= 35) { - makeSpan(c, "Not attracted to men", "red", true, slave.attrXY); - } else if (slave.attrXY <= 65) { - makeSpan(c, "Indifferent to men", undefined, true, slave.attrXY); - } else if (slave.attrXY <= 85) { - makeSpan(c, "Attracted to men", "green", true, slave.attrXY); - } else if (slave.attrXY <= 95) { - makeSpan(c, "Aroused by men", "green", true, slave.attrXY); - } else if (slave.attrXX > 95) { - if (slave.energy <= 95) { - makeSpan(c, "Omnisexual!", "green"); - } else { - makeSpan(c, "Omnisexual nymphomaniac!", "green"); - } - } else { - makeSpan(c, "Passionate about men", "green", true, slave.attrXY); - } - if (slave.attrXX <= 5) { - makeSpan(c, "disgusted by women", "red", true, slave.attrXX); - } else if (slave.attrXX <= 15) { - makeSpan(c, "turned off by women", "red", true, slave.attrXX); - } else if (slave.attrXX <= 35) { - makeSpan(c, "not attracted to women", "red", true, slave.attrXX); - } else if (slave.attrXX <= 65) { - makeSpan(c, "indifferent to women", undefined, true, slave.attrXX); - } else if (slave.attrXX <= 85) { - makeSpan(c, "attracted to women", "green", true, slave.attrXX); - } else if (slave.attrXX <= 95) { - makeSpan(c, "aroused by women", "green", true, slave.attrXX); - } else if (slave.attrXY <= 95) { - makeSpan(c, "passionate about women", "green", true, slave.attrXX); - } - if (slave.energy > 95) { - if ((slave.attrXY <= 95) || (slave.attrXX <= 95)) { - makeSpan(c, "Nymphomaniac!", "green"); - } - } else if (slave.energy > 80) { - makeSpan(c, "Powerful sex drive", "green", true, slave.energy); - } else if (slave.energy > 60) { - makeSpan(c, "Good sex drive", "green", true, slave.energy); - } else if (slave.energy > 40) { - makeSpan(c, "Average sex drive", "yellow", true, slave.energy); - } else if (slave.energy > 20) { - makeSpan(c, "Poor sex drive", "red", true, slave.energy); - } else { - makeSpan(c, "No sex drive", "red", true, slave.energy); - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function long_smart_fetish(slave, c) { - function descStr() { - if (slave.fetishKnown === 1) { - if (slave.clitSetting === "off") { - return `SP off.`; - } else if (((slave.fetish !== "submissive") || (slave.fetishStrength <= 95)) && (slave.clitSetting === "submissive")) { - return `SP: submissive.`; - } else if (((slave.fetish !== "cumslut") || (slave.fetishStrength <= 95)) && (slave.clitSetting === "oral")) { - return `SP: oral.`; - } else if (((slave.fetish !== "humiliation") || (slave.fetishStrength <= 95)) && (slave.clitSetting === "humiliation")) { - return `SP: humiliation.`; - } else if (((slave.fetish !== "buttslut") || (slave.fetishStrength <= 95)) && (slave.clitSetting === "anal")) { - return `SP: anal.`; - } else if (((slave.fetish !== "boobs") || (slave.fetishStrength <= 95)) && (slave.clitSetting === "boobs")) { - return `SP: breasts.`; - } else if (((slave.fetish !== "sadist") || (slave.fetishStrength <= 95)) && (slave.clitSetting === "sadist")) { - return `SP: sadism.`; - } else if (((slave.fetish !== "masochist") || (slave.fetishStrength <= 95)) && (slave.clitSetting === "masochist")) { - return `SP: masochism.`; - } else if (((slave.fetish !== "dom") || (slave.fetishStrength <= 95)) && (slave.clitSetting === "dom")) { - return `SP: dominance.`; - } else if (((slave.fetish !== "pregnancy") || (slave.fetishStrength <= 95)) && (slave.clitSetting === "pregnancy")) { - return `SP: pregnancy.`; - } else if ((slave.fetish !== "none") && (slave.clitSetting === "vanilla")) { - return `SP: vanilla.`; - } else if ((slave.energy <= 95) && (slave.clitSetting === "all")) { - return `SP: all.`; - } else if ((slave.energy > 5) && (slave.clitSetting === "none")) { - return `SP: none.`; - } else if (!["anti-men", "anti-women", "men", "women"].includes(slave.clitSetting)) { - return `SP: monitoring.`; - } - } else { - switch (slave.clitSetting) { - case "off": - return `SP off.`; - case "submissive": - return `SP: submissive.`; - case "oral": - return `SP: oral.`; - case "humiliation": - return `SP: humiliation.`; - case "anal": - return `SP: anal.`; - case "boobs": - return `SP: breasts.`; - case "sadist": - return `SP: sadism.`; - case "masochist": - return `SP: masochism.`; - case "dom": - return `SP: dominance.`; - case "pregnancy": - return `SP: pregnancy.`; - case "vanilla": - return `SP: vanilla.`; - case "all": - return `SP: all.`; - case "none": - return `SP: none.`; - } - } - return null; - } - const s = descStr(); - if (s) { - makeSpan(c, s); - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function long_smart_attraction(slave, c) { - if (slave.attrKnown === 1) { - if ((slave.attrXX < 100) && (slave.clitSetting === "women")) { - makeSpan(c, `SP: women.`); - } else if ((slave.attrXY < 100) && (slave.clitSetting === "men")) { - makeSpan(c, `SP: men.`); - } - } else { - if (slave.clitSetting === "women") { - makeSpan(c, `SP: women.`); - } else if (slave.clitSetting === "men") { - makeSpan(c, `SP: men.`); - } - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function long_behavior_flaw(slave, c) { - function descStr() { - switch (slave.behavioralFlaw) { - case "arrogant": - return `Arrogant.`; - case "bitchy": - return `Bitchy.`; - case "odd": - return `Odd.`; - case "hates men": - return `Hates men.`; - case "hates women": - return `Hates women.`; - case "gluttonous": - return `Stress eater.`; - case "anorexic": - return `Anorexic.`; - case "devout": - return `Devoutly religious.`; - case "liberated": - return `Mentally liberated.`; - default: - slave.behavioralFlaw = "none"; - return null; - } - } - const s = descStr(); - if (s) { - makeSpan(c, s, "red"); - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function long_sex_flaw(slave, c) { - switch (slave.sexualFlaw) { - case "hates oral": - case "hates anal": - case "hates penetration": - case "shamefast": - makeSpan(c, slave.sexualFlaw, "red", true); - break; - case "idealistic": - case "repressed": - case "apathetic": - case "crude": - case "judgemental": - makeSpan(c, `Sexually ${slave.sexualFlaw}.`, "red"); - break; - case "cum addict": - case "anal addict": - case "attention whore": - makeSpan(c, slave.sexualFlaw, "yellow", true); - break; - case "breast growth": - makeSpan(c, `Breast obsession.`, "yellow"); - break; - case "abusive": - case "malicious": - makeSpan(c, `Sexually ${slave.sexualFlaw}.`, "yellow"); - break; - case "self hating": - makeSpan(c, `Self hatred.`, "yellow"); - break; - case "neglectful": - makeSpan(c, `Self neglectful.`, "yellow"); - break; - case "breeder": - makeSpan(c, `Breeding obsession.`, "yellow"); - break; - default: - slave.sexualFlaw = "none"; - break; - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function long_behavior_quirk(slave, c) { - switch (slave.behavioralQuirk) { - case "confident": - case "cutting": - case "funny": - case "fitness": - case "adores women": - case "adores men": - case "insecure": - case "sinful": - case "advocate": - makeSpan(c, slave.behavioralQuirk, "green", true); - break; - default: - slave.behavioralQuirk = "none"; - break; - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function long_sex_quirk(slave, c) { - switch (slave.sexualQuirk) { - case "gagfuck queen": - case "painal queen": - case "strugglefuck queen": - case "tease": - case "romantic": - case "perverted": - case "caring": - case "unflinching": - case "size queen": - makeSpan(c, slave.sexualQuirk, "green", true); - break; - default: - slave.sexualQuirk = "none"; - break; - } - } + function displayOptionsFragment() { + const res = document.createDocumentFragment(); + + // SAH is "Summarized, Abbreviated, Hidden" + const SAHOptions = { + "Summarized": 2, + "Abbreviated": 1, + "Hidden": 0 + }; + + function appendOption(name, desc, options) { + res.append(App.UI.DOM.Widgets.optionSelector((value) => { + V.UI.slaveSummary.abbreviation[name] = value; Engine.play(passage()); + }, + options, V.UI.slaveSummary.abbreviation[name], desc)); + } + + function appendSAHOption(name, desc) { + appendOption(name, desc, SAHOptions); + } + + appendSAHOption("devotion", "Mental stats are"); + appendSAHOption("mental", "Mental attributes are"); + appendSAHOption("rules", "Rules are"); + appendSAHOption("health", "Health is"); + appendSAHOption("diet", "Diet and weight are"); + appendSAHOption("drugs", "Drugs and addiction are"); + appendSAHOption("hormoneBalance", "Hormone balance is"); + appendSAHOption("genitalia", "Genitalia are"); + appendSAHOption("physicals", "Physical traits are"); + appendSAHOption("skills", "Skills are"); + appendSAHOption("nationality", "Nationality is"); + appendSAHOption("race", "Race is"); + appendOption("rulesets", "Rules Assistant rulesets are", { + "Summarized": 2, + "Abbreviated": 1 + }); + appendOption("clothes", "Clothes are", { + "Summarized": 2, + "Hidden": 0 + }); + appendOption("origins", "Origins are", { + "Summarized": 2, + "Hidden": 0 + }); - /** - * @param {App.Entity.SlaveState} slave - * @returns {string} - */ - function short_extended_family(slave) { - let res = ""; - let handled = 0; - if (slave.mother > 0) { - const _ssj = V.slaves.findIndex(s => s.ID === slave.mother); - if (_ssj !== -1) { - res += `${SlaveFullName(V.slaves[_ssj])}'s ${getPronouns(slave).daughter}`; - if (slave.relationshipTarget === V.slaves[_ssj].ID) { - const friendShipShort = relationshipTermShort(slave); - res += ` & ${friendShipShort}`; - handled = 1; - } - } - res += " "; - } else if (slave.mother === -1) { - res += `Your ${getPronouns(slave).daughter}`; - if (slave.relationship < -1) { - res += ` & ${PCrelationshipTerm(slave)}`; - handled = 1; - } - res += " "; - } else if (slave.mother in V.missingTable && V.showMissingSlavesSD && V.showMissingSlaves) { - res += `${V.missingTable[slave.mother].fullName}'s ${getPronouns(slave).daughter} `; - } - if (slave.father > 0 && slave.father !== slave.mother) { - const _ssj = V.slaves.findIndex(s => s.ID === slave.father); - if (_ssj !== -1) { - res += `${SlaveFullName(V.slaves[_ssj])}'s ${getPronouns(slave).daughter}`; - if (slave.relationshipTarget === V.slaves[_ssj].ID && handled !== 1) { - const friendShipShort = relationshipTermShort(slave); - res += ` & ${friendShipShort}`; - handled = 1; - } - } - res += " "; - } else if (slave.father === -1 && slave.mother !== -1) { - res += `Your ${getPronouns(slave).daughter}`; - if (slave.relationship < -1) { - res += ` & ${PCrelationshipTerm(slave)}`; - handled = 1; - } - res += " "; - } else if (slave.father in V.missingTable && slave.father !== slave.mother && V.showMissingSlavesSD && V.showMissingSlaves) { - res += `${V.missingTable[slave.father].fullName}'s ${getPronouns(slave).daughter}`; - } - if (slave.daughters === 1) { - let _ssj = V.slaves.findIndex(s => s.mother === slave.ID); - if (_ssj !== -1) { - res += `${SlaveFullName(V.slaves[_ssj])}'s mother`; - if (slave.relationshipTarget === V.slaves[_ssj].ID) { - const friendShipShort = relationshipTermShort(slave); - res += ` & ${friendShipShort}`; - handled = 1; - } - } - res += " "; - _ssj = V.slaves.findIndex(s => s.father === slave.ID); - if (_ssj !== -1) { - res += `${SlaveFullName(V.slaves[_ssj])}'s father`; - if (slave.relationshipTarget === V.slaves[_ssj].ID && handled !== 1) { - const friendShipShort = relationshipTermShort(slave); - res += ` & ${friendShipShort}`; - handled = 1; - } - } - res += " "; - } else if (slave.daughters > 1) { - res += `multiple daughters `; - } - if (slave.sisters === 1) { - const _ssj = V.slaves.findIndex(s => areSisters(s, slave) > 0); - if (_ssj !== -1) { - res += `${SlaveFullName(V.slaves[_ssj])}'s ${getPronouns(slave).sister}`; - if (slave.relationshipTarget === V.slaves[_ssj].ID) { - const friendShipShort = relationshipTermShort(slave); - res += `& ${friendShipShort}`; - handled = 1; - } - } - res += " "; - } else if (slave.sisters > 1) { - res += `multiple sisters `; - } - if (slave.relationship > 0 && handled !== 1) { - const _ssj = V.slaves.findIndex(s => s.ID === slave.relationshipTarget); - if (_ssj !== -1) { - res += `${SlaveFullName(V.slaves[_ssj])}'s`; - const friendShipShort = relationshipTermShort(slave); - res += ` ${friendShipShort}`; - } - } else if (slave.relationship === -3 && slave.mother !== -1 && slave.father !== -1) { - res += `Your ${getPronouns(slave).wife}`; - } else if (slave.relationship === -2) { - res += `E Bonded`; - } else if (slave.relationship === -1) { - res += `E Slut`; - } return res; } - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function short_clone(slave, c) { - if (slave.clone !== 0) { - makeSpan(c, "Clone"); - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function short_rival(slave, c) { - if (slave.rivalry !== 0) { - const block = makeBlock(c, "lightsalmon"); - const _ssj = V.slaves.findIndex(s => s.ID === slave.rivalryTarget); - if (_ssj !== -1) { - if (slave.rivalry <= 1) { - block.textContent = `Disl ${SlaveFullName(V.slaves[_ssj])}`; - } else if (slave.rivalry <= 2) { - block.textContent = `${SlaveFullName(V.slaves[_ssj])}'s rival`; - } else { - block.textContent = `Hates ${SlaveFullName(V.slaves[_ssj])}`; - } - } - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function long_extended_family(slave, c) { - let handled = 0; - const block = makeBlock(); - if (slave.mother > 0) { - const _ssj = V.slaves.findIndex(s => s.ID === slave.mother); - if (_ssj !== -1) { - addText(block, `${SlaveFullName(V.slaves[_ssj])}'s `); - const tmpSpan = makeSpan(block, getPronouns(slave).daughter, "lightgreen"); - if (slave.relationshipTarget === V.slaves[_ssj].ID) { - const friendShipShort = relationshipTerm(slave); - tmpSpan.textContent += ` and ${friendShipShort}`; - handled = 1; - } - tmpSpan.textContent += '.'; - } - } else if (slave.mother === -1) { - addText(block, `Your `); - if (slave.relationship < -1) { - makeSpan(block, `${getPronouns(slave).daughter} and ${PCrelationshipTerm(slave)}.`, "lightgreen"); - handled = 1; - } else { - makeSpan(block, `${getPronouns(slave).daughter}.`, "lightgreen"); - } - } else if (slave.mother in V.missingTable && V.showMissingSlavesSD && V.showMissingSlaves) { - addText(block, `${V.missingTable[slave.mother].fullName}'s `); - makeSpan(block, `${getPronouns(slave).daughter}.`, "lightgreen"); - } - if (slave.father > 0 && slave.father !== slave.mother) { - const _ssj = V.slaves.findIndex(s => s.ID === slave.father); - if (_ssj !== -1) { - addText(block, `${SlaveFullName(V.slaves[_ssj])}'s `); - const tmpSpan = makeSpan(block, getPronouns(slave).daughter, "lightgreen"); - if (slave.relationshipTarget === V.slaves[_ssj].ID) { - const friendShipShort = relationshipTerm(slave); - tmpSpan.textContent += ` and ${friendShipShort}`; - handled = 1; - } - tmpSpan.textContent += '.'; - } - } else if (slave.father === -1 && slave.father !== slave.mother) { - addText(block, `Your `); - if (slave.relationship < -1) { - makeSpan(block, `${getPronouns(slave).daughter} and ${PCrelationshipTerm(slave)}.`, "lightgreen"); - handled = 1; - } else { - makeSpan(block, `${getPronouns(slave).daughter}.`, "lightgreen"); - } - } else if (slave.father in V.missingTable && slave.father !== slave.mother && V.showMissingSlavesSD && V.showMissingSlaves) { - addText(block, `${V.missingTable[slave.father].fullName}'s `); - makeSpan(block, `${getPronouns(slave).daughter}.`, "lightgreen"); - } - if (areSisters(V.PC, slave) > 0) { - addText(block, `Your `); - if (slave.relationship < -1) { - makeSpan(block, `${relativeTerm(V.PC, slave)} and ${PCrelationshipTerm(slave)}.`, "lightgreen"); - handled = 1; - } else { - makeSpan(block, `${relativeTerm(V.PC, slave)}.`, "lightgreen"); - } - } - if (slave.daughters === 1) { - let _ssj = V.slaves.findIndex(s => s.mother === slave.ID); - if (_ssj !== -1) { - addText(block, `${SlaveFullName(V.slaves[_ssj])}'s `); - const tmpSpan = makeSpan(block, "mother", "lightgreen"); - if (slave.relationshipTarget === V.slaves[_ssj].ID) { - const friendShipShort = relationshipTerm(slave); - tmpSpan.textContent += ` and ${friendShipShort}`; - handled = 1; - } - tmpSpan.textContent += '.'; - } - _ssj = V.slaves.findIndex(s => s.father === slave.ID); - if (_ssj !== -1) { - addText(block, `${SlaveFullName(V.slaves[_ssj])}'s `); - const tmpSpan = makeSpan(block, "father", "lightgreen"); - if (slave.relationshipTarget === V.slaves[_ssj].ID) { - const friendShipShort = relationshipTerm(slave); - tmpSpan.textContent += ` and ${friendShipShort}`; - handled = 1; - } - tmpSpan.textContent += '.'; - } - } else if (slave.daughters > 1) { - if (slave.daughters > 10) { - makeSpan(block, "Has tons of daughters.", "lightgreen"); - } else if (slave.daughters > 5) { - makeSpan(block, "Has many daughters.", "lightgreen"); - } else { - makeSpan(block, "Has several daughters.", "lightgreen"); - } - } - if (slave.sisters === 1) { - const _ssj = V.slaves.findIndex(s => areSisters(s, slave) > 0); - if (_ssj !== -1) { - addText(block, `${SlaveFullName(V.slaves[_ssj])}'s `); - const tmpSpan = makeSpan(block, getPronouns(slave).sister, "lightgreen"); - if (slave.relationshipTarget === V.slaves[_ssj].ID) { - const friendShipShort = relationshipTerm(slave); - tmpSpan.textContent += ` and ${friendShipShort}`; - handled = 1; - } - tmpSpan.textContent += '.'; - } - } else if (slave.sisters > 1) { - if (slave.sisters > 10) { - makeSpan(block, "One of many sisters.", "lightgreen"); - } else if (slave.sisters > 5) { - makeSpan(block, "Has many sisters.", "lightgreen"); - } else { - makeSpan(block, "Has several sisters.", "lightgreen"); - } - } - if (slave.relationship > 0 && handled !== 1) { - const _ssj = V.slaves.findIndex(s => s.ID === slave.relationshipTarget); - if (_ssj !== -1) { - const friendship = relationshipTerm(slave); - addText(block, `${SlaveFullName(V.slaves[_ssj])}'s `); - makeSpan(block, `${friendship}.`, "lightgreen"); - } - } else if (slave.relationship === -3 && slave.mother !== -1 && slave.father !== -1 && areSisters(V.PC, slave) === 0) { - makeSpan(block, `Your ${getPronouns(slave).wife}.`, "lightgreen"); - } else if (slave.relationship === -2) { - makeSpan(block, "Emotionally bonded to you.", "lightgreen"); - } else if (slave.relationship === -1) { - makeSpan(block, "Emotional slut.", "lightgreen"); - } - - if (block.textContent.length > 0) { - c.appendChild(block); - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function long_clone(slave, c) { - if (slave.clone !== 0) { - makeSpan(c, `Clone of ${slave.clone}.`, "skyblue"); - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function long_rival(slave, c) { - if (slave.rivalry !== 0) { - const block = makeBlock(c); - const _ssj = V.slaves.findIndex(s => s.ID === slave.rivalryTarget); - if (_ssj !== -1) { - if (slave.rivalry <= 1) { - makeSpan(block, "Dislikes", "lightsalmon"); - block.appendChild(document.createTextNode(` ${SlaveFullName(V.slaves[_ssj])}.`)); - } else if (slave.rivalry <= 2) { - block.appendChild(document.createTextNode(`${SlaveFullName(V.slaves[_ssj])}'s `)); - makeSpan(block, "rival.", "lightsalmon"); - } else { - makeSpan(block, "Hates", "lightsalmon"); - block.appendChild(document.createTextNode(` ${SlaveFullName(V.slaves[_ssj])}.`)); - } - } - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function long_clothes(slave, c) { - function descStr() { - switch (slave.clothes) { - case "attractive lingerie": - return `Nice lingerie.`; - case "a succubus outfit": - return `Succubus outfit.`; - case "a string bikini": - return `String bikini.`; - case "a scalemail bikini": - return `Scalemail bikini.`; - case "striped panties": - return `Striped panties.`; - case "a monokini": - return `Monokini.`; - case "an apron": - return `Apron.`; - case "a cybersuit": - return `Cybersuit.`; - case "cutoffs and a t-shirt": - return `Cutoffs, t-shirt.`; - case "a slutty outfit": - return `Slutty outfit.`; - case "uncomfortable straps": - return `Leather straps.`; - case "a fallen nuns habit": - return `Slutty habit.`; - case "a chattel habit": - return `Chattel habit.`; - case "a penitent nuns habit": - return `Cilice.`; - case "slutty jewelry": - return `Bangles.`; - case "attractive lingerie for a pregnant woman": - return `Preggo lingerie.`; - case "a maternity dress": - return `Maternity dress.`; - case "stretch pants and a crop-top": - return `Stretch pants, crop-top.`; - case "harem gauze": - return `Harem outfit.`; - case "a slave gown": - return `Slave gown.`; - case "a halter top dress": - return `Halter top dress.`; - case "a mini dress": - return `Mini dress.`; - case "a ball gown": - return `Ball gown.`; - case "slutty business attire": - return `Slutty suit.`; - case "nice business attire": - return `Nice suit.`; - case "a comfortable bodysuit": - return `Bodysuit.`; - case "a military uniform": - return `Military uniform.`; - case "a schutzstaffel uniform": - return `Schutzstaffel uniform.`; - case "a slutty schutzstaffel uniform": - return `Slutty Schutzstaffel uniform.`; - case "a red army uniform": - return `Red Army uniform.`; - case "a long qipao": - return `Long Qipao.`; - case "battlearmor": - return `Battlearmor.`; - case "a mounty outfit": - return `Mounty outfit.`; - case "a dirndl": - return `Dirndl.`; - case "lederhosen": - return `Lederhosen.`; - case "a biyelgee costume": - return `Biyelgee costume.`; - case "a leotard": - return `Leotard.`; - case "a bunny outfit": - return `Bunny outfit.`; - case "a slutty maid outfit": - return `Slutty maid.`; - case "a nice maid outfit": - return `Nice maid.`; - case "a slutty nurse outfit": - return `Slutty nurse.`; - case "a nice nurse outfit": - return `Nice nurse.`; - case "a schoolgirl outfit": - return `Schoolgirl outfit.`; - case "a kimono": - return `Kimono.`; - case "a hijab and abaya": - return `Hijab and abaya.`; - case "battledress": - return `Battledress.`; - case "a latex catsuit": - return `Nice latex.`; - case "restrictive latex": - return `Bondage latex.`; - case "conservative clothing": - return `Conservative clothing.`; - case "chains": - return `Chains.`; - case "overalls": - return `Overalls.`; - case "a cheerleader outfit": - return `Cheerleader.`; - case "clubslut netting": - return `Netting.`; - case "shibari ropes": - return `Shibari.`; - case "Western clothing": - return `Chaps.`; - case "body oil": - return `Body oil.`; - case "a toga": - return `Toga.`; - case "a huipil": - return `Huipil.`; - case "a slutty qipao": - return `Slutty qipao.`; - case "spats and a tank top": - return `Spats, tank top.`; - case "a burkini": - return `Burkini.`; - case "a niqab and abaya": - return `Niqab and abaya.`; - case "a klan robe": - return `Klan robe.`; - case "a hijab and blouse": - return `Hijab and blouse.`; - case "a burqa": - return `Burqa.`; - case "kitty lingerie": - return `Kitty lingerie.`; - case "a tube top and thong": - return `Tube top, thong.`; - case "a button-up shirt and panties": - return `Button-up shirt, panties.`; - case "a gothic lolita dress": - return `Gothic lolita dress.`; - case "a hanbok": - return `Hanbok.`; - case "a bra": - return `Nice bra.`; - case "a button-up shirt": - return `Nice button-up shirt.`; - case "a nice pony outfit": - return `Nice pony outfit.`; - case "a sweater": - return `Nice sweater.`; - case "a tank-top": - return `Nice tank-top.`; - case "a thong": - return `Nice thong.`; - case "a tube top": - return `Nice tube top.`; - case "a one-piece swimsuit": - return `Swimsuit.`; - case "a police uniform": - return `Police uniform.`; - case "a striped bra": - return `Striped bra.`; - case "a skimpy loincloth": - return `Skimpy loincloth.`; - case "a slutty klan robe": - return `Slutty klan robe.`; - case "a slutty pony outfit": - return `Slutty pony outfit.`; - case "a Santa dress": - return `Santa dress.`; - case "a sports bra": - return `Sports bra.`; - case "a sweater and panties": - return `Sweater, panties.`; - case "a t-shirt": - return `T-shirt.`; - case "a tank-top and panties": - return `Tank-top, panties.`; - case "a t-shirt and thong": - return `Thong, t-shirt.`; - case "an oversized t-shirt and boyshorts": - return `Over-sized t-shirt, boy shorts.`; - case "an oversized t-shirt": - return `Nice over-sized t-shirt.`; - case "a t-shirt and jeans": - return `Blue jeans, t-shirt.`; - case "boyshorts": - return `Boy shorts.`; - case "cutoffs": - return `Jean shorts.`; - case "leather pants and pasties": - return `Leather pants, pasties.`; - case "leather pants": - return `Nice leather pants.`; - case "panties": - return `Nice panties.`; - case "sport shorts and a t-shirt": - return `Nice sport shorts, shirt.`; - case "a t-shirt and panties": - return `Panties, t-shirt.`; - case "panties and pasties": - return `Pasties, panties.`; - case "pasties": - return `Pasties.`; - case "striped underwear": - return `Striped underwear`; - case "sport shorts and a sports bra": - return `Shorts, bra.`; - case "jeans": - return `Tight blue jeans.`; - case "a sweater and cutoffs": - return `Jean shorts, sweater.`; - case "leather pants and a tube top": - return `Leather pants, tube top.`; - case "sport shorts": - return `Shorts.`; - case "a bimbo outfit": - return `Bimbo outfit.`; - case "a courtesan dress": - return `Courtesan dress.`; - default: - return `Naked.`; - } - } - makeSpan(c, descStr()); - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function long_collar(slave, c) { - function descStr() { - switch (slave.collar) { - case "uncomfortable leather": - return `Leather collar.`; - case "tight steel": - return `Steel collar.`; - case "preg biometrics": - return `Pregnancy biometrics collar.`; - case "cruel retirement counter": - return `Cruel counter collar.`; - case "shock punishment": - return `Shock collar.`; - case "dildo gag": - return `Dildo gag.`; - case "massive dildo gag": - return `Throat-bulging dildo gag.`; - case "neck corset": - return `Neck corset.`; - case "stylish leather": - return `Stylish leather collar.`; - case "satin choker": - return `Satin choker.`; - case "silk ribbon": - return `Silken ribbon.`; - case "heavy gold": - return `Gold collar.`; - case "bowtie": - return `Bowtie collar.`; - case "pretty jewelry": - return `Pretty collar.`; - case "nice retirement counter": - return `Nice counter collar.`; - case "bell collar": - return `Bell collar.`; - case "leather with cowbell": - return `Cowbell collar.`; - case "ancient Egyptian": - return `Wesekh.`; - case "ball gag": - return `Ball gag.`; - case "bit gag": - return `Bit gag.`; - case "porcelain mask": - return `Porcelain mask.`; - default: - return null; - } - } - const s = descStr(); - if (s) { - makeSpan(c, s); - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function long_belly(slave, c) { - function descStr() { - switch (slave.bellyAccessory) { - case "shapewear": - return `Shapewear.`; - case "a small empathy belly": - return `Small fake belly.`; - case "a medium empathy belly": - return `Medium fake belly.`; - case "a large empathy belly": - return `Large fake belly.`; - case "a huge empathy belly": - return `Huge fake belly.`; - case "a corset": - return `Corset.`; - case "an extreme corset": - return `Extreme corsetage.`; - case "a support band": - return `Support band.`; - default: - return null; - } - } - const s = descStr(); - if (s) { - makeSpan(c, s); - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function long_arms(slave, c) { - if (["hand gloves", "elbow gloves"].includes(slave.armAccessory)) { - makeSpan(c, slave.armAccessory, undefined, true); - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function long_legs(slave, c) { - if (slave.legAccessory === "short stockings") { - makeSpan(c, "Short stockings."); - } else if (slave.legAccessory === "long stockings") { - makeSpan(c, "Long stockings."); - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function long_shoes(slave, c) { - if (["boots", "extreme heels", "extreme platform heels", "flats", "heels", "platform heels", "platform shoes", "pumps"].includes(slave.shoes)) { - makeSpan(c, slave.shoes, undefined, true); - } else if (slave.heels === 1) { - makeSpan(c, "Crawling.", "yellow"); - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function long_chastity(slave, c) { - if (slave.chastityAnus === 1 && slave.chastityPenis === 1 && slave.chastityVagina === 1) { - makeSpan(c, "Full chastity."); - } else if (slave.chastityPenis === 1 && slave.chastityVagina === 1) { - makeSpan(c, "Genital chastity."); - } else if ((slave.chastityAnus === 1 && slave.chastityVagina === 1) || (slave.chastityAnus === 1 && slave.chastityPenis === 1)) { - makeSpan(c, "Combined chastity."); - } else if (slave.chastityVagina === 1) { - makeSpan(c, "Vaginal chastity."); - } else if (slave.chastityPenis === 1) { - makeSpan(c, "Chastity cage."); - } else if (slave.chastityAnus === 1) { - makeSpan(c, "Anal chastity."); - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function long_vaginal_acc(slave, c) { - if (slave.vaginalAttachment !== "vibrator") { - switch (slave.vaginalAccessory) { - case "bullet vibrator": - makeSpan(c, "Attached bullet vibrator."); - break; - case "smart bullet vibrator": - makeSpan(c, "Attached smart bullet vibrator."); - break; - case "dildo": - makeSpan(c, "Vaginal dildo."); - break; - case "large dildo": - makeSpan(c, "Large vaginal dildo."); - break; - case "huge dildo": - makeSpan(c, "Huge vaginal dildo."); - break; - case "long dildo": - makeSpan(c, "Long vaginal dildo."); - break; - case "long, large dildo": - makeSpan(c, "Long and large vaginal dildo."); - break; - case "long, huge dildo": - makeSpan(c, "Long and wide vaginal dildo."); - break; - } - } - if (slave.vaginalAttachment !== "none") { - switch (slave.vaginalAttachment) { - case "vibrator": - makeSpan(c, "Vibrating dildo."); - break; - } - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function long_dick_acc(slave, c) { - switch (slave.dickAccessory) { - case "sock": - makeSpan(c, "Cock sock."); - break; - case "bullet vibrator": - makeSpan(c, "Frenulum bullet vibrator."); - break; - case "smart bullet vibrator": - makeSpan(c, "Smart frenulum bullet vibrator."); - break; - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function long_buttplug(slave, c) { - switch (slave.buttplug) { - case "plug": - makeSpan(c, "Buttplug."); - break; - case "large plug": - makeSpan(c, "Large buttplug."); - break; - case "huge plug": - makeSpan(c, "Huge buttplug."); - break; - case "long plug": - makeSpan(c, "Long buttplug."); - break; - case "long, large plug": - makeSpan(c, "Large, long buttplug."); - break; - case "long, huge plug": - makeSpan(c, "Enormous buttplug."); - break; - } - switch (slave.buttplugAttachment) { - case "tail": - makeSpan(c, "Attached tail."); - break; - case "cat tail": - makeSpan(c, "Attached cat tail."); - break; - case "fox tail": - makeSpan(c, "Attached fox tail."); - break; - case "cow tail": - makeSpan(c, "Attached cow tail."); - break; - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function rules_assistant(slave, c) { - if (slave.useRulesAssistant === 0) { - makeSpan(c, "RA-Exempt", "lightgreen"); - } else if (V.abbreviateRulesets === 2 && (slave.currentRules !== undefined) && (slave.currentRules.length > 0)) { - c.innerHTML = `Rules: ${V.defaultRules.filter(x => ruleApplied(slave, x)).map(x => x.name).join(", ")}`; - } - } - - /** - * @param {App.Entity.SlaveState} slave - * @param {Node} c - * @returns {void} - */ - function origins(slave, c) { - const para = makeParagraph(c); - para.classList.add("gray"); - const pronouns = getPronouns(slave); - para.textContent = `${slave.origin.replace(/\$([A-Z]?[a-z]+)/g, (match, cap1) => pronouns[cap1] || match)}`; - } - - /** - * @param {HTMLElement} element - * @param {string|string[]} [classNames] - */ - function _addClassNames(element, classNames) { - if (classNames != undefined) { /* eslint-disable-line eqeqeq */ - if (Array.isArray(classNames)) { - element.classList.add(...classNames); - } else { - element.classList.add(classNames); - } - } - } - - /** - * @param {Node} container - * @param {string} text - * @param {string|string[]} [classNames] - * @param {boolean} [stdDecor=false] - * @param {number} [value] - */ - function makeSpan(container, text, classNames, stdDecor = false, value) { - let r = document.createElement("span"); - r.classList.add("ssi"); - _addClassNames(r, classNames); - if (value != undefined && V.summaryStats) { /* eslint-disable-line eqeqeq */ - text += `[${value}]`; - } - r.textContent = stdDecor ? `${capFirstChar(text)}.` : text; - if (container) { - container.appendChild(r); - } - return r; - } - - /** - * @param {Node} container - * @param {string} text - * @returns {Text} - */ - function addText(container, text) { - const r = document.createTextNode(text); - if (container) { - container.appendChild(r); - } - return r; - } - - /** - * @param {Node} [container] - * @param {string|string[]} [classNames] - */ - function makeBlock(container, classNames) { - let r = document.createElement("span"); - r.classList.add("ssb"); - _addClassNames(r, classNames); - if (container) { - container.appendChild(r); - } - return r; - } - - /** - * @param {Node} container - * @param {string|string[]} [classNames] - * @returns {HTMLParagraphElement} - */ - function makeParagraph(container, classNames) { - let r = document.createElement("p"); - r.classList.add("si"); - _addClassNames(r, classNames); - if (container) { - container.appendChild(r); - } - return r; - } - - return SlaveSummary; -})(); + return { + makeNewState: makeNewState, + settingsChanged: settingsChanged, + societyChanged: societyChanged, + render: render, + displayOptionsFragment: displayOptionsFragment + }; +}(); diff --git a/src/uncategorized/fsDevelopments.tw b/src/uncategorized/fsDevelopments.tw index b32cd29bc31d84da14275459bc071d8fe0b89d40..32433250947b235aac603a21e374c108a6d6de8a 100644 --- a/src/uncategorized/fsDevelopments.tw +++ b/src/uncategorized/fsDevelopments.tw @@ -1297,3 +1297,5 @@ <</if>> <</if>> <</if>> + +<<run App.UI.SlaveSummary.societyChanged()>> diff --git a/src/uncategorized/policies.tw b/src/uncategorized/policies.tw index 677cdced99dee85e636853ca48062b3edd5292da..9646d6669cfc93f2c7629039cde9a31dcd5f101f 100644 --- a/src/uncategorized/policies.tw +++ b/src/uncategorized/policies.tw @@ -7,6 +7,7 @@ </style> <<set $nextButton = "Back">> +<<set _passageSwitchHandler = App.EventHandlers.societyChanged>> <<if $arcologies[0].FSSupremacistLawME && $PC.race != $arcologies[0].FSSupremacistRace>> <<set $nextLink = "Gameover", $gameover = "Idiot Ball 2 The Dumbassening">> <<elseif $arcologies[0].FSSubjugationistLawME && $PC.race == $arcologies[0].FSSubjugationistRace>> diff --git a/src/uncategorized/slaveInteract.tw b/src/uncategorized/slaveInteract.tw index 52f4425885c3cc02e173d9a52d53b106c88b573b..b2842a8b890b8a72a23c5f7b2bb4af2e9a8d5ca4 100644 --- a/src/uncategorized/slaveInteract.tw +++ b/src/uncategorized/slaveInteract.tw @@ -432,37 +432,11 @@ <</if>> <</if>> - <br><br>Non-assignment orgasm rules: - <div style="text-indent:2em"> - Masturbation is <span id="relMasturbation" style="font-weight:bold"><<if getSlave($AS).rules.release.masturbation === 1>>allowed<<else>>forbidden<</if>></span>. - <<link "Allow">><<set getSlave($AS).rules.release.masturbation = 1>><<replace "#relMasturbation">>allowed<</replace>><</link>> | - <<link "Forbid">><<set getSlave($AS).rules.release.masturbation = 0>><<replace "#relMasturbation">>forbidden<</replace>><</link>> - </div> - <div style="text-indent:2em"> - Sex with romantic partner is <span id="relPartner" style="font-weight:bold"><<if getSlave($AS).rules.release.partner === 1>>allowed<<else>>forbidden<</if>></span>. - <<link "Allow">><<set getSlave($AS).rules.release.partner = 1>><<replace "#relPartner">>allowed<</replace>><</link>> | - <<link "Forbid">><<set getSlave($AS).rules.release.partner = 0>><<replace "#relPartner">>forbidden<</replace>><</link>> - </div> - <<if $seeIncest == 1>> - <div style="text-indent:2em"> - Sex with close family is <span id="relFamily" style="font-weight:bold"><<if getSlave($AS).rules.release.family === 1>>allowed<<else>>forbidden<</if>></span>. - <<link "Allow">><<set getSlave($AS).rules.release.family = 1>><<replace "#relFamily">>allowed<</replace>><</link>> | - <<link "Forbid">><<set getSlave($AS).rules.release.family = 0>><<replace "#relFamily">>forbidden<</replace>><</link>> - </div> - <</if>> - <div style="text-indent:2em"> - Sex with other slaves is <span id="relSlaves" style="font-weight:bold"><<if getSlave($AS).rules.release.slaves === 1>>allowed<<else>>forbidden<</if>></span>. - <<link "Allow">><<set getSlave($AS).rules.release.slaves = 1>><<replace "#relSlaves">>allowed<</replace>><</link>> | - <<link "Forbid">><<set getSlave($AS).rules.release.slaves = 0>><<replace "#relSlaves">>forbidden<</replace>><</link>> - </div> - <div style="text-indent:2em"> - Routine sex with <<= properMaster()>> is <span id="relMaster" style="font-weight:bold"><<if getSlave($AS).rules.release.master === 1>>granted<<else>>denied<</if>></span>. - <<link "Grant">><<set getSlave($AS).rules.release.master = 1>><<replace "#relMaster">>granted<</replace>><</link>> | - <<link "Deny">><<set getSlave($AS).rules.release.master = 0>><<replace "#relMaster">>denied<</replace>><</link>> - </div> + <span id="orgasm"></span> + <script>App.UI.SlaveInteract.orgasm(getSlave(V.activeSlave.ID))</script> <<if getSlave($AS).voice != 0>> - <br>Speech rules: <span id="speechRules" style="font-weight:bold"><<= getSlave($AS).rules.speech>></span>. + Speech rules: <span id="speechRules" style="font-weight:bold"><<= getSlave($AS).rules.speech>></span>. <<link "Restrictive">><<set getSlave($AS).rules.speech = "restrictive">><<replace "#speechRules">><<= getSlave($AS).rules.speech>><</replace>><</link>> | <<link "Permissive">><<set getSlave($AS).rules.speech = "permissive">><<replace "#speechRules">><<= getSlave($AS).rules.speech>><</replace>><</link>> <<if getSlave($AS).accent > 0 && getSlave($AS).accent < 4>>| <<link "Accent elimination">><<set getSlave($AS).rules.speech = "accent elimination">><<replace "#speechRules">><<= getSlave($AS).rules.speech>><</replace>><</link>> @@ -476,39 +450,8 @@ <<link "Just friends">><<set getSlave($AS).rules.relationship = "just friends">><<replace "#relationshipRules">><<= getSlave($AS).rules.relationship>><</replace>><</link>> | <<link "Permissive">><<set getSlave($AS).rules.relationship = "permissive">><<replace "#relationshipRules">><<= getSlave($AS).rules.relationship>><</replace>><</link>> <</if>> - - <<if getSlave($AS).clitPiercing == 3 || getSlave($AS).vaginalAccessory == "smart bullet vibrator">> - <div> - <<if getSlave($AS).clitPiercing == 3>> - <<if getSlave($AS).dick < 1>> - $His smart clit piercing <<if getSlave($AS).vaginalAccessory == "smart bullet vibrator">>and smart bullet vibrator are<<else>>is<</if>> set to - <<else>> - $His smart frenulum piercing <<if getSlave($AS).vaginalAccessory == "smart bullet vibrator">>and smart bullet vibrator are<<else>>is<</if>> set to - <</if>> - <<else>> - $His smart bullet vibe is set to - <</if>> - <span id="setting" style="font-weight:bold"><<= getSlave($AS).clitSetting>></span>. - <<link "Vanilla">><<set getSlave($AS).clitSetting = "vanilla">><<replace "#setting">><<= getSlave($AS).clitSetting>><</replace>><</link>> - | <<link "Oral">><<set getSlave($AS).clitSetting = "oral">><<replace "#setting">><<= getSlave($AS).clitSetting>><</replace>><</link>> - | <<link "Anal">><<set getSlave($AS).clitSetting = "anal">><<replace "#setting">><<= getSlave($AS).clitSetting>><</replace>><</link>> - | <<link "Boobs">><<set getSlave($AS).clitSetting = "boobs">><<replace "#setting">><<= getSlave($AS).clitSetting>><</replace>><</link>> - | <<link "Sub">><<set getSlave($AS).clitSetting = "submissive">><<replace "#setting">><<= getSlave($AS).clitSetting>><</replace>><</link>> - | <<link "Dom">><<set getSlave($AS).clitSetting = "dom">><<replace "#setting">><<= getSlave($AS).clitSetting>><</replace>><</link>> - | <<link "Humiliation">><<set getSlave($AS).clitSetting = "humiliation">><<replace "#setting">><<= getSlave($AS).clitSetting>><</replace>><</link>> - <<if $seePreg != 0>> - | <<link "Preg">><<set getSlave($AS).clitSetting = "pregnancy">><<replace "#setting">><<= getSlave($AS).clitSetting>><</replace>><</link>> - <</if>> - | <<link "Pain">><<set getSlave($AS).clitSetting = "masochist">><<replace "#setting">><<= getSlave($AS).clitSetting>><</replace>><</link>> - | <<link "Sadism">><<set getSlave($AS).clitSetting = "sadist">><<replace "#setting">><<= getSlave($AS).clitSetting>><</replace>><</link>> - | <<link "Men">><<set getSlave($AS).clitSetting = "men">><<replace "#setting">><<= getSlave($AS).clitSetting>><</replace>><</link>> - | <<link "Anti-men">><<set getSlave($AS).clitSetting = "anti-men">><<replace "#setting">><<= getSlave($AS).clitSetting>><</replace>><</link>> - | <<link "Women">><<set getSlave($AS).clitSetting = "women">><<replace "#setting">><<= getSlave($AS).clitSetting>><</replace>><</link>> - | <<link "Anti-women">><<set getSlave($AS).clitSetting = "anti-women">><<replace "#setting">><<= getSlave($AS).clitSetting>><</replace>><</link>> - | <<link "All sex">><<set getSlave($AS).clitSetting = "all">><<replace "#setting">><<= getSlave($AS).clitSetting>><</replace>><</link>> - | <<link "No sex">><<set getSlave($AS).clitSetting = "none">><<replace "#setting">><<= getSlave($AS).clitSetting>><</replace>><</link>> - </div> - <</if>> + <span id="smartSettings"></span> + <script>App.UI.SlaveInteract.smartSettings(getSlave(V.activeSlave.ID))</script> </div> </div> diff --git a/src/uncategorized/summaryOptions.tw b/src/uncategorized/summaryOptions.tw index 507d35f4e91a147484f70da5603a9405cc706033..c839f964e8536e79801fe1106d8c59dcae6a1875 100644 --- a/src/uncategorized/summaryOptions.tw +++ b/src/uncategorized/summaryOptions.tw @@ -70,95 +70,7 @@ Sample summary: <<option 2 "Card">> <</options>> -<<options $abbreviateDevotion "Summary Options">> - Mental stats are - <<option 2 "Summarized">> - <<option 1 "Abbreviated">> - <<option 0 "Hidden">> -<</options>> - -<<options $abbreviateMental "Summary Options">> - Mental attributes are - <<option 2 "Summarized">> - <<option 1 "Abbreviated">> - <<option 0 "Hidden">> -<</options>> - -<<options $abbreviateRules "Summary Options">> - Rules are - <<option 2 "Summarized">> - <<option 1 "Abbreviated">> - <<option 0 "Hidden">> -<</options>> - -<<options $abbreviateHealth "Summary Options">> - Health is - <<option 2 "Summarized">> - <<option 1 "Abbreviated">> - <<option 0 "Hidden">> -<</options>> - -<<options $abbreviateDiet "Summary Options">> - Diet and weight are - <<option 2 "Summarized">> - <<option 1 "Abbreviated">> - <<option 0 "Hidden">> -<</options>> - -<<options $abbreviateDrugs "Summary Options">> - Drugs and addiction are - <<option 2 "Summarized">> - <<option 1 "Abbreviated">> - <<option 0 "Hidden">> -<</options>> - -<<options $abbreviateHormoneBalance "Summary Options">> - Hormone balance is - <<option 2 "Summarized">> - <<option 1 "Abbreviated">> - <<option 0 "Hidden">> -<</options>> - -<<options $abbreviateGenitalia "Summary Options">> - Genitalia are - <<option 2 "Summarized">> - <<option 1 "Abbreviated">> - <<option 0 "Hidden">> -<</options>> - -<<options $abbreviatePhysicals "Summary Options">> - Physical traits are - <<option 2 "Summarized">> - <<option 1 "Abbreviated">> - <<option 0 "Hidden">> -<</options>> - -<<options $abbreviateSkills "Summary Options">> - Skills are - <<option 2 "Summarized">> - <<option 1 "Abbreviated">> - <<option 0 "Hidden">> -<</options>> - -<<options $abbreviateNationality "Summary Options">> - Nationality is - <<option 2 "Summarized">> - <<option 1 "Abbreviated">> - <<option 0 "Hidden">> -<</options>> - -<<options $abbreviateRace "Summary Options">> - Race is - <<option 2 "Summarized">> - <<option 1 "Abbreviated">> - <<option 0 "Hidden">> -<</options>> - -<<options $abbreviateRulesets "Summary Options">> - Rules Assistant rulesets are - <<option 2 "Summarized">> - <<option 1 "Abbreviated">> -<</options>> +<<= App.UI.DOM.includeDOM(App.UI.SlaveSummary.displayOptionsFragment(), "dof") >> <<options $summaryStats "Summary Options">> Granular slave stat numbers are @@ -172,12 +84,6 @@ Main menu assignment shortcuts are <<option 0 "Hidden">> <</options>> -<<options $abbreviateOrigins "Summary Options">> - Origins are - <<option 2 "Summarized">> - <<option 0 "Hidden">> -<</options>> - <<if $showMissingSlaves>> <<options $showMissingSlavesSD "Summary Options">> Missing slave parents are @@ -187,5 +93,5 @@ Main menu assignment shortcuts are <</if>> <p style="font-style:italic"> - [[FC Dev's preferred options|Summary Options ][$seeDesk = 0, $seeFCNN = 0, $sortSlavesBy = "devotion",$sortSlavesOrder = "descending",$sortSlavesMain = 0,$rulesAssistantMain = 1,$abbreviateDevotion = 1,$abbreviateRules = 1,$abbreviateClothes = 2,$abbreviateHealth = 1,$abbreviateDiet = 1,$abbreviateDrugs = 1,$abbreviateRace = 1,$abbreviateGenitalia = 1,$abbreviatePhysicals = 1,$abbreviateSkills = 1,$abbreviateMental = 1,$abbreviateSidebar = 1]] + [[FC Dev's preferred options|Summary Options ][$seeDesk = 0, $seeFCNN = 0, $sortSlavesBy = "devotion",$sortSlavesOrder = "descending",$sortSlavesMain = 0,$rulesAssistantMain = 1, $UI.slaveSummary.abbreviation = {devotion: 1, mental: 1, rules: 1, clothes: 2, health: 1, diet: 1, drugs: 1, hormoneBalance: 1, race: 1, genitalia: 1, physicals: 1, skills: 1, nationality: 1, rulesets: 1, clothes: 0, origins: 0} ,$abbreviateSidebar = 1]] </p> diff --git a/src/zz1-last/setupEventHandlers.js b/src/zz1-last/setupEventHandlers.js index 9a41b2959c9a850010c0079626bf5252ab8ae5ee..190756ccced5b1ba246a99d25876336893d5d9dc 100644 --- a/src/zz1-last/setupEventHandlers.js +++ b/src/zz1-last/setupEventHandlers.js @@ -5,3 +5,7 @@ Config.saves.onSave = App.EventHandlers.onSave; $(document).on(':storyready', function(ev) { App.EventHandlers.storyReady(); }); + +$(document).one(':passagestart', function() { + App.EventHandlers.optionsChanged(); +});