PlayerCharacter.ts 69 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510
  1. declare let rand: Window["rand"];
  2. const timed_stat_changes ={ //Hourly
  3. energy: {
  4. sleep: 2,
  5. default: 6
  6. },
  7. hydra: {
  8. sleep: 4,
  9. default: 12
  10. },
  11. sleep:{
  12. sleep: -8,
  13. default: 4
  14. }
  15. }
  16. const dieRisks = {
  17. 'hunger':{
  18. variable: 'pcs_energy',
  19. durations: [720,720,14400]
  20. },
  21. 'thirst':{
  22. variable: 'pcs_hydra',
  23. durations: [720,720,1440]
  24. }
  25. }
  26. const fameAlwaysLocale = ['sex','prostitute'];
  27. class PlayerCharacter{
  28. get INSTANCE():PlayerCharacter{return State.variables.pc;}
  29. gameover:string;
  30. _death = {};
  31. //#region Name
  32. name_first = 'Svetlana';
  33. name_last = 'Lebedev';
  34. name_nick = 'Sveta';
  35. get name_full(){return `${this.name_first} ${this.name_last}`;}
  36. //#endregion
  37. //#region Images
  38. /**
  39. * A custom path to an image-file for the portrait of the character.
  40. * @type {string}
  41. */
  42. avatar:string = '';
  43. /**
  44. * Path of an image-file that is used as the portrait of the character.
  45. * Uses `avatar` if set, otherwise a combination of hair length and color as specified by `:: $face_image`.
  46. * @readonly
  47. * @type {string}
  48. */
  49. get image():string{
  50. return this.avatar || this.bodyImage('hair');
  51. }
  52. bodyImage(part:string|undefined=undefined):string{
  53. const wardrobe = State.variables.wardrobe;
  54. switch(part){
  55. case undefined:
  56. case 'body':
  57. /*
  58. <<if ($pc.knowpreg == 1 or ($pc.preg == 1 and $pc.thinkpreg == 1) or ($pc.preg == 1 and $pc.PregChem > 3600)) and $pc.bodset == 3>>
  59. <<if $pc.PregChem > 6216>>
  60. <<set $result = 'pc/body/shape/'+$bodimgsets[(($pc.bodset * 10) + 9)]+'/8.jpg'>>
  61. <<elseif $pc.PregChem < 2688>>
  62. <<set $result = 'pc/body/shape/'+$bodimgsets[(($pc.bodset * 10) + 9)]+'/0.jpg'>>
  63. <<else>>
  64. <<set $result = 'pc/body/shape/'+$bodimgsets[(($pc.bodset * 10) + 9)]+'/(($pc.PregChem - 2184) / 504).jpg'>>
  65. <</if>>
  66. <<elseif $pc.salocatnow >= 1 and $pc.salocatnow <= 5>>
  67. <<set $result = 'pc/body/shape/'+$bodimgsets[(($pc.bodset * 10) + 9)]+'/'+$pc.salocatnow+'.jpg'>>
  68. <<elseif $pc.salocatnow <= 0>>
  69. <<set $result = 'pc/body/shape/0.jpg'>>
  70. <<elseif $pc.salocatnow == 6>>
  71. <<if getvar("$imgset6ovr["+$pc.bodset+"]") == 1>>
  72. <<set $result = 'pc/body/shape/'+$bodimgsets[(($pc.bodset * 10) + 9)]+'/6.jpg'>>
  73. <<else>>
  74. <<set $result = 'pc/body/shape/6.jpg'>>
  75. <</if>>
  76. <<else>>
  77. <<if getvar("$imgset7ovr["+$pc.bodset+"]") == 1>>
  78. <<set $result = 'pc/body/shape/'+$bodimgsets[(($pc.bodset * 10) + 9)]+'/7.jpg'>>
  79. <<else>>
  80. <<set $result = 'pc/body/shape/7.jpg'>>
  81. <</if>>
  82. <</if>>
  83. */
  84. let imageIndex = Math.clamp(this.bmiCategory+2,1,5);
  85. return `pc/body/shape/default_low/${imageIndex}.jpg`;
  86. case 'bra':
  87. if(wardrobe.bra.isValidItem)
  88. return wardrobe.bra.image;
  89. return this.bodyImage('breasts');
  90. case 'clothes':
  91. /*
  92. <<if $wardrobe.clothingworntype == 'nude' and getvar("$towel") == 1 and !$wardrobe.isWearingPanties>>
  93. <<set $result = 'pc/body/towel.jpg'>>
  94. <<elseif $wardrobe.clothingworntype == 'nude' and getvar("$robe") == 1>>
  95. <<set $result = 'pc/body/robe.jpg'>>
  96. <<elseif $wardrobe.clothingworntype == 'nude' and $wardrobe.isWearingPanties>>
  97. <<set $result = 'pc/body/nude.jpg'>>
  98. <<elseif $wardrobe.clothingworntype == 'nude' and !$wardrobe.isWearingPanties>>
  99. <<set $result = 'pc/body/nude1.jpg'>>
  100. <<elseif $wardrobe.clothingworntype == 'misc_outfits' and $wardrobe.clothingwornnumber == 1>>
  101. <<set $result = setup.func('$clothing_image', $wardrobe.clothingworntype, $wardrobe.clothingwornnumber)>>
  102. <<else>>
  103. <<set $result = $wardrobe.clothes.image>>
  104. <!--
  105. <<set $result = setup.func('$clothing_image', $wardrobe.clothingworntype, $wardrobe.clothingwornnumber)>>
  106. <<if getvar("$PClobimbo") == 1>>
  107. <</if>> -->
  108. <!--<<if $wardrobe.clothingworntype != 'coat' and !$wardrobe.isWearingSwimwear>>
  109. <<gs 'clothing_attributes' $wardrobe.clothingworntype $wardrobe.clothingwornnumber>>
  110. <<gs 'clothing_descriptions'>>
  111. <<else>>
  112. <<if $wardrobe.clothingworntype == 'danilovich_swimsuit'>>
  113. setup.func('$attributes_danilovich_swim_one', $wardrobe.clothingworntype, clothingwornnumber)
  114. <<elseif $wardrobe.clothingworntype == 'scandalicious_swimsuit'>>
  115. setup.func('$attributes_scandalicious_swim_one', $wardrobe.clothingworntype, clothingwornnumber)
  116. <<elseif $wardrobe.clothingworntype == 'scandalicious_bikinis'>>
  117. setup.func('$attributes_scandalicious_swim_two', $wardrobe.clothingworntype, clothingwornnumber)
  118. <<elseif $wardrobe.clothingworntype == 'allure_swimsuit'>>
  119. setup.func('$attributes_allure_swim_one', $wardrobe.clothingworntype, clothingwornnumber)
  120. <<elseif $wardrobe.clothingworntype == 'allure_bikinis'>>
  121. setup.func('$attributes_allure_swim_two', $wardrobe.clothingworntype, clothingwornnumber)
  122. <<elseif $wardrobe.clothingworntype == 'nerdvana_swimsuit'>>
  123. setup.func('$attributes_nerdvana_swim_one', $wardrobe.clothingworntype, clothingwornnumber)
  124. <<elseif $wardrobe.clothingworntype == 'nerdvana_bikinis'>>
  125. setup.func('$attributes_nerdvana_swim_two', $wardrobe.clothingworntype, clothingwornnumber)
  126. <</if>>
  127. <</if>>
  128. <<if $wardrobe.clothingworntype == 'gm_maid' or $wardrobe.PCloStyle2 == 1>>
  129. <<elseif $wardrobe.clothingworntype == 'gm_server' or $wardrobe.PCloStyle2 == 2>>
  130. <<elseif $wardrobe.clothingworntype == 'eroto_strip' or $wardrobe.PCloStyle2 == 3>>
  131. <</if>>-->
  132. <</if>>
  133. */
  134. if(wardrobe.clothes.isValidItem)
  135. return wardrobe.clothes.image;
  136. if(wardrobe.panties.isValidItem)
  137. return 'pc/body/nude.jpg';
  138. return 'pc/body/nude1.jpg';
  139. case 'hair':
  140. return setup.func("$face_image");
  141. case 'coat':
  142. if(wardrobe.coat.isValidItem)
  143. return wardrobe.coat.image;
  144. return '';
  145. case 'panties':
  146. if(wardrobe.panties.isValidItem)
  147. return wardrobe.panties.image;
  148. return setup.func('$pube_image');
  149. case 'shoes':
  150. if(wardrobe.shoes.isValidItem)
  151. return wardrobe.shoes.image;
  152. return 'pc/body/feet.jpg';
  153. case 'tits':
  154. case 'breasts':
  155. /*
  156. <<if $pc.nipples >= 40 and $pc.nipples < 60 and $pc.tits == 2>>
  157. <<set $result = 'pc/body/tits/t'+$pc.tits+'_p.jpg'>>
  158. <<else>>
  159. <<set $result = 'pc/body/tits/t'+$pc.tits+'.jpg'>>
  160. <</if>>
  161. */
  162. return 'pc/body/tits/t'+this.tits+'.jpg';
  163. }
  164. return '';
  165. }
  166. /*<<case 'bodysuit'>>
  167. <<if !$wardrobe.isWearingBra>>
  168. <<set $result = 'pc/body/tits/ttits.jpg'>>
  169. <<else>>
  170. <<set $result = setup.func('$pcs_outfit_image', $pc.bodysuitworntype+'_bodysuits', bodysuitwornnumber)>>
  171. <</if>>*/
  172. /*
  173. <<case 'teeth'>>
  174. <<if getvar("$pcs_brace") == 1>>
  175. <<set $result ='pc/body/teeth/brace.jpg'>>
  176. <<elseif $pc.pcs_teeth == -1>>
  177. <<set $result ='pc/body/teeth/goodteeth.jpg'>>
  178. <<elseif $pc.pcs_teeth == 1>>
  179. <<set $result ='pc/body/teeth/badteeth1.jpg'>>
  180. <<elseif $pc.pcs_teeth == 2>>
  181. <<set $result ='pc/body/teeth/badteeth2.jpg'>>
  182. <<else>>
  183. <<set $result ='pc/body/teeth/averageteeth.jpg'>>
  184. <</if>>*/
  185. //#endregion
  186. dailyUpdate(){
  187. this.bodyDailyUpdate();
  188. }
  189. //#region Birthday & Age
  190. birthday = 1;
  191. birthmonth = 4;
  192. birthyear = 1999;
  193. get birthdayDate(){
  194. return new Date(Date.UTC(this.birthyear,this.birthmonth-1,this.birthday));
  195. }
  196. get age(){
  197. let age = State.variables.time.ageOfDate(this.birthday,this.birthmonth,this.birthyear);
  198. if(age < 16)
  199. {
  200. console.error("Critical Error: Playercharacter too young");
  201. Engine.restart();
  202. }
  203. return age;
  204. }
  205. set age(v){
  206. v = Math.max(v,16);
  207. let currentAge = this.age;
  208. let difference = v - currentAge;
  209. this.birthyear -= difference;
  210. console.log("AGE set to "+v);
  211. }
  212. //#endregion
  213. //#region Visual Age
  214. _visualAgeDaysOffset = 0;
  215. get visualAgeDays(){return this._visualAgeDaysOffset}
  216. set visualAgeDays(v){this._visualAgeDaysOffset = v;}
  217. get visualAgeDaysInverse(){
  218. return this._visualAgeDaysOffset * -1;
  219. }
  220. set visualAgeDaysInverse(v){
  221. this._visualAgeDaysOffset = v * -1;
  222. }
  223. get visualAge(){
  224. return State.variables.time.ageOfDate(this.visualBirthday);
  225. }
  226. set visualAge(v){
  227. let currentVisualAge = this.visualAge;
  228. let offset = v - currentVisualAge;
  229. this._visualAgeDaysOffset += Math.floor(offset * 365.25);
  230. }
  231. get visualBirthday(){
  232. let visualBirthday = this.birthdayDate;
  233. visualBirthday.setUTCDate(visualBirthday.getUTCDate() - this._visualAgeDaysOffset);
  234. return visualBirthday;
  235. }
  236. //#endregion
  237. //#region Mood
  238. _mood = new setup.Mood();
  239. get mood(){return this._mood.mood;}
  240. set mood(v){this._mood.mood = v}
  241. //get moodlets(){return this._mood.moodlets}
  242. get moodletsActive(){return this._mood.moodletsActive}
  243. get moodletsActiveByGroup(){return this._mood.moodletsActiveByGroup}
  244. get moodletsActiveEffect(){return this._mood.moodletsActiveEffect}
  245. get moodletsActiveByGroupAccumulationApplied(){return this._mood.moodletsActiveByGroupAccumulationApplied}
  246. moodletApplyById(moodletId,minutes=0){return this._mood.moodletApplyById(moodletId,minutes)}
  247. //moodletCombinedData(moodletId){return this._mood.moodletCombinedData(moodletId)}
  248. moodletDeactivateById(moodletId){return this._mood.moodletDeactivateById(moodletId)}
  249. moodletIncTime(moodletId,minutes,skipIncludedMoodlets=false){return this._mood.moodletIncTime(moodletId,minutes,skipIncludedMoodlets)}
  250. //moodletIsActive(moodletId){return this._mood.moodletIsActive(moodletId)}
  251. moodletUpdate(moodletId,updateObject){return this._mood.moodletUpdate(moodletId,updateObject)}
  252. #moodletsClean(){return this._mood._moodletsClean();}
  253. get moodletsSpecial():{[key: string]: ActiveMoodlet}{
  254. //return Object.assign({},this.moodletPain,this.activeEffectsMoodlets);
  255. return {pain:this.moodletPain};
  256. }
  257. get moodletPain():ActiveMoodlet{
  258. return PainMoodlet.createPainMoodlet(this.painTotal);
  259. }
  260. //#endregion
  261. //#region Personality
  262. //#region Deprecated
  263. // ----- Personality -----
  264. _pcs_dom = 0
  265. get pcs_dom(){return this._pcs_dom;}
  266. set pcs_dom(v){
  267. if(v < 0){
  268. this.pcs_sub -= v;
  269. this._pcs_dom = 0;
  270. }else{
  271. this._pcs_dom = Math.min(100,v);
  272. this._balanceDomSub();
  273. }
  274. }
  275. _pcs_sub = 0
  276. get pcs_sub(){return this._pcs_sub;}
  277. set pcs_sub(v){
  278. if(v < 0){
  279. this.pcs_dom -= v;
  280. this._pcs_sub = 0;
  281. }else{
  282. this._pcs_sub = Math.min(100,v);
  283. this._balanceDomSub();
  284. }
  285. }
  286. _balanceDomSub(){
  287. if(this._pcs_dom > 0 && this._pcs_sub > this._pcs_dom){
  288. this._pcs_sub -= this._pcs_dom;
  289. this._pcs_dom = 0;
  290. }
  291. if(this._pcs_sub > 0 && this._pcs_dom > this._pcs_sub){
  292. this._pcs_dom -= this._pcs_sub;
  293. this._pcs_sub = 0;
  294. }
  295. }
  296. //#endregion
  297. //#endregion
  298. //#region Mental Capacity
  299. get consciousness(){
  300. return this.#activeEffectValueByKey('consciousness','*');
  301. }
  302. _pcs_willpwr = 70;
  303. get pcs_willpwr(){return this._pcs_willpwr;}
  304. set pcs_willpwr(v){
  305. this._pcs_willpwr = Math.clamp(v,0,this.willpowermax);
  306. console.log("Willpower set to "+this._pcs_willpwr);
  307. }
  308. will_counter = 0;
  309. _willpowermax = 70;
  310. get willpowermax(){return this._willpowermax}
  311. set willpowermax(v){this._willpowermax = Math.max(50,v);}
  312. pcs_willpower_feeder = 0;
  313. //#endregion
  314. //#region Main Stats (Hunger, Thirst & Sleep)
  315. consume(consumable:string|Consumable,percentage=100){
  316. if(typeof consumable == 'string')
  317. consumable = Consumable.get(consumable);
  318. consumable.consume(this,percentage);
  319. }
  320. //#region Hunger and Eating
  321. _energyBalance = 0; // A value between -6 and 6 that determines how fast you gain or lose bmi.
  322. _energy = 0; // The energy you consumed today.
  323. _energyDemand = 0; // Todays energy demand. Get updated every 15 minutes along witht the update of the hunger bar. Can also be increased by doing sports.
  324. _pcs_energy = 100;
  325. /**
  326. * This is effecitvely the hunger-bar.
  327. * @date 7/23/2023 - 5:56:29 PM
  328. *
  329. * @type {number}
  330. */
  331. get pcs_energy(){return this._pcs_energy;}
  332. set pcs_energy(v){
  333. this._pcs_energy = Math.clamp(v,0,100);
  334. if(v > 0 && this._death['hunger']?.stage){
  335. this._death['hunger'] = {stage:0};
  336. for(let i=1;i<=dieRisks.hunger.durations.length;i++)
  337. this.moodletDeactivateById('hunger_'+i);
  338. }
  339. }
  340. _dieHungerStage = 0;
  341. _dieHungerNextStageDate = undefined;
  342. /**
  343. * Intake in calories. Influences BMI in the long run.
  344. * @type {number}
  345. */
  346. get energy(){return this._energy;}
  347. set energy(v){this._energy = v;}
  348. get energyBalance(){return this._energyBalance;}
  349. set energyBalance(v){this._energyBalance = v;}
  350. get energyDemand(){return this._energyDemand;}
  351. set energyDemand(v){this._energyDemand = v;}
  352. /**
  353. * Energy Demand adjusted for the current BMI.
  354. * @date 7/27/2023 - 11:47:05 AM
  355. *
  356. * @readonly
  357. * @type {number}
  358. */
  359. get energyRequirement(){
  360. let baseRequirement = this.energyDemand; //timed_stat_changes.energy.sleep * 8 + timed_stat_changes.energy.default * 16;
  361. let requirementFactor = baseRequirement / 100;
  362. let bmi = this.bmi;
  363. // The following calculations assume that the base requirement is 100, therefore we need to multiplicate them with requirementFactor
  364. if(bmi < 19)
  365. return requirementFactor * (100 - Math.sqrt(19-bmi) * 10);
  366. if (bmi > 25)
  367. return requirementFactor * (100 + 5 * (bmi - 25));
  368. return baseRequirement;
  369. }
  370. _energyHistory = [];
  371. get energyHistory(){return this._energyHistory;}
  372. _energyHistoryLengthTarget = 7;
  373. get energyHistoryLengthTarget(){return this._energyHistoryLengthTarget;}
  374. set energyHistoryLengthTarget(v){
  375. this._energyHistoryLengthTarget = v;
  376. while(this._energyHistory.length > v)
  377. this._energyHistory.shift();
  378. }
  379. dailyEnergyUpdate(){
  380. let energyRequirement = this.energyRequirement;
  381. let energy = this.energy;
  382. let energyQuota = energy / energyRequirement;
  383. let balanceTarget = 0;
  384. if(energyQuota < 0.5)
  385. balanceTarget = -6;
  386. else if(energyQuota < 0.8)
  387. balanceTarget = -3;
  388. else if(energyQuota > 1.5)
  389. balanceTarget = 6;
  390. else if(energyQuota > 1.2)
  391. balanceTarget = 3;
  392. let balanceCurrent = this.energyBalance;
  393. let balanceDifference = balanceTarget - balanceCurrent;
  394. // Gaining weight is way easier than losing it.
  395. if(balanceDifference < 0)
  396. balanceDifference = balanceDifference / 3;
  397. else
  398. balanceDifference = balanceDifference * 2 / 3;
  399. this.energyBalance += balanceDifference;
  400. // Change the BMI
  401. let bmi_change = Math.pow(this.energyBalance,2) * 0.005 * Math.sign(this.energyBalance);
  402. this.bmi += bmi_change;
  403. console.log("$pc.dailyEnergyUpdate(): energyRequirement,energy,balanceOld, balanceNew, bmiChange, bmiNew",energyRequirement,energy,balanceCurrent,this.energyBalance,bmi_change,this.bmi);
  404. this._energyHistory.push({
  405. energyRequirement: energyRequirement,
  406. energy: energy,
  407. balanceTarget: balanceTarget,
  408. balanceChange: balanceDifference,
  409. balanceNew: this.energyBalance,
  410. bmiChange: bmi_change,
  411. bmiNew: this.bmi
  412. });
  413. this.energy = 0;
  414. this.energyDemand = 0;
  415. }
  416. get pcs_weight(){
  417. return Math.pow(this.height / 100,2) * this.bmi;
  418. }
  419. _bmi = 20;
  420. get bmi(){
  421. return this._bmi;
  422. }
  423. set bmi(v){
  424. this._bmi = v;
  425. }
  426. /**
  427. * Returns the bmi-category
  428. *
  429. * @readonly
  430. * @type {(-2|-1|0|1|2|3|4|5)} -2: severely underweight, -1: underweight, 0: normal, 1: overweight, 2-5: increasingly obese
  431. */
  432. get bmiCategory():-2|-1|0|1|2|3|4|5{
  433. let bmi = this.bmi;
  434. if(bmi<16)
  435. return -2;
  436. if(bmi<18.5)
  437. return -1;
  438. if(bmi<25)
  439. return 0;
  440. if(bmi<30)
  441. return 1;
  442. if(bmi<35)
  443. return 2;
  444. if(bmi<40)
  445. return 3;
  446. if(bmi<45)
  447. return 4;
  448. return 5;
  449. }
  450. //#endregion
  451. //#region Drinking
  452. _pcs_hydra = 100;
  453. _dieThirstStage = 0;
  454. _dieThirstNextStageDate = undefined;
  455. get pcs_hydra(){return this._pcs_hydra;}
  456. set pcs_hydra(v){
  457. /*if(v < 0){
  458. v = 0;
  459. this.pcs_health -= 5;
  460. setup.msgPredefined("warn_hydra_low");
  461. }
  462. */
  463. this._pcs_hydra = Math.clamp(v,0,100);
  464. if(v > 0 && this._death['thirst']?.stage){
  465. this._death['thirst'] = {stage:0};
  466. for(let i=1;i<=dieRisks.thirst.durations.length;i++)
  467. this.moodletDeactivateById('thirst_'+i);
  468. }
  469. }
  470. //#endregion
  471. //#region Sleep
  472. _pcs_sleep = 100;
  473. get pcs_sleep(){return this._pcs_sleep;}
  474. set pcs_sleep(v){
  475. /*if(v < 0){
  476. v = 0;
  477. this.mood -= 5;
  478. setup.msgPredefined("warn_sleep_low");
  479. }*/
  480. this._pcs_sleep = Math.clamp(v,0,100);
  481. console.log("Sleep set to "+this._pcs_sleep);
  482. }
  483. isSleeping = 0;
  484. //#endregion
  485. minutesTilStat(stat,target=0,isAsleep=undefined){
  486. const time = State.variables.time;
  487. isAsleep ??= (this.isSleeping == 1);
  488. let current = 0;
  489. let configIndex;
  490. switch(stat){
  491. case 'energy':
  492. case 'hunger':
  493. configIndex = 'energy';
  494. current = this.pcs_energy;
  495. break;
  496. case 'hydra':
  497. case 'thirst':
  498. configIndex = 'hydra';
  499. current = this.pcs_hydra;
  500. break;
  501. }
  502. const timeMod = 0.25 * this.timeFactor;
  503. let change = 0;
  504. if(isAsleep)
  505. change = timed_stat_changes[configIndex]['sleep'] * timeMod;
  506. else
  507. change = timed_stat_changes[configIndex]['default'] * timeMod;
  508. let requiredUpdates = Math.ceil((current - target) / (change || 1));
  509. const minutesToNext15MinutesInterval = 15 - time.now.getUTCMinutes() % 15;
  510. return Math.max(0,(requiredUpdates - 1)*15 + minutesToNext15MinutesInterval);
  511. }
  512. //#endregion
  513. //#region Body Odor
  514. deodorant_on = 0;
  515. deodorant_time = 0;
  516. _pcs_sweat = 0;
  517. get pcs_sweat(){return this._pcs_sweat;}
  518. set pcs_sweat(v){
  519. this._pcs_sweat = v;
  520. console.log("Sweat set to "+this._pcs_sweat);
  521. }
  522. sweatAdd(v:number){
  523. this.pcs_sweat += v;
  524. }
  525. //#endregion
  526. //#region Arousal
  527. get hornyMin(){
  528. const cycleArousalModificator = this.cycleArousalModificator;
  529. return cycleArousalModificator.target;
  530. }
  531. _pcs_horny = 0;
  532. get horny(){return Math.max(this._pcs_horny,this.hornyMin);}
  533. set horny(v){
  534. if(typeof v != "number" || isNaN(v)){
  535. console.error("Trying to set pcs_horny to non-number",v);
  536. return;
  537. }
  538. this._pcs_horny = Math.clamp(v,0,100);
  539. console.log("Horny set to "+this._pcs_horny);
  540. }
  541. /**
  542. * How much horny is supposed to decrease each minute.
  543. * @readonly
  544. * @type {number}
  545. */
  546. get hornyDeteriorationRate(){
  547. const cycleArousalModificator = this.cycleArousalModificator;
  548. return (1 / cycleArousalModificator.factor);
  549. }
  550. //#endregion
  551. //#region Inhibition
  552. get pcs_inhib(){
  553. return this.skillLevel('inhibition');
  554. }
  555. set pcs_inhib(v){
  556. this.skillSetLevel('inhibition',v);
  557. }
  558. //#endregion
  559. //#region Frost
  560. _frost = 0;
  561. get frost(){
  562. if(this.alko > 0)
  563. return 0;
  564. return this._frost;
  565. }
  566. set frost(v){
  567. this._frost = v;
  568. }
  569. //#endregion
  570. //#region Drugs
  571. _drugs = new setup.Drugs();
  572. get drugsActiveEffects(){return this._drugs.activeEffects}
  573. get drugsActiveEffectIds(){return this._drugs.activeEffectIds}
  574. drugsDeteriorate(minutes){return this._drugs.deteriorate(minutes)}
  575. drugVolInc(drugId, inc){return this._drugs.volInc(drugId, inc)}
  576. drugVolSet(drugId,v){return this._drugs.volSet(drugId, v)}
  577. drugVol(drugId){return this._drugs.vol(drugId)}
  578. get alko(){return this.drugVol('alcohol')}
  579. set alko(v){this.drugVolSet('alcohol',v)}
  580. //#endregion
  581. //#region Appearance History
  582. _appearanceHistory:Array<{time:Date,varname:string,val:string|number}> = [];
  583. appearanceHistoryPush(varname:string,val:string|number){
  584. this._appearanceHistory.push({
  585. time: State.variables.time.now,
  586. varname: varname,
  587. val: val
  588. });
  589. }
  590. //#endregion
  591. //#region Face
  592. //#region Eyes
  593. /**
  594. * 0: brown
  595. * 1: grey
  596. * 2: green
  597. * 3: blue
  598. * @type {(0|1|2|3)}
  599. */
  600. eyecolor:0|1|2|3 = 3;
  601. /**
  602. * 0: small
  603. * 1: medium
  604. * 2: large
  605. * 3: huge
  606. * @type {(0|1|2|3)}
  607. */
  608. eyesize:0|1|2|3 = 1;
  609. //#endregion
  610. faceGeneticAttractiveness = 0;
  611. faceSurgeries = 0;
  612. get faceAttractiveness(){
  613. return Math.clamp(this.faceGeneticAttractiveness+this.faceSurgeries,-3,3);
  614. }
  615. //#endregion
  616. // ----- Body ------
  617. _pcs_vag = 0
  618. get pcs_vag(){return this._pcs_vag;}
  619. set pcs_vag(v){this._pcs_vag = Math.min(36,v);}
  620. _pcs_ass = 0
  621. get pcs_ass(){return this._pcs_ass;}
  622. set pcs_ass(v){this._pcs_ass = Math.min(36,v);}
  623. _pcs_throat = 0
  624. get pcs_throat(){return this._pcs_throat;}
  625. set pcs_throat(v){this._pcs_throat = Math.min(36,v);}
  626. _pcs_health = 0;
  627. get pcs_health(){return Math.min(this._pcs_health, this.healthmax)}
  628. set pcs_health(v){
  629. /*if(v < 0)
  630. this.gameover = 1;*/
  631. this._pcs_health = Math.min(v, this.healthmax);
  632. }
  633. get healthmax(){
  634. let healthmax_calc = Math.max(1,(this.vitality * 10 + this.strength * 5));
  635. let mult_by_pain = 1;
  636. let pain_total = this.painTotal;
  637. if(pain_total > 80)
  638. mult_by_pain = 0.20;
  639. else if(pain_total > 60)
  640. mult_by_pain = 0.40;
  641. else if(pain_total > 40)
  642. mult_by_pain = 0.60;
  643. else if(pain_total > 20)
  644. mult_by_pain = 0.80;
  645. else if(pain_total > 0)
  646. mult_by_pain = 0.90;
  647. healthmax_calc = Math.ceil(healthmax_calc * mult_by_pain);
  648. return healthmax_calc;
  649. }
  650. _pcs_stam = 0;
  651. get pcs_stam(){return this._pcs_stam}
  652. set pcs_stam(v){this._pcs_stam = Math.min(v, this.stammax)}
  653. get stammax(){return Math.max(1,5 * (2 * this.vitality + this.agility + this.strength) / 2)}
  654. get speed(){return (2 * (this.strength + this.agility) + this.vitality) / 5}
  655. genbsize = 12; // the set genetic bust size
  656. nbsize = 12; // starts at a set genetic bust size, but can be adjusted down if salo drops too low
  657. silicone = 0;
  658. silicone_butt = 0;
  659. butt_cheat = 0;
  660. magicf2b = 0; //magicf2b = set in body_shape for the fat moved to bust
  661. /*get pcs_hips(){return (this.pcs_hgt * this.hratio) / 100 + this.vhips;}
  662. get pcs_waist(){return (this.pcs_hips * this.wratio) / 100 + this.vofat;}
  663. get pcs_band(){return (this.pcs_waist * this.bratio) / 100 + this.vofat;}
  664. get pcs_bust(){return (this.pcs_waist * this.bratio) / 100 + this.nbsize + this.magicf2b + this.silicone;}
  665. get pcs_butt(){return (this.pcs_hips / 10) + this.silicone_butt + this.butt_cheat;}*/
  666. //get pcs_cupsize(){return (this.pcs_bust - this.pcs_band);}
  667. _cupsize = 15;
  668. get pcs_cupsize(){return this._cupsize;}
  669. set pcs_cupsize(v){this._cupsize = v;}
  670. get tits(){
  671. if(this.pcs_cupsize <= 5) return 0;
  672. if(this.pcs_cupsize <= 10) return 1;
  673. if(this.pcs_cupsize <= 15) return 2;
  674. if(this.pcs_cupsize <= 20) return 3;
  675. if(this.pcs_cupsize <= 25) return 4;
  676. if(this.pcs_cupsize <= 30) return 5;
  677. if(this.pcs_cupsize <= 35) return 6;
  678. if(this.pcs_cupsize <= 40) return 7;
  679. if(this.pcs_cupsize <= 45) return 8;
  680. if(this.pcs_cupsize <= 50) return 9;
  681. if(this.pcs_cupsize <= 55) return 10;
  682. return 11;
  683. }
  684. set tits(v){
  685. this.pcs_cupsize = Math.ceil((v+1)*5);
  686. }
  687. /**
  688. * Cup size of the breasts.
  689. * @date 1/21/2024 - 12:20:29 PM
  690. *
  691. * @readonly
  692. * @type {("AA cup" | "A cup" | "B cup" | "C cup" | "D cup" | "E cup" | "F cup" | "G cup" | "H cup" | "I cup" | "J cup" | "K cup" | "??? cup")}
  693. */
  694. get titsize(){
  695. switch(this.tits){
  696. case 0: return 'AA cup';
  697. case 1: return 'A cup';
  698. case 2: return 'B cup';
  699. case 3: return 'C cup';
  700. case 4: return 'D cup';
  701. case 5: return 'E cup';
  702. case 6: return 'F cup';
  703. case 7: return 'G cup';
  704. case 8: return 'H cup';
  705. case 9: return 'I cup';
  706. case 10: return 'J cup';
  707. case 11: return 'K cup';
  708. default: return '??? cup';
  709. }
  710. }
  711. //#region Height
  712. pcs_hgt = 170;
  713. /**
  714. * The characters height in cm.
  715. * @type {number}
  716. */
  717. get height(){
  718. return this.pcs_hgt;
  719. }
  720. set height(v){
  721. this.pcs_hgt = v;
  722. }
  723. //#endregion
  724. //#region Hair
  725. //#region Head
  726. pcs_hairbsh = 0;
  727. pcs_hairlng = 300;
  728. /**
  729. * Hair Length in mm
  730. * @type {number}
  731. */
  732. get hairLength(){return this.pcs_hairlng;}
  733. set hairLength(v){this.pcs_hairlng = v;}
  734. hairColor = 0;
  735. hairColorNatural = 0;
  736. hairDyeFade = 0;
  737. hairDye(newColor,fadeDuration=undefined){
  738. this.appearanceHistoryPush('hairColor',newColor);
  739. this.hairColor = newColor;
  740. this.hairDyeFade = fadeDuration ?? 30;
  741. }
  742. //#endregion
  743. //#region Legs
  744. _leghair = 0;
  745. /**
  746. * The length of the hair on the legs in mm.
  747. * @date 7/23/2023 - 9:25:25 AM
  748. *
  749. * @type {number}
  750. */
  751. get legHair(){return this._leghair;}
  752. set legHair(v){this._leghair = v;}
  753. legHairState = 0; //0: default, 1: lasered
  754. get legHairGrowth(){
  755. switch(this.legHairState){
  756. case 0: return (this.age < 18 ? 0.14 : 0.21);
  757. case 1: return 0;
  758. }
  759. }
  760. get legHairIsLasered(){return (this.legHairState == 1);}
  761. set legHairIsLasered(v){
  762. if(v){
  763. this.legHair = 0;
  764. this.legHairState = 1;
  765. }else{
  766. this.legHairState = 0;
  767. }
  768. }
  769. get legHairVisibility(){
  770. if(this.legHair <= 0)
  771. return 0;
  772. else if(this.legHair <= 0.5) // ~3 Days
  773. return 1;
  774. else if(this.legHair <= 1.5) // ~1 Week
  775. return 2;
  776. else if(this.legHair <= 6) // ~1 Month
  777. return 3;
  778. return 4;
  779. }
  780. set legHairVisibility(v){
  781. switch (v) {
  782. case 0: this._leghair = 0; return;
  783. case 1: this._leghair = 0.2; return;
  784. case 2: this._leghair = 1; return;
  785. case 3: this._leghair = 4; return;
  786. default:this._leghair = 20; return;
  787. }
  788. }
  789. //#endregion
  790. //#region Pubes
  791. pubesLength = 0; // Pubes hair length in mm
  792. pubestyle = 0; // The style the pubes get shaved into. 0 is not shaving.
  793. pubesState = 0; // 0: Default, 1: lasered
  794. get pubesGrowth(){
  795. switch(this.pubesState){
  796. case 0: return 0.5;
  797. case 1: return 0;
  798. }
  799. }
  800. get pubesAreLasered(){return (this.pubesState == 1);}
  801. set pubesAreLasered(v){
  802. if(v){
  803. this.pubesLength = 0;
  804. this.pubesState = 1;
  805. }else{
  806. this.pubesState = 0;
  807. }
  808. }
  809. //#endregion
  810. //#endregion
  811. preg = 0; // 1: is pregnant, 0: is not pregnant
  812. knowpreg = 0; // 1: Is pregnant and knows it, 0: doesn't know is pregnant but could be
  813. thinkpreg = 0; // 1: thinks she is pregnant (doesn't have to be true), 0: doesn't think she is pregnant (doesn't have to be true either)
  814. PregChem = 0; // Size of the pregnancy
  815. clit_size = 0;
  816. get isPregnancyAware(){ // Either knows she is pregnant, correctly assumes to be pregnant or is big enough to no longer be in denial.
  817. if (this.knowpreg == 1) return 1;
  818. if (this.preg == 1 && this.thinkpreg == 1) return 1;
  819. if (this.preg == 1 && this.PregChem > 3600) return 1;
  820. return 0;
  821. }
  822. get bodset(){ //body image and descriptor control variable, used to indicate which image and descriptor set is in use
  823. /*if(this.isPregnancyAware == 1) return 3;
  824. if (this.muscularity >= 70) return 2;
  825. if (this.muscularity <= 40) return 0;*/
  826. return 1;
  827. }
  828. get body(){
  829. /*let bodimgsets = State.variables.bodimgsets;
  830. if(this.isPregnancyAware){
  831. if(this.PregChem > 6216)
  832. return bodimgsets[(this.bodset * 10) + 8];
  833. if(this.PregChem < 2688)
  834. return bodimgsets[this.bodset * 10];
  835. return bodimgsets[Math.floor(this.bodset * 10 + ((this.PregChem - 2184) / 504))];
  836. }
  837. if(this.salocatnow <= 7)
  838. return bodimgsets[((this.bodset * 10) + this.salocatnow)];
  839. return bodimgsets[(this.bodset * 10) + 7];*/
  840. return null;
  841. }
  842. //#region Teeth
  843. /*pcs_teeth = 0; //-1: perfectly white
  844. teeth = {
  845. brushed: 0,
  846. caffe_or_tea: 0,
  847. degradation:0 ,
  848. smoked: 0
  849. }
  850. get teethQuality(){return this.pcs_teeth}*/
  851. _teeth = 1;
  852. _teeethMissing = [];
  853. get teeth(){return this._teeth;}
  854. set teeth(v){this._teeth=Math.max(0,v);}
  855. get teethQuality(){
  856. let t = this.teeth;
  857. if(t < 100)
  858. return 0;
  859. else if(t < 1000)
  860. return 1;
  861. return 2;
  862. }
  863. set teethQuality(v){
  864. switch(v){
  865. case 0: this.teeth = 50; break;
  866. case 1: this.teeth = 500; break;
  867. case 2: this.teeth = 1500; break;
  868. }
  869. }
  870. get teethMissingCount(){
  871. return this._teeethMissing.length;
  872. }
  873. //#endregion
  874. //pcs_breath = 0;
  875. //#region Lipbalm
  876. _pcs_lipbalm = 0;
  877. get pcs_lipbalm(){return this._pcs_lipbalm;}
  878. set pcs_lipbalm(v){this._pcs_lipbalm = Math.max(0,v);}
  879. //#endregion
  880. //#region Skin Quality
  881. //moisturizerDailyCount = 0;
  882. //skinDailyGain = 0;
  883. //skinDailyPenalty = 0;
  884. _pcs_skin = 500;
  885. get pcs_skin(){
  886. return this._pcs_skin;
  887. }
  888. set pcs_skin(v){
  889. this._pcs_skin = Math.clamp(v,0,1000);
  890. }
  891. get skinAppearance(){
  892. return Math.round(this.pcs_skin / 200 - 2.5);
  893. }
  894. set skinAppearance(v){
  895. switch(v){
  896. case -3: this.pcs_skin = 0; return;
  897. case -2: this.pcs_skin = 100; return;
  898. case -1: this.pcs_skin = 300; return;
  899. case 0: this.pcs_skin = 500; return;
  900. case 1: this.pcs_skin = 700; return;
  901. case 2: this.pcs_skin = 900; return;
  902. case 3: this.pcs_skin = 1000; return;
  903. }
  904. }
  905. tan = 0;
  906. //#endregion
  907. bodyDailyUpdate(){
  908. this.dailyEnergyUpdate();
  909. /*if(this.muscularity > this.strength)
  910. this.muscularity -= 1;
  911. else if(this.muscularity < this.strength)
  912. this.muscularity += 1;
  913. if(this.healthiness > this.vitality)
  914. this.healthiness -= 1;
  915. else if(this.healthiness < this.vitality)
  916. this.healthiness += 1;
  917. if(this.dexterity > this.agility)
  918. this.dexterity -= 1;
  919. else if(this.dexterity < this.agility)
  920. this.dexterity += 1;*/
  921. /*if(this.fat > (17 + this.healthiness / 25)){
  922. this.salo += 1;
  923. this.fat = 0;
  924. }
  925. else if(this.fat < (-2 - (this.healthiness / 10))){
  926. this.salo -= 1;
  927. this.fat = 0;
  928. }
  929. this.fat = this.fat / 4;
  930. this.bodySaloCalc();*/
  931. this.hairLength += 1;
  932. /*if(this.pcs_lashes > 2){
  933. if(this.lashextensionstyle >= 1){
  934. this.lashextensionduration -= 1;
  935. if(this.lashextensionduration >= 1 && this.lashextensionduration <= 4)
  936. message("warn","It's time for you to do your maintenance on your lash extensions; you should go to the salon or you risk growing them all out.");
  937. if(this.lashextensionduration <= 0){
  938. message("bad","You waited too long to do maintenance on your lash extensions; there's too little there to notice or work with at this point.");
  939. this.pcs_lashes = this.pcs_naturallashes;
  940. this.lashextensionstyle = 0;
  941. this.lashextensionduration = 0;
  942. this.lashextensionnew = 0;
  943. }
  944. }
  945. if(this.false_lashes > 0){
  946. this.false_lashes -= 1;
  947. if (this.false_lashes == 0){
  948. message("bad","Your false lashes came off in the night; there's no recovering them now.");
  949. this.pcs_lashes = this.pcs_naturallashes;
  950. }
  951. message("info","Somehow, your lashes managed to stay attached throughout the night. You might be able to get away with wearing them another day straight.");
  952. }
  953. }*/
  954. // Hair colour change
  955. //TODO: Dasdas
  956. /*if(this.pcs_haircol != this.nathcol){
  957. this.hairDyeFade = Math.max(this.hairDyeFade - 1 , 0);
  958. }*/
  959. // Leg and pubes hair growth
  960. this.legHair += this.legHairGrowth;
  961. //Pubic hair growth at 1/2 per night
  962. this.pubesLength += this.pubesGrowth;
  963. this.teeth += 1;
  964. console.info("Deactivated: degradation of pube hair coloring");
  965. /*<!-- !!pubic hair colouring-->
  966. <!-- !! pcs_pubecol = natural colour-->
  967. <!-- !! pcs_pubecol_num[1] = flag for saveupdate-->
  968. <!-- !! pcs_pubecol_num[2] = actual colour-->
  969. <!-- !! pcs_pubecol_num[3] = countdown timer for dye-->
  970. <<if $pcs_pubecol_num[2] != $pcs_pubecol>>
  971. <<setinit $pcs_pubecol_num[3] -= 1>>
  972. <<if $pcs_pubecol_num[3] < 0>>
  973. <<setinit $pcs_pubecol_num[3] = 0>>
  974. <</if>>
  975. <<if $pcs_pubecol_num[3] == 0>>
  976. <<setinit $pcs_pubecol_num[2] = $pcs_pubecol>>
  977. <</if>>
  978. <</if>>
  979. <<if getvar("$pubesLength") < 2>>
  980. <<setinit $pcs_pubecol_num[2] = $pcs_pubecol>>
  981. <</if>>*/
  982. console.info("Deactivated: degradation of hair scrunches");
  983. /*
  984. <<if getvar("$hscrunch") > 0>>
  985. <<set $hscrunchrand = rand(1, 100)>>
  986. <<if getvar("$hscrunchrand") <= 8>>
  987. <<setn $hscrunch -= 1>>
  988. <</if>>
  989. <</if>>*/
  990. /*if(this.pcs_skin <= 300)
  991. this.pcs_skin += Math.min(this.skinDailyGain * 2, 20) - this.skinDailyPenalty - 1;
  992. else if(this.$pcs_skin <= 600)
  993. this.pcs_skin += Math.min(this.skinDailyGain, 10) - this.skinDailyPenalty - 1;
  994. else if(this.pcs_skin <= 800)
  995. this.pcs_skin += Math.min(this.skinDailyGain / 2, 5) - this.skinDailyPenalty - 1;
  996. else if(this.pcs_skin <= 900)
  997. this.pcs_skin += Math.min(this.skinDailyGain / 3, 3) - this.skinDailyPenalty - 1;
  998. else if(this.pcs_skin <= 1000)
  999. this.pcs_skin += Math.min(this.skinDailyGain / 5, 2) - this.skinDailyPenalty - 1;
  1000. if(this.pcs_teeth < 0){
  1001. // Daly degradation of perfect white teeth
  1002. let tempteeth = 1;
  1003. if(this.teeth['caffe_or_tea'] > 8)
  1004. tempteeth += 1;
  1005. if(this.teeth['smoked'] > 1)
  1006. tempteeth += 1;
  1007. tempteeth -= Math.min(3, this.teeth['brushed']);
  1008. this.teeth['degradation'] += Math.max(tempteeth, 0);
  1009. this.teeth['brushed'] = 0;
  1010. this.teeth['smoked'] = 0;
  1011. this.teeth['caffe_or_tea'] = 0;
  1012. if(this.teeth['degradation'] > 60)
  1013. {
  1014. // After a certain time of not taking care of your teeth you will loose you perfect whit smile.
  1015. this.teeth['degradation'] = 0;
  1016. this.pcs_teeth = 0;
  1017. }
  1018. this.teeth['degradation'] = Math.max(this.teeth['degradation'],0);
  1019. }
  1020. this.moisturizerDailyCount = 0;
  1021. this.skinDailyGain = 0;
  1022. this.skinDailyPenalty = 0;*/
  1023. }
  1024. get agility(){ return this.skillLevel('agility');} set agility(v){ this.skillSetLevel('agility',v);}
  1025. get strength(){return this.skillLevel('strength');} set strength(v){this.skillSetLevel('strength',v);}
  1026. get vitality(){return this.skillLevel('vitality');} set vitality(v){this.skillSetLevel('vitality',v);}
  1027. //#region Appearance
  1028. get pcs_makeup(){
  1029. /*if(this.cosmetic_tattoo > 0)
  1030. return this.cosmetic_tattoo + 1;
  1031. return this._pcs_makeup;*/
  1032. console.warn('Usage of get $pc.pcs_makeup is deprecated');
  1033. return Math.floor((this.makeupAmount + this.makeupQuality)/2);
  1034. }
  1035. set pcs_makeup(v){
  1036. console.warn('Usage of set $pc.pcs_makeup is deprecated');
  1037. //this._pcs_makeup = v;
  1038. this.makeupAmount = v;
  1039. this.makeupQuality= v;
  1040. }
  1041. _makeupAmount = 0;
  1042. get makeupAmount(){
  1043. return this._makeupAmount;
  1044. }
  1045. set makeupAmount(v){
  1046. this._makeupAmount = v;
  1047. }
  1048. _makeupQuality = 0;
  1049. get makeupQuality(){
  1050. return this._makeupQuality;
  1051. }
  1052. set makeupQuality(v){
  1053. this._makeupQuality = v;
  1054. }
  1055. cosmetic_tattoo = 0;
  1056. get appearance():Appearance{
  1057. return Appearance.get(this);
  1058. }
  1059. get hotcat():number{
  1060. return this.appearance.currentValue;
  1061. }
  1062. //#endregion
  1063. // ----- Toys -----
  1064. analplugin = 0;
  1065. vibratorin = 0;
  1066. // ----- Skills -----
  1067. get intelligence(){
  1068. return this.skillLevel('intelligence');
  1069. }
  1070. //#region Skills
  1071. _skills:{[key: string]:SkillOfCharacter} = {
  1072. }
  1073. skill(key: string):SkillOfCharacter{
  1074. return this._skills[key] ?? {
  1075. ceil:0,floor:0,experience:0,experienceHistory:[0],lastUsed:0
  1076. }
  1077. }
  1078. get skillsAll(){
  1079. let result = {};
  1080. for(const [skillId, skillData] of Object.entries(this._skills)){
  1081. result[skillId] = Object.assign({},skillData,{
  1082. level: this.skillLevel(skillId)
  1083. });
  1084. }
  1085. return result;
  1086. }
  1087. get skillExperienceGainMult(){
  1088. let lackOfSleep = 0; //TODO: get the correct value
  1089. if(lackOfSleep >= 20)
  1090. return 0.25;
  1091. else if(lackOfSleep >= 10)
  1092. return 0.5;
  1093. else if(lackOfSleep >= 5)
  1094. return 0.75;
  1095. else if(lackOfSleep >= 2)
  1096. return 0.9;
  1097. return 1;
  1098. }
  1099. #skillGain(skillId:string,experience:number){
  1100. return Skill.get(skillId).skillGain(this.skill(skillId),experience);
  1101. }
  1102. /**
  1103. * Initializes a skill if it doesn't exist.
  1104. * @param {string} skillId
  1105. */
  1106. #skillInit(skillId:string){
  1107. if(!(skillId in this._skills))
  1108. this._skills[skillId] = {
  1109. lastUsed: 0,
  1110. experience: 0,
  1111. experienceHistory:[0],
  1112. ceil: 0,
  1113. floor: 0
  1114. }
  1115. }
  1116. #skill_dayly(){
  1117. let today = State.variables.time.daystart;
  1118. for (const [skillId, skillData] of Object.entries(this._skills)){
  1119. this._skills[skillId].experienceHistory.push(skillData.experience);
  1120. if(this._skills[skillId].experienceHistory.length > 30)
  1121. this._skills[skillId].experienceHistory.shift();
  1122. this._skills[skillId].ceil = Math.max(skillData.experience, skillData.floor);
  1123. this._skills[skillId].floor = Math.max(Math.ceil(skillData.experience/2), skillData.floor);
  1124. let daysNotUsed = today - this._skills[skillId].lastUsed;
  1125. let newexperience = skillData.experience;
  1126. if(daysNotUsed > 14){
  1127. let currentLevel = this.skill_exp2lvl(skillId,skillData.experience);
  1128. let experienceForCurrentLevel = this.skill_lvl2exp(skillId,currentLevel);
  1129. let experienceForPreviousLevel= this.skill_lvl2exp(skillId,currentLevel-1);
  1130. let experienceDifferenceToPreviousLevel = experienceForCurrentLevel - experienceForPreviousLevel;
  1131. let experienceLoss = Math.ceil(experienceDifferenceToPreviousLevel / 4);
  1132. newexperience -= experienceLoss //* this.timeFactor;
  1133. }
  1134. this._skills[skillId].experience = Math.max(newexperience,this._skills[skillId].floor);
  1135. }
  1136. }
  1137. skill_exp2lvl(skillId:string,experience:number){
  1138. return Skill.get(skillId).experience2Skill(experience);
  1139. }
  1140. skill_lvl2exp(skillId:string,level:number){
  1141. return Skill.get(skillId).skill2Experience(level);
  1142. }
  1143. /**
  1144. * Increases the experience in one skill, taking daily limits into account. Returns the number of actual experience points gained.
  1145. * @param {string} skillId
  1146. * @param {number} inc
  1147. * @returns {number}
  1148. */
  1149. skillExperienceGain(skillId:string,inc:number):number{
  1150. if(inc == 0)
  1151. return;
  1152. this.#skillInit(skillId);
  1153. const effectiveGain = this.#skillGain(skillId,inc);
  1154. this._skills[skillId].experience += effectiveGain;
  1155. console.log("Skill Experience Gain:",skillId,inc,effectiveGain,this._skills[skillId].experience );
  1156. return effectiveGain;
  1157. }
  1158. skillsExperienceGain(skillObj:{[key: string]:number},factor=1){
  1159. for(const [skillId,inc] of Object.entries(skillObj)){
  1160. this.skillExperienceGain(skillId,inc*factor)
  1161. }
  1162. }
  1163. skillExperienceHistory(skillId){
  1164. this.#skillInit(skillId);
  1165. let eh = clone(this._skills[skillId].experienceHistory);
  1166. eh.push(this._skills[skillId].experience);
  1167. return eh;
  1168. }
  1169. skillExperienceHistoryDayExpTuple(skillId){
  1170. let today = State.variables.time.daystart;
  1171. let eh = this.skillExperienceHistory(skillId);
  1172. let result = [];
  1173. let todayIndex = eh.length - 1;
  1174. for(let i = 0; i < eh.length; i++){
  1175. let day = -todayIndex + i + today;
  1176. let tuple = [day,eh[i]];
  1177. result.push(tuple);
  1178. }
  1179. return result;
  1180. }
  1181. skillFloor(skillId){
  1182. this.#skillInit(skillId);
  1183. return this._skills[skillId].floor;
  1184. }
  1185. skillLevel(skillId){
  1186. this.#skillInit(skillId);
  1187. return (
  1188. this.skillLevelRaw(skillId) +
  1189. this.#skillLevelBonusOfActiveEffect(skillId)
  1190. );
  1191. }
  1192. skillLevelAtStartOfDay(skillId){
  1193. return this.skill_exp2lvl(skillId,this.skill(skillId).experienceHistory.last());
  1194. }
  1195. #skillLevelBonusOfActiveEffect(skillId){
  1196. let result = 0;
  1197. for(const effect of Object.values(this.activeEffects))
  1198. result += effect.skills?.[skillId] ?? 0;
  1199. return result;
  1200. }
  1201. skillLevelRaw(skillId){
  1202. return this.skill_exp2lvl(skillId,this.skill(skillId).experience);
  1203. }
  1204. //Is usually used for initialization, therefore we'll add one history entry if it doesn't exist
  1205. skillSetLevel(skillId:string,lvl:number){
  1206. this.#skillInit(skillId);
  1207. let exp = this.skill_lvl2exp(skillId,lvl);
  1208. this._skills[skillId].experience = exp;
  1209. console.log("Setting skill level (Skill, Lvl, Exp)",skillId,lvl,exp);
  1210. }
  1211. //#endregion
  1212. //#region Fetishes
  1213. _fetishes = {};
  1214. fetish(fetishId){
  1215. return this._fetishes[fetishId] ?? 1;
  1216. }
  1217. fetishSet(fetishId,v){
  1218. this._fetishes[fetishId] = v;
  1219. }
  1220. fetishes(fetishIds){
  1221. if(typeof fetishIds == "string")
  1222. return this.fetishes([fetishIds]);
  1223. let result = 1;
  1224. let indifferentCount = 0;
  1225. let arouseCount = 0;
  1226. for(const fetishId of fetishIds){
  1227. const arousalOfFetish = this.fetish(fetishId);
  1228. if(arousalOfFetish < 0)
  1229. return arousalOfFetish;
  1230. if(arousalOfFetish == 0)
  1231. indifferentCount += 1;
  1232. else if(arousalOfFetish > 1){
  1233. result += arousalOfFetish - 1;
  1234. arouseCount += 1;
  1235. }
  1236. }
  1237. if(indifferentCount > 0 && arouseCount == 0)
  1238. return 0;
  1239. return result;
  1240. }
  1241. //#endregion
  1242. //#region Sex-Stats
  1243. /*
  1244. Each Sex-Encounter has the following format:
  1245. {
  1246. npc: The ID of the npc Sveta had sex with. If she has sex with more than one character (e.g. threesome) an encounter has to be created for each of them.
  1247. time:The time it happened (Date-Object (UTC-time))
  1248. aware: Is Sveta aware of the sex-act? False in case of hypnosis, blackouts, etc.
  1249. civ: Cum in vagina
  1250. civa:Cum in vagina aware
  1251. tags: Array of tags
  1252. }
  1253. */
  1254. _sexEncounters = [];
  1255. sexEncounterAdd(time,events){
  1256. this._sexEncounters.push({
  1257. time: time,
  1258. events: events
  1259. });
  1260. }
  1261. /*sexEncountersFiltered(maxage=Infinity,aware=undefined,civ=undefined,civa=undefined){
  1262. let result = [];
  1263. for (const sexEncounter of this._sexEncounters) {
  1264. let ageOfEncounterInDays = Math.floor(State.variables.time.secondsSinceDate(sexEncounter.time) / 86400);
  1265. if(ageOfEncounterInDays > maxage)
  1266. continue;
  1267. if(typeof aware === "boolean" && aware !== sexEncounter.aware) continue;
  1268. if(typeof civ === "boolean" && civ !== sexEncounter.civ) continue;
  1269. if(typeof civa === "boolean" && civa !== sexEncounter.civa) continue;
  1270. result.push(sexEncounter);
  1271. }
  1272. return result;
  1273. }*/
  1274. sexEncountersFilteredByNpc(maxage=Infinity,aware=undefined){
  1275. let result = {};
  1276. for(const encounter of this._sexEncounters){
  1277. for(const event of encounter.events){
  1278. if(typeof aware === "boolean"){
  1279. if((aware && event.unaware) || (!aware && !event.unaware))
  1280. continue;
  1281. }
  1282. result[event.npc] ??= 0;
  1283. result[event.npc] += 1;
  1284. }
  1285. }
  1286. return result;
  1287. }
  1288. //Every Stat has two values: the number you are aware of and the number you are unaware of
  1289. /** @access private */
  1290. _sexStats = {
  1291. vaginal: [0,0],
  1292. vaginal_fist:[0,0],
  1293. vaginal_dildo:[0,0],
  1294. vaginal_strap:[0,0]
  1295. }
  1296. /**
  1297. * Returns a sexstat or, it it isn't set, def. Returns undefined in case of an error.
  1298. * @example
  1299. * sexStat('vaginal_total','both',0);
  1300. * @date 5/29/2023 - 9:32:32 AM
  1301. *
  1302. * @param {string} key
  1303. * @param {string} [awareness='both'] - Either 'aware' or 'unaware' or 'both' (the sum of the former)
  1304. * @param {number} [def=0]
  1305. * @returns {number|undefined}
  1306. */
  1307. sexStat(key,awareness='both',def=0){
  1308. switch(key){
  1309. case 'vaginal_total':
  1310. return 0;//return (this.stat('vaginal',awareness) + this.stat('vaginal_fist',awareness) + this.stat('vaginal_dildo',awareness) + this.stat('vaginal_strap',awareness));
  1311. }
  1312. if(!(key in this._sexStats))
  1313. return def;
  1314. switch(awareness){
  1315. case 'both':
  1316. return this._sexStats[key][0] + this._sexStats[key][1];
  1317. case 'aware':
  1318. return this._sexStats[key][0];
  1319. case 'unaware':
  1320. return this._sexStats[key][1];
  1321. default:
  1322. console.error('Invalid argument for PlayerCharacter.sexStat: awareness',awareness);
  1323. }
  1324. return undefined;
  1325. }
  1326. /**
  1327. * Increases a sex-stat by inc.
  1328. * @example
  1329. * sexStatInc('vaginal','aware',1)
  1330. * @date 5/29/2023 - 9:24:04 AM
  1331. *
  1332. * @param {string} key
  1333. * @param {string} awareness - Either 'aware' or 'unaware'
  1334. * @param {number} [inc=1]
  1335. */
  1336. sexStatInc(key,awareness,inc=1){
  1337. let currentValue = this.sexStat(key,awareness);
  1338. this.sexStatSet(key,awareness,currentValue+inc);
  1339. }
  1340. /**
  1341. * Sets a sex-stat to v.
  1342. * @example
  1343. * sexStatSet('vaginal','aware',1)
  1344. * @date 5/29/2023 - 9:30:58 AM
  1345. *
  1346. * @param {string} key
  1347. * @param {string} awareness - Either 'aware' or 'unaware'
  1348. * @param {number} v - The new value.
  1349. * @returns {number|undefined} The new value or, if there was an error, undefined.
  1350. */
  1351. sexStatSet(key,awareness,v){
  1352. this._sexStats[key] ??= [0,0];
  1353. switch(awareness){
  1354. case 'aware':
  1355. this._sexStats[key][0] = v;
  1356. return v;
  1357. case 'unaware':
  1358. this._sexStats[key][1] = v;
  1359. return v;
  1360. default:
  1361. console.error('Invalid argument for PlayerCharacter.sexStatSet: awareness',awareness);
  1362. return undefined;
  1363. }
  1364. }
  1365. get isVirgin(){
  1366. return (this.sexStat('vaginal','both') == 0) ? true : false;
  1367. }
  1368. get thinksIsVirgin(){
  1369. return (this.sexStat('vaginal','aware') == 0) ? true : false;
  1370. }
  1371. //#endregion
  1372. //#region Traits
  1373. _traits = {
  1374. nerd_status: 0
  1375. }
  1376. trait(key,def=0){
  1377. if(key in this._traits)
  1378. return this._traits[key];
  1379. return def;
  1380. }
  1381. traitDec(key,v){
  1382. this._traits[key] -= v;
  1383. }
  1384. traitInc(key,v){
  1385. this._traits[key] += v;
  1386. }
  1387. traitSet(key,v){
  1388. this._traits[key] = v;
  1389. }
  1390. //#endregion
  1391. // ----- Arousal -----
  1392. /*
  1393. For checking arousal and when applicable triggering orgasms.
  1394. action:
  1395. All acts are from Sveta''s perspective and in cases of both giving and receiving, receiving should be used.
  1396. It can be when receiving any of the following
  1397. 'clit_finger' - Clit being stimulated directly by a finger
  1398. 'clit_vibe' - Clit being stimulated directly by a vibrator (set low, use negative time and double/triple up for more power)
  1399. 'porn' - viewing pornographic material
  1400. 'voyeur_sex' - watching, usually as in spying on, other people have sex
  1401. 'voyeur' - watching, usually as in spying on, erotic acts of others
  1402. 'erotic' - being aroused by eroticism
  1403. 'erotic_nudity' - being aroused by nudity of others
  1404. 'trib' - rubbing pussy against another pussy
  1405. 'massage' - rubbing your body, back, feet, etc. with their hands/arms
  1406. 'cuni' - stimulation of your pussy by someones toungue
  1407. 'rimming' - stimulation of your anus by someones toungue
  1408. 'vaginal' 'vaginal_finger' 'vaginal_fist' 'vaginal_dildo' 'vaginal_strap' 'vaginal_vibe' - stimulation of your vagina with a penis and various others
  1409. 'self_fisting' - fisting your own vagina
  1410. 'anal' 'anal_finger' 'anal_fist' 'anal_dildo' 'anal_strap' 'anal_vibe' - stimulation of your anus with a penis and various others
  1411. 'self_fisting_anal' - fisting your own anus
  1412. 'kiss' - snogging, tonsil tennis, lip locking, etc.
  1413. 'BDSM' - receiving candle wax, flogging, leash play, bondage etc
  1414. 'pee' - being peed upon
  1415. also when giving any of the following:
  1416. 'flashlite' - flashing underwear
  1417. 'flash' - flashing naked breasts/arse/vagina
  1418. 'massage_give' - rubbing their body, back, feet, etc. with your hands/arms
  1419. 'cuni_give' - stimulating someones pussy with your toungue
  1420. 'rimming_give' - stimulating someones anus with your toungue
  1421. 'vaginal_finger_give' 'vaginal_fist_give' 'vaginal_dildo_give' 'vaginal_strap_give' 'vaginal_vibe_give' - stimulating someones vagina in various ways
  1422. 'clit_finger_give' - stimulating someones clit
  1423. 'anal_finger_give' 'anal_fist_give' 'anal_dildo_give' 'anal_vibe_give' 'anal_strap_give' - stimulating someones anus in various ways
  1424. 'hj' - jerking a guy off with your hand
  1425. 'bj' - sucking a guy off
  1426. 'dildo_suck' - simulating a bj on a dildo/strapon
  1427. 'titjob' - using boobs to jerk off a guy
  1428. 'footjob' - using feet to jerk off a guy
  1429. 'BDSM_give' - giving candle wax, flogging, leash play, bondage etc
  1430. 'pee_give' - peeing on somone
  1431. finally
  1432. 'foreplay' - receiving other stuff
  1433. 'foreplay_give' - giving other stuff
  1434. time
  1435. for time taken in minutes - it is use partly for arousal calculation and partly for moving time ahead. If you want to calculate just the arousal and do not move time (simultaneous stimulation), use negative value.
  1436. npcId
  1437. specify the involved npc
  1438. flags
  1439. Are optional but can be themes involved in the act and can be any of the following:
  1440. 'maso' 'bound' 'beast' 'exhibitionism' 'rough' 'prostitution' 'dom' 'sub' 'incest'
  1441. 'feet' 'lesbian' 'group' 'gangbang' 'humiliation' 'deepthroat' 'unknown' 'gloryhole' 'rape' 'futa' 'masturbate'
  1442. 'unaware'
  1443. */
  1444. _analCapacity = 0.5;
  1445. get analCapacity(){
  1446. return this._analCapacity;
  1447. }
  1448. _vaginalCapacity = 1;
  1449. get vaginalCapacity(){
  1450. return this._vaginalCapacity;
  1451. }
  1452. get vaginalLubrication(){
  1453. return this.horny / 100;
  1454. }
  1455. analCapacityAdaptTo(v){
  1456. if(v <= this._analCapacity)
  1457. return;
  1458. const sizeDifference = v - this._analCapacity;
  1459. if(sizeDifference <= 1)
  1460. this._analCapacity = Math.min(v, this._analCapacity + 0.1);
  1461. else
  1462. this._analCapacity = Math.min(v, this._analCapacity + sizeDifference / 10);
  1463. }
  1464. vaginalCapacityAdaptTo(v){
  1465. if(v <= this._vaginalCapacity)
  1466. return;
  1467. const sizeDifference = v - this._vaginalCapacity;
  1468. if(sizeDifference <= 1)
  1469. this._vaginalCapacity = Math.min(v, this._vaginalCapacity + 0.1);
  1470. else
  1471. this._vaginalCapacity = Math.min(v, this._vaginalCapacity + sizeDifference / 10);
  1472. }
  1473. current_arousal_scene_actions = [];
  1474. arouse(action,time,npcId=undefined,flags=undefined){
  1475. console.warn("PlayerCharacter.arouse() is deprecated. Use <<arouse>> instead",action,time,npcId,flags);
  1476. }
  1477. //TODO
  1478. arousalEnd(){
  1479. console.warn("PlayerCharacter.arousalEnd() is deprecated");
  1480. }
  1481. //#region Cum
  1482. /*
  1483. Cum-Locations
  1484. 0 = 'In your Vagina'
  1485. 1 = 'On your labia'
  1486. 2 = 'On your panties over your vagina'
  1487. 3 = 'In your anus'
  1488. 4 = 'On your butt'
  1489. 5 = 'On your panties over your butt'
  1490. 6 = 'On your clothes in your groin area'
  1491. 7 = 'On your clothes'
  1492. 8 = 'On your back'
  1493. 9 = 'On your legs'
  1494. 10 = 'On your arms'
  1495. 11 = 'On your face'
  1496. 12 = 'Inside your mouth'
  1497. 13 = 'On your hands'
  1498. 14 = 'On your stomach'
  1499. 15 = 'On your breasts'
  1500. 16 = 'In your hair'
  1501. 17 = 'In a condom in your vagina
  1502. */
  1503. _cum:{ [key: string]: Array<any>} = {};
  1504. get cums(){
  1505. this._cumPurgeExpired();
  1506. return this._cum;
  1507. }
  1508. /**
  1509. * Removes cum-data on the inside of the PCs body.
  1510. * @date 10/8/2023 - 2:16:00 PM
  1511. */
  1512. _cumPurgeExpired(){
  1513. const bodyparts = setup.getBodyparts();
  1514. let updatedCumData = {};
  1515. const maxAgeInMinutes = 360; // 6 Hours
  1516. let oldestPossibleDateTime = State.variables.time.now;
  1517. oldestPossibleDateTime.setUTCMinutes(oldestPossibleDateTime.getUTCMinutes() - maxAgeInMinutes);
  1518. for (const [bodypart, cumData] of Object.entries(this._cum)) {
  1519. if(bodyparts[bodypart].inside)
  1520. updatedCumData[bodypart] = cumData.filter(cum => cum.time.getTime() >= oldestPossibleDateTime.getTime());
  1521. else
  1522. updatedCumData[bodypart] = cumData;
  1523. }
  1524. this._cum = updatedCumData;
  1525. }
  1526. cum(npcId,bodypartId){
  1527. let bodypartData = setup.getBodypart(bodypartId);
  1528. if(!bodypartData || bodypartData.cumDisabled)
  1529. console.warn('The following bodypart is not enabled for $pc.cum():',bodypartId);
  1530. bodypartId = bodypartData.id;
  1531. const time = State.variables.time;
  1532. const now = time.now;
  1533. const cumData = {
  1534. npc: npcId,
  1535. time: now
  1536. };
  1537. this._cum[bodypartId] ??= [];
  1538. this._cum[bodypartId].push(cumData);
  1539. }
  1540. cumCleanAll(){
  1541. this._cum={};
  1542. }
  1543. cumCleanByActivity(activityId){
  1544. const bodyparts = Object.entries(setup.getBodyparts());
  1545. const bodypartIdsToClean = bodyparts.filter(([id,bodypart]) => bodypart['clean'].includes(activityId)).map(([id,bodypart]) => id);
  1546. for(let bodypartIdToClean of bodypartIdsToClean)
  1547. this._cum[bodypartIdToClean] = [];
  1548. }
  1549. get cumBodypartIds(){
  1550. return Object.entries(this.cums).filter(([bodypartId,cumArray]) => cumArray.length > 0).map(([bodypartId,cumArray]) => bodypartId);
  1551. }
  1552. get cumVisibleBodypartIds(){
  1553. return this.cumBodypartIds.filter(bodypartId=>!this.bodyPartCovered(bodypartId));
  1554. }
  1555. //#endregion
  1556. vgape = 0;
  1557. agape = 0;
  1558. spanked = 0;
  1559. //#region Pain
  1560. _pain = new setup.Pain();
  1561. pain(bodypart:string){return this._pain.painByBodypart(bodypart)}
  1562. get painByBodyparts(){return this._pain.painByBodyparts}
  1563. get painByBodypartsSorted(){return this._pain.painByBodypartsSorted}
  1564. painDeteriorate(timeToAdd:number){return this._pain.painDeteriorate(timeToAdd)}
  1565. painInc(bodypart,reason='',v){return this._pain.painInc(bodypart,reason,v)}
  1566. painReasonsByBodypartSorted(bodypart){return this._pain.reasonsByBodypartSorted(bodypart)}
  1567. get painTotal(){return this._pain.painTotal}
  1568. get painOfActiveEffects(){
  1569. var painRaw = {};
  1570. const activeEffects = this.activeEffects;
  1571. for(const activeEffect of Object.values(activeEffects)){
  1572. for(const [bodypartId,reasonData] of Object.entries(activeEffect.pain ?? {})){
  1573. painRaw[bodypartId] ??= {};
  1574. for(const [reasonId,painValue] of Object.entries(reasonData ?? {})){
  1575. painRaw[bodypartId][reasonId] ??= 0;
  1576. painRaw[bodypartId][reasonId] += painValue;
  1577. }
  1578. }
  1579. }
  1580. return painRaw;
  1581. }
  1582. get painReductionOfActiveEffects(){
  1583. return this.#activeEffectValueByKey('painReduction','dim',0.5);
  1584. }
  1585. //#endregion
  1586. timeFactor = 1; //Used for cheating
  1587. //#region Fame
  1588. _fame = {};
  1589. fame(location,type=null){
  1590. if(!type){
  1591. console.warn('Calling $pc.fame in deprecated format.',location);
  1592. let idParts = location.split('_');
  1593. location = idParts[0];
  1594. let type = idParts[1];
  1595. return this.fame(location,type);
  1596. }
  1597. if(!(location in this._fame))
  1598. return 0;
  1599. if(type == 'slut')
  1600. return this.fame(location,'sex') + this.fame(location,'prostitute');
  1601. return this._fame[location][type] || 0;
  1602. }
  1603. fameDec(location,type,amount,local=false){
  1604. this.fameInc(location,type,amount * -1,local);
  1605. }
  1606. fameInc(location,type,amount,local=false){
  1607. this._fame[location] ??= {};
  1608. this._fame[location][type] ??= 0;
  1609. let current = this._fame[location][type];
  1610. if(typeof amount == "string"){
  1611. if(current > 900){ //The original says 1000... how is this possible if 1000 is supposed to be the soft cap?
  1612. switch(amount){
  1613. case 'tiny':
  1614. case 'small':
  1615. return;
  1616. case 'medium': amount = rand(0,1);break;
  1617. case 'large': amount = rand(1,2);break;
  1618. case 'huge': amount = rand(1,4);break;
  1619. case 'BronzeMedal': amount = rand(15,25);break;
  1620. case 'SilverMedal': amount = rand(25,35);break;
  1621. case 'GoldMedal': amount = rand(35,45);break;
  1622. default:
  1623. console.warn('Argument for amount not reckognized in $pc.fameInc:',amount);
  1624. amount = 0;
  1625. }
  1626. }else if(current > 700){
  1627. switch(amount){
  1628. case 'tiny': amount = rand(0,1);break;
  1629. case 'small': amount = rand(1,2);break;
  1630. case 'medium': amount = rand(1,4);break;
  1631. case 'large': amount = rand(6,12);break;
  1632. case 'huge': amount = rand(10,24);break;
  1633. case 'BronzeMedal': amount = rand(25,60);break;
  1634. case 'SilverMedal': amount = rand(60,100);break;
  1635. case 'GoldMedal': amount = rand(100,150);break;
  1636. default:
  1637. console.warn('Argument for amount not reckognized in $pc.fameInc:',amount);
  1638. amount = 0;
  1639. }
  1640. }else if(current > 400){
  1641. switch(amount){
  1642. case 'tiny': amount = rand(1,2);break;
  1643. case 'small': amount = rand(1,4);break;
  1644. case 'medium': amount = rand(6,12);break;
  1645. case 'large': amount = rand(10,24);break;
  1646. case 'huge': amount = rand(20,50);break;
  1647. case 'BronzeMedal': amount = rand(50,100);break;
  1648. case 'SilverMedal': amount = rand(100,150);break;
  1649. case 'GoldMedal': amount = rand(150,200);break;
  1650. default:
  1651. console.warn('Argument for amount not reckognized in $pc.fameInc:',amount);
  1652. amount = 0;
  1653. }
  1654. }else{
  1655. switch(amount){
  1656. case 'tiny': amount = rand(1,4);break;
  1657. case 'small': amount = rand(6,12);break;
  1658. case 'medium': amount = rand(10,24);break;
  1659. case 'large': amount = rand(20,50);break;
  1660. case 'huge': amount = rand(40,70);break;
  1661. case 'BronzeMedal': amount = rand(150,250);break;
  1662. case 'SilverMedal': amount = rand(250,350);break;
  1663. case 'GoldMedal': amount = rand(350,450);break;
  1664. default:
  1665. console.warn('Argument for amount not reckognized in $pc.fameInc:',amount);
  1666. amount = 0;
  1667. }
  1668. }
  1669. }else{
  1670. if(current > 900)
  1671. amount /= 10;
  1672. else if(current > 700)
  1673. amount /= 6;
  1674. else if(current > 400)
  1675. amount /= 3;
  1676. }
  1677. if(amount == 0)
  1678. return;
  1679. let target = Math.clamp(current+amount,0,1000);
  1680. this._fame[location][type] = target;
  1681. if(type in fameAlwaysLocale)
  1682. local = true;
  1683. }
  1684. //#endregion
  1685. //#region Effects
  1686. _effects:{ [key: string]: { expiration:Date; [key: string]: any; } } = {};
  1687. get activeEffectIds():string[]{
  1688. return Object.keys(this.activeEffects);
  1689. }
  1690. get activeEffects():{[key: string]:any}{
  1691. let result = {};
  1692. const time = State.variables.time;
  1693. for(const [effectid,effectData] of Object.entries(this._effects)){
  1694. if(effectData.expiration === undefined)
  1695. continue;
  1696. if(effectData.expiration === null || time.isFuture(effectData.expiration))
  1697. result[effectid] = effectData;
  1698. }
  1699. result = Object.assign({},this.drugsActiveEffects,result);
  1700. return result;
  1701. }
  1702. get activeEffectsMoodlets():{ [key: string]: ActiveMoodlet; }{
  1703. const time = State.variables.time;
  1704. return Object.fromEntries(
  1705. this.activeEffectsMoodletIDs.map(
  1706. (moodletId) =>
  1707. [
  1708. moodletId,
  1709. ActiveMoodlet.create(moodletId, {expiration: time.endTime})
  1710. ]
  1711. )
  1712. );
  1713. }
  1714. get activeEffectsMoodletIDs():Array<string>{
  1715. var result = [];
  1716. const activeEffects = this.activeEffects;
  1717. for(const activeEffect of Object.values(activeEffects)){
  1718. if(activeEffect.moodlet)
  1719. result.push(activeEffect.moodlet);
  1720. }
  1721. return result;
  1722. }
  1723. get activeEffectsSidebar(){
  1724. return Object.values(this.activeEffects).filter(effect=>effect.sidebar);
  1725. }
  1726. /**
  1727. * @param {string} key
  1728. * @param {undefined|'+'|'*'|'dim'} [reduceMode='+']
  1729. * @param {any[]} args Additional arguments.
  1730. * @returns {number}
  1731. */
  1732. #activeEffectValueByKey(key,reduceMode='+',...args):number{
  1733. const valueArray = this.#activeEffectValuesByKey(key);
  1734. switch (reduceMode) {
  1735. case '*':
  1736. return valueArray.reduce((accumulator, currentValue) => accumulator * currentValue,1);
  1737. case 'dim':
  1738. const base = args[0] ?? 0.5;
  1739. return valueArray.sort((a,b)=>b-a).reduce((accumulator, currentValue,index) => accumulator + Math.pow(base,index) * currentValue,0);
  1740. case '+':
  1741. default:
  1742. return valueArray.reduce((accumulator, currentValue) => accumulator + currentValue,0);
  1743. }
  1744. }
  1745. /**
  1746. * @param {string} key
  1747. * @returns {{}}
  1748. */
  1749. #activeEffectValuesByKey(key:string){
  1750. var result =[];
  1751. const activeEffects = this.activeEffects;
  1752. for(const effect of Object.values(activeEffects)){
  1753. if(key in effect)
  1754. result.push(effect[key]);
  1755. }
  1756. return result;
  1757. }
  1758. /**
  1759. * Description placeholder
  1760. * @date 10/3/2023 - 1:07:07 PM
  1761. *
  1762. * @param {string} effectId
  1763. * @param {number|Date|null} expirationOrDuration - Either a date when the effect expires, a number that indicates the minutes til it expires, or null if it won't expire.
  1764. */
  1765. effectAdd(effectId, expirationOrDuration, metadata={}){
  1766. const time = State.variables.time;
  1767. let expiration = expirationOrDuration;
  1768. if(typeof expirationOrDuration == 'number')
  1769. expiration = time.nowWithMinutesOffset(expirationOrDuration);
  1770. this._effects[effectId] = Object.assign({expiration:expiration},metadata) ;
  1771. }
  1772. /**
  1773. * @param {string} effectId
  1774. * @returns {boolean}
  1775. */
  1776. effectIsActive(effectId){
  1777. return (effectId in this.activeEffects);
  1778. }
  1779. //#endregion
  1780. //#region Timed Actions
  1781. execute_every_15_minutes(){
  1782. const time = State.variables.time;
  1783. let timeMod = 0.25 * this.timeFactor; //We are doing some hourly calculations 4 times as often
  1784. let change = {
  1785. 'energy': 0,
  1786. 'hydra': 0
  1787. }
  1788. if(this.isSleeping){
  1789. change.energy = timed_stat_changes['energy']['sleep'] * timeMod;
  1790. change.hydra = timed_stat_changes['hydra']['sleep'] * timeMod;
  1791. }else{
  1792. change.energy = timed_stat_changes['energy']['default'] * timeMod;
  1793. change.hydra = timed_stat_changes['hydra']['default'] * timeMod;
  1794. }
  1795. this.pcs_energy -= change.energy;
  1796. this.pcs_hydra -= change.hydra;
  1797. this.energyDemand += change.energy;
  1798. if(this.isSleeping){
  1799. this.pcs_sleep -= timed_stat_changes['sleep']['sleep'] * timeMod ;
  1800. this.pcs_stam += this.stammax / 10;
  1801. this.pcs_willpwr += this.willpowermax / 100;
  1802. }else{
  1803. this.pcs_sleep -= timed_stat_changes['sleep']['default'] * timeMod ;
  1804. this.pcs_stam += this.stammax / 20;
  1805. this.pcs_willpwr += this.willpowermax / 300;
  1806. }
  1807. // ----- Dying -----
  1808. for (const [riskId, riskData] of Object.entries(dieRisks)){
  1809. this._death[riskId] ??= {stage:0};
  1810. if(this[riskData['variable']] == 0){
  1811. if(this._death[riskId].stage == 0 || !time.isFuture(this._death[riskId].nextStageDate)){
  1812. if((this._death[riskId].stage ?? 0) >= riskData.durations.length){
  1813. this.gameover = riskId;
  1814. console.warn("GAMEOVER set",riskId);
  1815. }else{
  1816. this._death[riskId].nextStageDate = time.nowWithMinutesOffset(riskData.durations[this._death[riskId].stage]);
  1817. this._death[riskId].stage += 1;
  1818. this.moodletApplyById(riskId+'_'+this._death[riskId].stage);
  1819. }
  1820. }
  1821. }
  1822. }
  1823. }
  1824. execute_every_1_hour(){
  1825. let timeFactor = this.timeFactor;
  1826. if(this.mood <= 20)
  1827. this.will_counter -= 2 * timeFactor;
  1828. if(this.willpowermax > 100){
  1829. if(this.pcs_willpwr < 25)
  1830. this.will_counter -= 1 * timeFactor;
  1831. }else{
  1832. if(this.pcs_willpwr < this.willpowermax / 4)
  1833. this.will_counter -= 1 * timeFactor;
  1834. }
  1835. if(this.will_counter <= -10){
  1836. this.willpowermax -= 1;
  1837. this.will_counter = 0;
  1838. }
  1839. this.pcs_lipbalm -= 1 * timeFactor;
  1840. }
  1841. execute_every_1_day(){
  1842. this.bodyDailyUpdate();
  1843. this.#skill_dayly();
  1844. this.#moodletsClean();
  1845. }
  1846. execute_every_timeUpdate(timeToAdd:number){
  1847. let timestamp = State.variables.time.minutesTimestamp;
  1848. if(this.deodorant_on == 1 && timestamp > this.deodorant_time)
  1849. this.deodorant_on = 0;
  1850. //this.performShoePainAndExperience(timeToAdd);
  1851. this.performInhibitionExperience(timeToAdd);
  1852. this.horny -= timeToAdd * this.hornyDeteriorationRate;
  1853. this.drugsDeteriorate(timeToAdd);
  1854. this.painDeteriorate(timeToAdd);
  1855. }
  1856. //#endregion
  1857. performInhibitionExperience(minutes){
  1858. if(this.isSleeping)
  1859. return;
  1860. let balancingFactorExp = 0.5;
  1861. let outsideFactor = 2;
  1862. let location = State.variables.location;
  1863. // You don't get any inhibition progress while being at home
  1864. if(location.isHome)
  1865. return;
  1866. let totalExpFactor = balancingFactorExp * (location.isOutdoors ? outsideFactor : 1);
  1867. let wardrobe = State.variables.wardrobe;
  1868. let clothesInhib = wardrobe.clothes.inhibition;
  1869. let playerInhib = this.pcs_inhib;
  1870. if(playerInhib > clothesInhib)
  1871. return;
  1872. let inhibDiff = clothesInhib - playerInhib;
  1873. let expRaw = Math.ceil(inhibDiff / 10) * 10; // Round up to 10, 20, etc.
  1874. let experienceGained = expRaw * minutes / 60 * totalExpFactor;
  1875. this.skillExperienceGain("inhibition",experienceGained);
  1876. }
  1877. //#region Deco
  1878. // Deco includes tattoos, piercings, glasses, etc.
  1879. _deco = {}
  1880. _decoOwned = {}
  1881. decoAdd(type,position,index){
  1882. if(!this._decoOwned[type])
  1883. this._decoOwned[type] = {};
  1884. if(!this._decoOwned[type][position])
  1885. this._decoOwned[type][position] = [];
  1886. if(!this._decoOwned[type][position].includes(index))
  1887. this._decoOwned[type][position].push(index);
  1888. }
  1889. decoHas(type:string,position:string,index:number){
  1890. if(!(this._decoOwned[type]?.[position]))
  1891. return false;
  1892. return this._decoOwned[type][position].includes(index);
  1893. }
  1894. /**
  1895. * Returns the index of a decoration of `type` worn at `position`.
  1896. * @param {string} type
  1897. * @param {string} [position='default']
  1898. * @returns {number} 0 if there is no decoration, -1 if the type is prepared but empty (such as having a piercing) or >0 if a decoration is active.
  1899. */
  1900. decoGet(type:string,position: string='default'):number{
  1901. return this._deco[type]?.[position] ?? 0;
  1902. }
  1903. decoImage(type:string,position:string){
  1904. let constantDecoData = setup.getDeco(type,position,this.decoGet(type,position));
  1905. return constantDecoData.image;
  1906. }
  1907. get decoOwned(){return this._decoOwned;}
  1908. decoRemove(type,position='default'){
  1909. this.decoSet(type,position,null);
  1910. }
  1911. decoSet(type,position='default',v){
  1912. if(!(type in this._deco))
  1913. this._deco[type] = {};
  1914. this._deco[type][position] = v;
  1915. }
  1916. decoWear(type,position='default',v){
  1917. this.decoSet(type,position,v);
  1918. if(v && v != -1)
  1919. this.decoAdd(type,position,v);
  1920. }
  1921. //#endregion
  1922. // ----- Bodyparts -----
  1923. bodyPartCovered(bodypartId){
  1924. let bodypartData = setup.getBodypart(bodypartId);
  1925. if(!bodypartData){
  1926. console.error('Data not found by $pc.bodyPartCovered for bodypart',bodypartId);
  1927. return false;
  1928. }
  1929. if(typeof bodypartData.covered === "undefined")
  1930. return false;
  1931. if(typeof bodypartData.covered === "boolean")
  1932. return bodypartData.covered;
  1933. if(typeof bodypartData.covered === "string")
  1934. return Scripting.evalTwineScript(bodypartData.covered);
  1935. console.error('Unsupported type of covered of bodypart in $pc.bodyPartCovered',bodypartId,bodypartData);
  1936. return false;
  1937. }
  1938. // ----- Init -----
  1939. init_final(){
  1940. for (const [skillId, skillData] of Object.entries(this._skills)){
  1941. this._skills[skillId].experienceHistory = [skillData.experience];
  1942. }
  1943. }
  1944. //#region Occupation
  1945. get isSchoolStudent(){
  1946. return State.variables.q.questIsActive("school");
  1947. }
  1948. //#endregion
  1949. //#region Vehicles
  1950. _vehicleInUse = null;
  1951. get vehicleInUse(){return this._vehicleInUse;}
  1952. set vehicleInUse(v){this._vehicleInUse = v;}
  1953. get vehicleData(){
  1954. let inventory = State.variables.inventory;
  1955. let vehicleData = inventory.metadata(this.vehicleInUse,'vehicles');
  1956. return vehicleData;
  1957. }
  1958. vehicleCanEnterPassage(passage){
  1959. var vehicleType = this.vehicleType;
  1960. if(vehicleType != 'car')
  1961. return true;
  1962. var passageTags = tags(passage);
  1963. return passageTags.includes('car');
  1964. }
  1965. get vehicleSpeed(){
  1966. return this.vehicleData.speed ?? 1;
  1967. }
  1968. get vehicleType(){
  1969. if(!this.vehicleInUse)
  1970. return 'walk';
  1971. let inventory = State.variables.inventory;
  1972. let vehicleData = inventory.metadata(this.vehicleInUse,'vehicles');
  1973. return vehicleData.type;
  1974. }
  1975. //#endregion
  1976. //#region Menstruation Cycle
  1977. _cycleStart = -7;
  1978. _cycleLength = 28;
  1979. _cycleLengthLast = 28;
  1980. get cycleDay(){
  1981. const time = State.variables.time;
  1982. const daystart = time.daystart;
  1983. return daystart - this.cycleStart;
  1984. }
  1985. get cycleLength(){return this._cycleLength;} // the length of the current cycle
  1986. set cycleLength(v){this._cycleLength = v;}
  1987. get cycleLengthLast(){return this._cycleLengthLast;}
  1988. set cycleLengthLast(v){this._cycleLengthLast = v;}
  1989. get cycleStart(){return this._cycleStart;} // the daystart of the current cycle
  1990. set cycleStart(v){this._cycleStart = v;}
  1991. cycleStartMessageSent = true;
  1992. cycleStartHour = 17;
  1993. get menstruationLength(){return 3;}
  1994. get ovulationDay(){return this.cycleStart + this.cycleLength - 14;}
  1995. ovulationHour = 19;
  1996. get cyclePhase(){
  1997. if(this.effectIsActive('cycleBlocked'))
  1998. return 'blocked';
  1999. const today = State.variables.time.daystart;
  2000. if(today - this.cycleStart - 1 > this.cycleLength || (today - this.cycleStart > this.cycleLength && State.variables.time.hour >= this.cycleStartHour))
  2001. this.cycleStartNew();
  2002. if(today == this.cycleStart){
  2003. //if(State.variables.time.hour < this.cycleStartHour)
  2004. // return 'end';
  2005. return 'menses';
  2006. }else if(this.cycleStart + this.menstruationLength >= today){
  2007. return 'menses';
  2008. }else if(today < this.ovulationDay){
  2009. return 'start';
  2010. }else if(today == this.ovulationDay && State.variables.time.hour < this.ovulationHour){
  2011. return 'start';
  2012. }else{
  2013. return 'end';
  2014. }
  2015. }
  2016. cycleStartNew(){
  2017. const today = State.variables.time.daystart;
  2018. this.cycleStart = today;
  2019. this.cycleStartMessageSent = false;
  2020. this.cycleLengthLast = this.cycleLength;
  2021. if(this.cycleLength >= 35)
  2022. this.cycleLength += rand(-3,0);
  2023. else if(this.cycleLength >= 33)
  2024. this.cycleLength += rand(-2,1);
  2025. else if(this.cycleLength >= 26)
  2026. this.cycleLength += rand(-1,1);
  2027. else if(this.cycleLength >= 24)
  2028. this.cycleLength += rand(-1,2);
  2029. else
  2030. this.cycleLength += rand(0,3);
  2031. this.cycleStartHour = rand(0,23);
  2032. this.ovulationHour = rand(0,23);
  2033. }
  2034. get cycleArousalModificator(){
  2035. switch(this.cyclePhase){
  2036. case 'end':
  2037. return {factor: 0.5, target: 0};
  2038. case 'menses':
  2039. return {factor: 1, target: 0};
  2040. case 'start':
  2041. default:
  2042. const ovulationDay = this.ovulationDay;
  2043. const daystart = State.variables.time.daystart;
  2044. const daysTilOvolationDay = ovulationDay - daystart;
  2045. if(daysTilOvolationDay <= 1)
  2046. return {factor: 2, target: 30};
  2047. if(daysTilOvolationDay <= 2)
  2048. return {factor: 1.5, target: 10};
  2049. return {factor: 1, target: 0};
  2050. }
  2051. }
  2052. get cycleProductsEnabled(){
  2053. const today = State.variables.time.daystart;
  2054. if(this.cyclePhase == 'menses')
  2055. return true;
  2056. if(today - this.cycleStart >= this.cycleLength)
  2057. return true;
  2058. return false;
  2059. }
  2060. _cycleProduct = null;
  2061. get cycleProduct() {
  2062. return this._cycleProduct;
  2063. }
  2064. set cycleProduct(value) {
  2065. this._cycleProduct = value;
  2066. }
  2067. cycleProductSince = null;
  2068. get cycleProductsUsed(){
  2069. if(State.variables.time.secondsSinceDate(this.cycleProductSince) > 43200)
  2070. return false;
  2071. return !(!(this._cycleProduct));
  2072. }
  2073. //#endregion
  2074. //#region Indecencies
  2075. get indecencies(){
  2076. let result = [];
  2077. if(this.cyclePhase == 'menses' && !this.cycleProductsUsed)
  2078. result.push('menses_blood');
  2079. if(State.variables.wardrobe.isNaked){
  2080. if(!State.variables.wardrobe.bra.isValidItem)
  2081. result.push('naked_breast');
  2082. if(!State.variables.wardrobe.panties.isValidItem)
  2083. result.push('naked_pussy');
  2084. }
  2085. if(this.cumVisibleBodypartIds.length > 0)
  2086. result.push('cum');
  2087. return result;
  2088. }
  2089. get isIndecent(){
  2090. return (!(this.indecencies.length == 0));
  2091. }
  2092. //#endregion
  2093. //#region SYSTEM
  2094. constructor(){}
  2095. _init(playerCharacter: { [x: string]: any; }){
  2096. Object.keys(playerCharacter).forEach(function (pn) {
  2097. this[pn] = clone(playerCharacter[pn]);
  2098. }, this);
  2099. return this;
  2100. }
  2101. clone = function () {
  2102. return (new setup.PlayerCharacter())._init(this);
  2103. };
  2104. toJSON = function () {
  2105. var ownData = {};
  2106. Object.keys(this).forEach(function (pn) {
  2107. if(typeof this[pn] !== "function")
  2108. ownData[pn] = clone(this[pn]);
  2109. }, this);
  2110. return JSON.reviveWrapper('(new setup.PlayerCharacter())._init($ReviveData$)', ownData);
  2111. };
  2112. //#endregion
  2113. }
  2114. setup.PlayerCharacter = PlayerCharacter;