12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510 |
- declare let rand: Window["rand"];
- const timed_stat_changes ={ //Hourly
- energy: {
- sleep: 2,
- default: 6
- },
- hydra: {
- sleep: 4,
- default: 12
- },
- sleep:{
- sleep: -8,
- default: 4
- }
- }
- const dieRisks = {
- 'hunger':{
- variable: 'pcs_energy',
- durations: [720,720,14400]
- },
- 'thirst':{
- variable: 'pcs_hydra',
- durations: [720,720,1440]
- }
- }
- const fameAlwaysLocale = ['sex','prostitute'];
- class PlayerCharacter{
- get INSTANCE():PlayerCharacter{return State.variables.pc;}
- gameover:string;
- _death = {};
- //#region Name
- name_first = 'Svetlana';
- name_last = 'Lebedev';
- name_nick = 'Sveta';
- get name_full(){return `${this.name_first} ${this.name_last}`;}
- //#endregion
- //#region Images
- /**
- * A custom path to an image-file for the portrait of the character.
- * @type {string}
- */
- avatar:string = '';
-
- /**
- * Path of an image-file that is used as the portrait of the character.
- * Uses `avatar` if set, otherwise a combination of hair length and color as specified by `:: $face_image`.
- * @readonly
- * @type {string}
- */
- get image():string{
- return this.avatar || this.bodyImage('hair');
- }
- bodyImage(part:string|undefined=undefined):string{
- const wardrobe = State.variables.wardrobe;
- switch(part){
- case undefined:
- case 'body':
- /*
- <<if ($pc.knowpreg == 1 or ($pc.preg == 1 and $pc.thinkpreg == 1) or ($pc.preg == 1 and $pc.PregChem > 3600)) and $pc.bodset == 3>>
- <<if $pc.PregChem > 6216>>
- <<set $result = 'pc/body/shape/'+$bodimgsets[(($pc.bodset * 10) + 9)]+'/8.jpg'>>
- <<elseif $pc.PregChem < 2688>>
- <<set $result = 'pc/body/shape/'+$bodimgsets[(($pc.bodset * 10) + 9)]+'/0.jpg'>>
- <<else>>
- <<set $result = 'pc/body/shape/'+$bodimgsets[(($pc.bodset * 10) + 9)]+'/(($pc.PregChem - 2184) / 504).jpg'>>
- <</if>>
- <<elseif $pc.salocatnow >= 1 and $pc.salocatnow <= 5>>
- <<set $result = 'pc/body/shape/'+$bodimgsets[(($pc.bodset * 10) + 9)]+'/'+$pc.salocatnow+'.jpg'>>
- <<elseif $pc.salocatnow <= 0>>
- <<set $result = 'pc/body/shape/0.jpg'>>
- <<elseif $pc.salocatnow == 6>>
- <<if getvar("$imgset6ovr["+$pc.bodset+"]") == 1>>
- <<set $result = 'pc/body/shape/'+$bodimgsets[(($pc.bodset * 10) + 9)]+'/6.jpg'>>
- <<else>>
- <<set $result = 'pc/body/shape/6.jpg'>>
- <</if>>
- <<else>>
- <<if getvar("$imgset7ovr["+$pc.bodset+"]") == 1>>
- <<set $result = 'pc/body/shape/'+$bodimgsets[(($pc.bodset * 10) + 9)]+'/7.jpg'>>
- <<else>>
- <<set $result = 'pc/body/shape/7.jpg'>>
- <</if>>
- <</if>>
- */
- let imageIndex = Math.clamp(this.bmiCategory+2,1,5);
- return `pc/body/shape/default_low/${imageIndex}.jpg`;
- case 'bra':
- if(wardrobe.bra.isValidItem)
- return wardrobe.bra.image;
- return this.bodyImage('breasts');
- case 'clothes':
- /*
- <<if $wardrobe.clothingworntype == 'nude' and getvar("$towel") == 1 and !$wardrobe.isWearingPanties>>
- <<set $result = 'pc/body/towel.jpg'>>
-
- <<elseif $wardrobe.clothingworntype == 'nude' and getvar("$robe") == 1>>
- <<set $result = 'pc/body/robe.jpg'>>
-
- <<elseif $wardrobe.clothingworntype == 'nude' and $wardrobe.isWearingPanties>>
- <<set $result = 'pc/body/nude.jpg'>>
-
- <<elseif $wardrobe.clothingworntype == 'nude' and !$wardrobe.isWearingPanties>>
- <<set $result = 'pc/body/nude1.jpg'>>
-
- <<elseif $wardrobe.clothingworntype == 'misc_outfits' and $wardrobe.clothingwornnumber == 1>>
- <<set $result = setup.func('$clothing_image', $wardrobe.clothingworntype, $wardrobe.clothingwornnumber)>>
-
- <<else>>
- <<set $result = $wardrobe.clothes.image>>
- <!--
- <<set $result = setup.func('$clothing_image', $wardrobe.clothingworntype, $wardrobe.clothingwornnumber)>>
-
- <<if getvar("$PClobimbo") == 1>>
-
- <</if>> -->
- <!--<<if $wardrobe.clothingworntype != 'coat' and !$wardrobe.isWearingSwimwear>>
- <<gs 'clothing_attributes' $wardrobe.clothingworntype $wardrobe.clothingwornnumber>>
- <<gs 'clothing_descriptions'>>
-
- <<else>>
- <<if $wardrobe.clothingworntype == 'danilovich_swimsuit'>>
- setup.func('$attributes_danilovich_swim_one', $wardrobe.clothingworntype, clothingwornnumber)
-
- <<elseif $wardrobe.clothingworntype == 'scandalicious_swimsuit'>>
- setup.func('$attributes_scandalicious_swim_one', $wardrobe.clothingworntype, clothingwornnumber)
-
- <<elseif $wardrobe.clothingworntype == 'scandalicious_bikinis'>>
- setup.func('$attributes_scandalicious_swim_two', $wardrobe.clothingworntype, clothingwornnumber)
-
- <<elseif $wardrobe.clothingworntype == 'allure_swimsuit'>>
- setup.func('$attributes_allure_swim_one', $wardrobe.clothingworntype, clothingwornnumber)
-
- <<elseif $wardrobe.clothingworntype == 'allure_bikinis'>>
- setup.func('$attributes_allure_swim_two', $wardrobe.clothingworntype, clothingwornnumber)
-
- <<elseif $wardrobe.clothingworntype == 'nerdvana_swimsuit'>>
- setup.func('$attributes_nerdvana_swim_one', $wardrobe.clothingworntype, clothingwornnumber)
-
- <<elseif $wardrobe.clothingworntype == 'nerdvana_bikinis'>>
- setup.func('$attributes_nerdvana_swim_two', $wardrobe.clothingworntype, clothingwornnumber)
-
- <</if>>
- <</if>>
- <<if $wardrobe.clothingworntype == 'gm_maid' or $wardrobe.PCloStyle2 == 1>>
-
- <<elseif $wardrobe.clothingworntype == 'gm_server' or $wardrobe.PCloStyle2 == 2>>
-
- <<elseif $wardrobe.clothingworntype == 'eroto_strip' or $wardrobe.PCloStyle2 == 3>>
-
- <</if>>-->
- <</if>>
- */
- if(wardrobe.clothes.isValidItem)
- return wardrobe.clothes.image;
- if(wardrobe.panties.isValidItem)
- return 'pc/body/nude.jpg';
- return 'pc/body/nude1.jpg';
- case 'hair':
- return setup.func("$face_image");
- case 'coat':
- if(wardrobe.coat.isValidItem)
- return wardrobe.coat.image;
- return '';
- case 'panties':
- if(wardrobe.panties.isValidItem)
- return wardrobe.panties.image;
- return setup.func('$pube_image');
- case 'shoes':
- if(wardrobe.shoes.isValidItem)
- return wardrobe.shoes.image;
- return 'pc/body/feet.jpg';
- case 'tits':
- case 'breasts':
- /*
- <<if $pc.nipples >= 40 and $pc.nipples < 60 and $pc.tits == 2>>
- <<set $result = 'pc/body/tits/t'+$pc.tits+'_p.jpg'>>
- <<else>>
- <<set $result = 'pc/body/tits/t'+$pc.tits+'.jpg'>>
- <</if>>
- */
- return 'pc/body/tits/t'+this.tits+'.jpg';
- }
- return '';
- }
- /*<<case 'bodysuit'>>
- <<if !$wardrobe.isWearingBra>>
- <<set $result = 'pc/body/tits/ttits.jpg'>>
-
- <<else>>
- <<set $result = setup.func('$pcs_outfit_image', $pc.bodysuitworntype+'_bodysuits', bodysuitwornnumber)>>
-
- <</if>>*/
- /*
- <<case 'teeth'>>
- <<if getvar("$pcs_brace") == 1>>
- <<set $result ='pc/body/teeth/brace.jpg'>>
- <<elseif $pc.pcs_teeth == -1>>
- <<set $result ='pc/body/teeth/goodteeth.jpg'>>
- <<elseif $pc.pcs_teeth == 1>>
- <<set $result ='pc/body/teeth/badteeth1.jpg'>>
- <<elseif $pc.pcs_teeth == 2>>
- <<set $result ='pc/body/teeth/badteeth2.jpg'>>
- <<else>>
- <<set $result ='pc/body/teeth/averageteeth.jpg'>>
- <</if>>*/
- //#endregion
- dailyUpdate(){
- this.bodyDailyUpdate();
- }
- //#region Birthday & Age
- birthday = 1;
- birthmonth = 4;
- birthyear = 1999;
- get birthdayDate(){
- return new Date(Date.UTC(this.birthyear,this.birthmonth-1,this.birthday));
- }
- get age(){
- let age = State.variables.time.ageOfDate(this.birthday,this.birthmonth,this.birthyear);
- if(age < 16)
- {
- console.error("Critical Error: Playercharacter too young");
- Engine.restart();
- }
- return age;
- }
- set age(v){
- v = Math.max(v,16);
- let currentAge = this.age;
- let difference = v - currentAge;
- this.birthyear -= difference;
- console.log("AGE set to "+v);
- }
- //#endregion
- //#region Visual Age
- _visualAgeDaysOffset = 0;
- get visualAgeDays(){return this._visualAgeDaysOffset}
- set visualAgeDays(v){this._visualAgeDaysOffset = v;}
- get visualAgeDaysInverse(){
- return this._visualAgeDaysOffset * -1;
- }
- set visualAgeDaysInverse(v){
- this._visualAgeDaysOffset = v * -1;
- }
- get visualAge(){
- return State.variables.time.ageOfDate(this.visualBirthday);
- }
- set visualAge(v){
- let currentVisualAge = this.visualAge;
- let offset = v - currentVisualAge;
- this._visualAgeDaysOffset += Math.floor(offset * 365.25);
- }
- get visualBirthday(){
- let visualBirthday = this.birthdayDate;
- visualBirthday.setUTCDate(visualBirthday.getUTCDate() - this._visualAgeDaysOffset);
- return visualBirthday;
- }
- //#endregion
- //#region Mood
- _mood = new setup.Mood();
- get mood(){return this._mood.mood;}
- set mood(v){this._mood.mood = v}
- //get moodlets(){return this._mood.moodlets}
- get moodletsActive(){return this._mood.moodletsActive}
- get moodletsActiveByGroup(){return this._mood.moodletsActiveByGroup}
- get moodletsActiveEffect(){return this._mood.moodletsActiveEffect}
- get moodletsActiveByGroupAccumulationApplied(){return this._mood.moodletsActiveByGroupAccumulationApplied}
- moodletApplyById(moodletId,minutes=0){return this._mood.moodletApplyById(moodletId,minutes)}
- //moodletCombinedData(moodletId){return this._mood.moodletCombinedData(moodletId)}
- moodletDeactivateById(moodletId){return this._mood.moodletDeactivateById(moodletId)}
- moodletIncTime(moodletId,minutes,skipIncludedMoodlets=false){return this._mood.moodletIncTime(moodletId,minutes,skipIncludedMoodlets)}
- //moodletIsActive(moodletId){return this._mood.moodletIsActive(moodletId)}
- moodletUpdate(moodletId,updateObject){return this._mood.moodletUpdate(moodletId,updateObject)}
- #moodletsClean(){return this._mood._moodletsClean();}
-
- get moodletsSpecial():{[key: string]: ActiveMoodlet}{
- //return Object.assign({},this.moodletPain,this.activeEffectsMoodlets);
- return {pain:this.moodletPain};
- }
- get moodletPain():ActiveMoodlet{
- return PainMoodlet.createPainMoodlet(this.painTotal);
- }
- //#endregion
- //#region Personality
- //#region Deprecated
- // ----- Personality -----
- _pcs_dom = 0
- get pcs_dom(){return this._pcs_dom;}
- set pcs_dom(v){
- if(v < 0){
- this.pcs_sub -= v;
- this._pcs_dom = 0;
- }else{
- this._pcs_dom = Math.min(100,v);
- this._balanceDomSub();
- }
- }
- _pcs_sub = 0
- get pcs_sub(){return this._pcs_sub;}
- set pcs_sub(v){
- if(v < 0){
- this.pcs_dom -= v;
- this._pcs_sub = 0;
- }else{
- this._pcs_sub = Math.min(100,v);
- this._balanceDomSub();
- }
- }
- _balanceDomSub(){
- if(this._pcs_dom > 0 && this._pcs_sub > this._pcs_dom){
- this._pcs_sub -= this._pcs_dom;
- this._pcs_dom = 0;
- }
- if(this._pcs_sub > 0 && this._pcs_dom > this._pcs_sub){
- this._pcs_dom -= this._pcs_sub;
- this._pcs_sub = 0;
- }
- }
- //#endregion
-
- //#endregion
- //#region Mental Capacity
- get consciousness(){
- return this.#activeEffectValueByKey('consciousness','*');
- }
-
- _pcs_willpwr = 70;
- get pcs_willpwr(){return this._pcs_willpwr;}
- set pcs_willpwr(v){
- this._pcs_willpwr = Math.clamp(v,0,this.willpowermax);
- console.log("Willpower set to "+this._pcs_willpwr);
- }
- will_counter = 0;
- _willpowermax = 70;
- get willpowermax(){return this._willpowermax}
- set willpowermax(v){this._willpowermax = Math.max(50,v);}
- pcs_willpower_feeder = 0;
- //#endregion
- //#region Main Stats (Hunger, Thirst & Sleep)
- consume(consumable:string|Consumable,percentage=100){
- if(typeof consumable == 'string')
- consumable = Consumable.get(consumable);
- consumable.consume(this,percentage);
- }
- //#region Hunger and Eating
- _energyBalance = 0; // A value between -6 and 6 that determines how fast you gain or lose bmi.
- _energy = 0; // The energy you consumed today.
- _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.
- _pcs_energy = 100;
- /**
- * This is effecitvely the hunger-bar.
- * @date 7/23/2023 - 5:56:29 PM
- *
- * @type {number}
- */
- get pcs_energy(){return this._pcs_energy;}
- set pcs_energy(v){
- this._pcs_energy = Math.clamp(v,0,100);
-
- if(v > 0 && this._death['hunger']?.stage){
- this._death['hunger'] = {stage:0};
- for(let i=1;i<=dieRisks.hunger.durations.length;i++)
- this.moodletDeactivateById('hunger_'+i);
- }
- }
- _dieHungerStage = 0;
- _dieHungerNextStageDate = undefined;
-
- /**
- * Intake in calories. Influences BMI in the long run.
- * @type {number}
- */
- get energy(){return this._energy;}
- set energy(v){this._energy = v;}
- get energyBalance(){return this._energyBalance;}
- set energyBalance(v){this._energyBalance = v;}
- get energyDemand(){return this._energyDemand;}
- set energyDemand(v){this._energyDemand = v;}
-
- /**
- * Energy Demand adjusted for the current BMI.
- * @date 7/27/2023 - 11:47:05 AM
- *
- * @readonly
- * @type {number}
- */
- get energyRequirement(){
- let baseRequirement = this.energyDemand; //timed_stat_changes.energy.sleep * 8 + timed_stat_changes.energy.default * 16;
- let requirementFactor = baseRequirement / 100;
- let bmi = this.bmi;
- // The following calculations assume that the base requirement is 100, therefore we need to multiplicate them with requirementFactor
- if(bmi < 19)
- return requirementFactor * (100 - Math.sqrt(19-bmi) * 10);
- if (bmi > 25)
- return requirementFactor * (100 + 5 * (bmi - 25));
- return baseRequirement;
- }
- _energyHistory = [];
- get energyHistory(){return this._energyHistory;}
- _energyHistoryLengthTarget = 7;
- get energyHistoryLengthTarget(){return this._energyHistoryLengthTarget;}
- set energyHistoryLengthTarget(v){
- this._energyHistoryLengthTarget = v;
- while(this._energyHistory.length > v)
- this._energyHistory.shift();
- }
- dailyEnergyUpdate(){
- let energyRequirement = this.energyRequirement;
- let energy = this.energy;
- let energyQuota = energy / energyRequirement;
- let balanceTarget = 0;
- if(energyQuota < 0.5)
- balanceTarget = -6;
- else if(energyQuota < 0.8)
- balanceTarget = -3;
- else if(energyQuota > 1.5)
- balanceTarget = 6;
- else if(energyQuota > 1.2)
- balanceTarget = 3;
-
- let balanceCurrent = this.energyBalance;
- let balanceDifference = balanceTarget - balanceCurrent;
- // Gaining weight is way easier than losing it.
- if(balanceDifference < 0)
- balanceDifference = balanceDifference / 3;
- else
- balanceDifference = balanceDifference * 2 / 3;
- this.energyBalance += balanceDifference;
- // Change the BMI
- let bmi_change = Math.pow(this.energyBalance,2) * 0.005 * Math.sign(this.energyBalance);
- this.bmi += bmi_change;
- console.log("$pc.dailyEnergyUpdate(): energyRequirement,energy,balanceOld, balanceNew, bmiChange, bmiNew",energyRequirement,energy,balanceCurrent,this.energyBalance,bmi_change,this.bmi);
- this._energyHistory.push({
- energyRequirement: energyRequirement,
- energy: energy,
- balanceTarget: balanceTarget,
- balanceChange: balanceDifference,
- balanceNew: this.energyBalance,
- bmiChange: bmi_change,
- bmiNew: this.bmi
- });
- this.energy = 0;
- this.energyDemand = 0;
- }
- get pcs_weight(){
- return Math.pow(this.height / 100,2) * this.bmi;
- }
- _bmi = 20;
- get bmi(){
- return this._bmi;
- }
- set bmi(v){
- this._bmi = v;
- }
-
- /**
- * Returns the bmi-category
- *
- * @readonly
- * @type {(-2|-1|0|1|2|3|4|5)} -2: severely underweight, -1: underweight, 0: normal, 1: overweight, 2-5: increasingly obese
- */
- get bmiCategory():-2|-1|0|1|2|3|4|5{
- let bmi = this.bmi;
- if(bmi<16)
- return -2;
- if(bmi<18.5)
- return -1;
- if(bmi<25)
- return 0;
- if(bmi<30)
- return 1;
- if(bmi<35)
- return 2;
- if(bmi<40)
- return 3;
- if(bmi<45)
- return 4;
- return 5;
- }
- //#endregion
- //#region Drinking
- _pcs_hydra = 100;
- _dieThirstStage = 0;
- _dieThirstNextStageDate = undefined;
- get pcs_hydra(){return this._pcs_hydra;}
- set pcs_hydra(v){
-
- /*if(v < 0){
- v = 0;
- this.pcs_health -= 5;
- setup.msgPredefined("warn_hydra_low");
- }
- */
- this._pcs_hydra = Math.clamp(v,0,100);
- if(v > 0 && this._death['thirst']?.stage){
- this._death['thirst'] = {stage:0};
- for(let i=1;i<=dieRisks.thirst.durations.length;i++)
- this.moodletDeactivateById('thirst_'+i);
- }
- }
- //#endregion
- //#region Sleep
- _pcs_sleep = 100;
- get pcs_sleep(){return this._pcs_sleep;}
- set pcs_sleep(v){
- /*if(v < 0){
- v = 0;
- this.mood -= 5;
- setup.msgPredefined("warn_sleep_low");
- }*/
- this._pcs_sleep = Math.clamp(v,0,100);
- console.log("Sleep set to "+this._pcs_sleep);
- }
- isSleeping = 0;
-
- //#endregion
- minutesTilStat(stat,target=0,isAsleep=undefined){
- const time = State.variables.time;
- isAsleep ??= (this.isSleeping == 1);
-
- let current = 0;
- let configIndex;
- switch(stat){
- case 'energy':
- case 'hunger':
- configIndex = 'energy';
- current = this.pcs_energy;
- break;
- case 'hydra':
- case 'thirst':
- configIndex = 'hydra';
- current = this.pcs_hydra;
- break;
- }
- const timeMod = 0.25 * this.timeFactor;
- let change = 0;
- if(isAsleep)
- change = timed_stat_changes[configIndex]['sleep'] * timeMod;
- else
- change = timed_stat_changes[configIndex]['default'] * timeMod;
- let requiredUpdates = Math.ceil((current - target) / (change || 1));
-
- const minutesToNext15MinutesInterval = 15 - time.now.getUTCMinutes() % 15;
- return Math.max(0,(requiredUpdates - 1)*15 + minutesToNext15MinutesInterval);
- }
- //#endregion
-
- //#region Body Odor
- deodorant_on = 0;
- deodorant_time = 0;
- _pcs_sweat = 0;
- get pcs_sweat(){return this._pcs_sweat;}
- set pcs_sweat(v){
- this._pcs_sweat = v;
- console.log("Sweat set to "+this._pcs_sweat);
- }
-
- sweatAdd(v:number){
- this.pcs_sweat += v;
- }
- //#endregion
- //#region Arousal
- get hornyMin(){
- const cycleArousalModificator = this.cycleArousalModificator;
- return cycleArousalModificator.target;
- }
- _pcs_horny = 0;
- get horny(){return Math.max(this._pcs_horny,this.hornyMin);}
- set horny(v){
- if(typeof v != "number" || isNaN(v)){
- console.error("Trying to set pcs_horny to non-number",v);
- return;
- }
- this._pcs_horny = Math.clamp(v,0,100);
- console.log("Horny set to "+this._pcs_horny);
- }
-
- /**
- * How much horny is supposed to decrease each minute.
- * @readonly
- * @type {number}
- */
- get hornyDeteriorationRate(){
- const cycleArousalModificator = this.cycleArousalModificator;
- return (1 / cycleArousalModificator.factor);
- }
- //#endregion
- //#region Inhibition
- get pcs_inhib(){
- return this.skillLevel('inhibition');
- }
- set pcs_inhib(v){
- this.skillSetLevel('inhibition',v);
- }
- //#endregion
-
- //#region Frost
- _frost = 0;
- get frost(){
- if(this.alko > 0)
- return 0;
- return this._frost;
- }
- set frost(v){
- this._frost = v;
- }
- //#endregion
- //#region Drugs
- _drugs = new setup.Drugs();
- get drugsActiveEffects(){return this._drugs.activeEffects}
- get drugsActiveEffectIds(){return this._drugs.activeEffectIds}
- drugsDeteriorate(minutes){return this._drugs.deteriorate(minutes)}
- drugVolInc(drugId, inc){return this._drugs.volInc(drugId, inc)}
- drugVolSet(drugId,v){return this._drugs.volSet(drugId, v)}
- drugVol(drugId){return this._drugs.vol(drugId)}
- get alko(){return this.drugVol('alcohol')}
- set alko(v){this.drugVolSet('alcohol',v)}
- //#endregion
-
- //#region Appearance History
- _appearanceHistory:Array<{time:Date,varname:string,val:string|number}> = [];
- appearanceHistoryPush(varname:string,val:string|number){
- this._appearanceHistory.push({
- time: State.variables.time.now,
- varname: varname,
- val: val
- });
- }
- //#endregion
- //#region Face
- //#region Eyes
-
- /**
- * 0: brown
- * 1: grey
- * 2: green
- * 3: blue
- * @type {(0|1|2|3)}
- */
- eyecolor:0|1|2|3 = 3;
- /**
- * 0: small
- * 1: medium
- * 2: large
- * 3: huge
- * @type {(0|1|2|3)}
- */
- eyesize:0|1|2|3 = 1;
- //#endregion
- faceGeneticAttractiveness = 0;
- faceSurgeries = 0;
- get faceAttractiveness(){
- return Math.clamp(this.faceGeneticAttractiveness+this.faceSurgeries,-3,3);
- }
- //#endregion
- // ----- Body ------
-
- _pcs_vag = 0
- get pcs_vag(){return this._pcs_vag;}
- set pcs_vag(v){this._pcs_vag = Math.min(36,v);}
- _pcs_ass = 0
- get pcs_ass(){return this._pcs_ass;}
- set pcs_ass(v){this._pcs_ass = Math.min(36,v);}
- _pcs_throat = 0
- get pcs_throat(){return this._pcs_throat;}
- set pcs_throat(v){this._pcs_throat = Math.min(36,v);}
- _pcs_health = 0;
- get pcs_health(){return Math.min(this._pcs_health, this.healthmax)}
- set pcs_health(v){
- /*if(v < 0)
- this.gameover = 1;*/
- this._pcs_health = Math.min(v, this.healthmax);
- }
- get healthmax(){
- let healthmax_calc = Math.max(1,(this.vitality * 10 + this.strength * 5));
- let mult_by_pain = 1;
- let pain_total = this.painTotal;
- if(pain_total > 80)
- mult_by_pain = 0.20;
- else if(pain_total > 60)
- mult_by_pain = 0.40;
- else if(pain_total > 40)
- mult_by_pain = 0.60;
- else if(pain_total > 20)
- mult_by_pain = 0.80;
- else if(pain_total > 0)
- mult_by_pain = 0.90;
- healthmax_calc = Math.ceil(healthmax_calc * mult_by_pain);
- return healthmax_calc;
- }
- _pcs_stam = 0;
- get pcs_stam(){return this._pcs_stam}
- set pcs_stam(v){this._pcs_stam = Math.min(v, this.stammax)}
- get stammax(){return Math.max(1,5 * (2 * this.vitality + this.agility + this.strength) / 2)}
- get speed(){return (2 * (this.strength + this.agility) + this.vitality) / 5}
- genbsize = 12; // the set genetic bust size
- nbsize = 12; // starts at a set genetic bust size, but can be adjusted down if salo drops too low
- silicone = 0;
- silicone_butt = 0;
- butt_cheat = 0;
- magicf2b = 0; //magicf2b = set in body_shape for the fat moved to bust
- /*get pcs_hips(){return (this.pcs_hgt * this.hratio) / 100 + this.vhips;}
- get pcs_waist(){return (this.pcs_hips * this.wratio) / 100 + this.vofat;}
- get pcs_band(){return (this.pcs_waist * this.bratio) / 100 + this.vofat;}
- get pcs_bust(){return (this.pcs_waist * this.bratio) / 100 + this.nbsize + this.magicf2b + this.silicone;}
- get pcs_butt(){return (this.pcs_hips / 10) + this.silicone_butt + this.butt_cheat;}*/
- //get pcs_cupsize(){return (this.pcs_bust - this.pcs_band);}
- _cupsize = 15;
- get pcs_cupsize(){return this._cupsize;}
- set pcs_cupsize(v){this._cupsize = v;}
- get tits(){
- if(this.pcs_cupsize <= 5) return 0;
- if(this.pcs_cupsize <= 10) return 1;
- if(this.pcs_cupsize <= 15) return 2;
- if(this.pcs_cupsize <= 20) return 3;
- if(this.pcs_cupsize <= 25) return 4;
- if(this.pcs_cupsize <= 30) return 5;
- if(this.pcs_cupsize <= 35) return 6;
- if(this.pcs_cupsize <= 40) return 7;
- if(this.pcs_cupsize <= 45) return 8;
- if(this.pcs_cupsize <= 50) return 9;
- if(this.pcs_cupsize <= 55) return 10;
- return 11;
- }
- set tits(v){
- this.pcs_cupsize = Math.ceil((v+1)*5);
- }
-
- /**
- * Cup size of the breasts.
- * @date 1/21/2024 - 12:20:29 PM
- *
- * @readonly
- * @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")}
- */
- get titsize(){
- switch(this.tits){
- case 0: return 'AA cup';
- case 1: return 'A cup';
- case 2: return 'B cup';
- case 3: return 'C cup';
- case 4: return 'D cup';
- case 5: return 'E cup';
- case 6: return 'F cup';
- case 7: return 'G cup';
- case 8: return 'H cup';
- case 9: return 'I cup';
- case 10: return 'J cup';
- case 11: return 'K cup';
- default: return '??? cup';
- }
- }
- //#region Height
- pcs_hgt = 170;
- /**
- * The characters height in cm.
- * @type {number}
- */
- get height(){
- return this.pcs_hgt;
- }
- set height(v){
- this.pcs_hgt = v;
- }
- //#endregion
- //#region Hair
- //#region Head
- pcs_hairbsh = 0;
- pcs_hairlng = 300;
-
- /**
- * Hair Length in mm
- * @type {number}
- */
- get hairLength(){return this.pcs_hairlng;}
- set hairLength(v){this.pcs_hairlng = v;}
- hairColor = 0;
- hairColorNatural = 0;
- hairDyeFade = 0;
- hairDye(newColor,fadeDuration=undefined){
- this.appearanceHistoryPush('hairColor',newColor);
- this.hairColor = newColor;
- this.hairDyeFade = fadeDuration ?? 30;
- }
- //#endregion
- //#region Legs
- _leghair = 0;
- /**
- * The length of the hair on the legs in mm.
- * @date 7/23/2023 - 9:25:25 AM
- *
- * @type {number}
- */
- get legHair(){return this._leghair;}
- set legHair(v){this._leghair = v;}
- legHairState = 0; //0: default, 1: lasered
- get legHairGrowth(){
- switch(this.legHairState){
- case 0: return (this.age < 18 ? 0.14 : 0.21);
- case 1: return 0;
- }
- }
- get legHairIsLasered(){return (this.legHairState == 1);}
- set legHairIsLasered(v){
- if(v){
- this.legHair = 0;
- this.legHairState = 1;
- }else{
- this.legHairState = 0;
- }
- }
- get legHairVisibility(){
- if(this.legHair <= 0)
- return 0;
- else if(this.legHair <= 0.5) // ~3 Days
- return 1;
- else if(this.legHair <= 1.5) // ~1 Week
- return 2;
- else if(this.legHair <= 6) // ~1 Month
- return 3;
- return 4;
- }
- set legHairVisibility(v){
- switch (v) {
- case 0: this._leghair = 0; return;
- case 1: this._leghair = 0.2; return;
- case 2: this._leghair = 1; return;
- case 3: this._leghair = 4; return;
- default:this._leghair = 20; return;
- }
- }
- //#endregion
- //#region Pubes
- pubesLength = 0; // Pubes hair length in mm
- pubestyle = 0; // The style the pubes get shaved into. 0 is not shaving.
- pubesState = 0; // 0: Default, 1: lasered
- get pubesGrowth(){
- switch(this.pubesState){
- case 0: return 0.5;
- case 1: return 0;
- }
- }
- get pubesAreLasered(){return (this.pubesState == 1);}
- set pubesAreLasered(v){
- if(v){
- this.pubesLength = 0;
- this.pubesState = 1;
- }else{
- this.pubesState = 0;
- }
- }
- //#endregion
- //#endregion
- preg = 0; // 1: is pregnant, 0: is not pregnant
- knowpreg = 0; // 1: Is pregnant and knows it, 0: doesn't know is pregnant but could be
- 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)
- PregChem = 0; // Size of the pregnancy
- clit_size = 0;
- get isPregnancyAware(){ // Either knows she is pregnant, correctly assumes to be pregnant or is big enough to no longer be in denial.
- if (this.knowpreg == 1) return 1;
- if (this.preg == 1 && this.thinkpreg == 1) return 1;
- if (this.preg == 1 && this.PregChem > 3600) return 1;
- return 0;
- }
- get bodset(){ //body image and descriptor control variable, used to indicate which image and descriptor set is in use
- /*if(this.isPregnancyAware == 1) return 3;
- if (this.muscularity >= 70) return 2;
- if (this.muscularity <= 40) return 0;*/
- return 1;
- }
- get body(){
- /*let bodimgsets = State.variables.bodimgsets;
- if(this.isPregnancyAware){
- if(this.PregChem > 6216)
- return bodimgsets[(this.bodset * 10) + 8];
- if(this.PregChem < 2688)
- return bodimgsets[this.bodset * 10];
- return bodimgsets[Math.floor(this.bodset * 10 + ((this.PregChem - 2184) / 504))];
- }
- if(this.salocatnow <= 7)
- return bodimgsets[((this.bodset * 10) + this.salocatnow)];
- return bodimgsets[(this.bodset * 10) + 7];*/
- return null;
- }
- //#region Teeth
- /*pcs_teeth = 0; //-1: perfectly white
- teeth = {
- brushed: 0,
- caffe_or_tea: 0,
- degradation:0 ,
- smoked: 0
- }
- get teethQuality(){return this.pcs_teeth}*/
- _teeth = 1;
- _teeethMissing = [];
- get teeth(){return this._teeth;}
- set teeth(v){this._teeth=Math.max(0,v);}
- get teethQuality(){
- let t = this.teeth;
- if(t < 100)
- return 0;
- else if(t < 1000)
- return 1;
- return 2;
- }
- set teethQuality(v){
- switch(v){
- case 0: this.teeth = 50; break;
- case 1: this.teeth = 500; break;
- case 2: this.teeth = 1500; break;
- }
- }
- get teethMissingCount(){
- return this._teeethMissing.length;
- }
- //#endregion
- //pcs_breath = 0;
- //#region Lipbalm
- _pcs_lipbalm = 0;
- get pcs_lipbalm(){return this._pcs_lipbalm;}
- set pcs_lipbalm(v){this._pcs_lipbalm = Math.max(0,v);}
- //#endregion
-
- //#region Skin Quality
- //moisturizerDailyCount = 0;
- //skinDailyGain = 0;
- //skinDailyPenalty = 0;
- _pcs_skin = 500;
- get pcs_skin(){
- return this._pcs_skin;
- }
- set pcs_skin(v){
- this._pcs_skin = Math.clamp(v,0,1000);
- }
- get skinAppearance(){
- return Math.round(this.pcs_skin / 200 - 2.5);
- }
- set skinAppearance(v){
- switch(v){
- case -3: this.pcs_skin = 0; return;
- case -2: this.pcs_skin = 100; return;
- case -1: this.pcs_skin = 300; return;
- case 0: this.pcs_skin = 500; return;
- case 1: this.pcs_skin = 700; return;
- case 2: this.pcs_skin = 900; return;
- case 3: this.pcs_skin = 1000; return;
- }
- }
- tan = 0;
- //#endregion
- bodyDailyUpdate(){
- this.dailyEnergyUpdate();
- /*if(this.muscularity > this.strength)
- this.muscularity -= 1;
- else if(this.muscularity < this.strength)
- this.muscularity += 1;
- if(this.healthiness > this.vitality)
- this.healthiness -= 1;
- else if(this.healthiness < this.vitality)
- this.healthiness += 1;
- if(this.dexterity > this.agility)
- this.dexterity -= 1;
- else if(this.dexterity < this.agility)
- this.dexterity += 1;*/
- /*if(this.fat > (17 + this.healthiness / 25)){
- this.salo += 1;
- this.fat = 0;
- }
- else if(this.fat < (-2 - (this.healthiness / 10))){
- this.salo -= 1;
- this.fat = 0;
- }
- this.fat = this.fat / 4;
- this.bodySaloCalc();*/
- this.hairLength += 1;
- /*if(this.pcs_lashes > 2){
- if(this.lashextensionstyle >= 1){
- this.lashextensionduration -= 1;
- if(this.lashextensionduration >= 1 && this.lashextensionduration <= 4)
- 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.");
- if(this.lashextensionduration <= 0){
- 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.");
- this.pcs_lashes = this.pcs_naturallashes;
- this.lashextensionstyle = 0;
- this.lashextensionduration = 0;
- this.lashextensionnew = 0;
- }
- }
- if(this.false_lashes > 0){
- this.false_lashes -= 1;
- if (this.false_lashes == 0){
- message("bad","Your false lashes came off in the night; there's no recovering them now.");
- this.pcs_lashes = this.pcs_naturallashes;
- }
- 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.");
- }
- }*/
- // Hair colour change
- //TODO: Dasdas
- /*if(this.pcs_haircol != this.nathcol){
- this.hairDyeFade = Math.max(this.hairDyeFade - 1 , 0);
- }*/
- // Leg and pubes hair growth
- this.legHair += this.legHairGrowth;
- //Pubic hair growth at 1/2 per night
- this.pubesLength += this.pubesGrowth;
- this.teeth += 1;
- console.info("Deactivated: degradation of pube hair coloring");
- /*<!-- !!pubic hair colouring-->
- <!-- !! pcs_pubecol = natural colour-->
- <!-- !! pcs_pubecol_num[1] = flag for saveupdate-->
- <!-- !! pcs_pubecol_num[2] = actual colour-->
- <!-- !! pcs_pubecol_num[3] = countdown timer for dye-->
- <<if $pcs_pubecol_num[2] != $pcs_pubecol>>
- <<setinit $pcs_pubecol_num[3] -= 1>>
- <<if $pcs_pubecol_num[3] < 0>>
- <<setinit $pcs_pubecol_num[3] = 0>>
- <</if>>
- <<if $pcs_pubecol_num[3] == 0>>
- <<setinit $pcs_pubecol_num[2] = $pcs_pubecol>>
- <</if>>
- <</if>>
- <<if getvar("$pubesLength") < 2>>
- <<setinit $pcs_pubecol_num[2] = $pcs_pubecol>>
- <</if>>*/
- console.info("Deactivated: degradation of hair scrunches");
- /*
- <<if getvar("$hscrunch") > 0>>
- <<set $hscrunchrand = rand(1, 100)>>
- <<if getvar("$hscrunchrand") <= 8>>
- <<setn $hscrunch -= 1>>
- <</if>>
- <</if>>*/
- /*if(this.pcs_skin <= 300)
- this.pcs_skin += Math.min(this.skinDailyGain * 2, 20) - this.skinDailyPenalty - 1;
- else if(this.$pcs_skin <= 600)
- this.pcs_skin += Math.min(this.skinDailyGain, 10) - this.skinDailyPenalty - 1;
- else if(this.pcs_skin <= 800)
- this.pcs_skin += Math.min(this.skinDailyGain / 2, 5) - this.skinDailyPenalty - 1;
- else if(this.pcs_skin <= 900)
- this.pcs_skin += Math.min(this.skinDailyGain / 3, 3) - this.skinDailyPenalty - 1;
- else if(this.pcs_skin <= 1000)
- this.pcs_skin += Math.min(this.skinDailyGain / 5, 2) - this.skinDailyPenalty - 1;
- if(this.pcs_teeth < 0){
- // Daly degradation of perfect white teeth
- let tempteeth = 1;
- if(this.teeth['caffe_or_tea'] > 8)
- tempteeth += 1;
- if(this.teeth['smoked'] > 1)
- tempteeth += 1;
- tempteeth -= Math.min(3, this.teeth['brushed']);
- this.teeth['degradation'] += Math.max(tempteeth, 0);
- this.teeth['brushed'] = 0;
- this.teeth['smoked'] = 0;
- this.teeth['caffe_or_tea'] = 0;
- if(this.teeth['degradation'] > 60)
- {
- // After a certain time of not taking care of your teeth you will loose you perfect whit smile.
- this.teeth['degradation'] = 0;
- this.pcs_teeth = 0;
- }
- this.teeth['degradation'] = Math.max(this.teeth['degradation'],0);
- }
- this.moisturizerDailyCount = 0;
- this.skinDailyGain = 0;
- this.skinDailyPenalty = 0;*/
- }
- get agility(){ return this.skillLevel('agility');} set agility(v){ this.skillSetLevel('agility',v);}
- get strength(){return this.skillLevel('strength');} set strength(v){this.skillSetLevel('strength',v);}
- get vitality(){return this.skillLevel('vitality');} set vitality(v){this.skillSetLevel('vitality',v);}
- //#region Appearance
- get pcs_makeup(){
- /*if(this.cosmetic_tattoo > 0)
- return this.cosmetic_tattoo + 1;
- return this._pcs_makeup;*/
- console.warn('Usage of get $pc.pcs_makeup is deprecated');
- return Math.floor((this.makeupAmount + this.makeupQuality)/2);
- }
- set pcs_makeup(v){
- console.warn('Usage of set $pc.pcs_makeup is deprecated');
- //this._pcs_makeup = v;
- this.makeupAmount = v;
- this.makeupQuality= v;
- }
- _makeupAmount = 0;
- get makeupAmount(){
- return this._makeupAmount;
- }
- set makeupAmount(v){
- this._makeupAmount = v;
- }
- _makeupQuality = 0;
- get makeupQuality(){
- return this._makeupQuality;
- }
- set makeupQuality(v){
- this._makeupQuality = v;
- }
- cosmetic_tattoo = 0;
- get appearance():Appearance{
- return Appearance.get(this);
- }
- get hotcat():number{
- return this.appearance.currentValue;
- }
- //#endregion
- // ----- Toys -----
- analplugin = 0;
- vibratorin = 0;
- // ----- Skills -----
- get intelligence(){
- return this.skillLevel('intelligence');
- }
- //#region Skills
- _skills:{[key: string]:SkillOfCharacter} = {
- }
- skill(key: string):SkillOfCharacter{
- return this._skills[key] ?? {
- ceil:0,floor:0,experience:0,experienceHistory:[0],lastUsed:0
- }
- }
- get skillsAll(){
- let result = {};
- for(const [skillId, skillData] of Object.entries(this._skills)){
- result[skillId] = Object.assign({},skillData,{
- level: this.skillLevel(skillId)
- });
- }
- return result;
- }
- get skillExperienceGainMult(){
- let lackOfSleep = 0; //TODO: get the correct value
- if(lackOfSleep >= 20)
- return 0.25;
- else if(lackOfSleep >= 10)
- return 0.5;
- else if(lackOfSleep >= 5)
- return 0.75;
- else if(lackOfSleep >= 2)
- return 0.9;
- return 1;
- }
- #skillGain(skillId:string,experience:number){
- return Skill.get(skillId).skillGain(this.skill(skillId),experience);
- }
-
- /**
- * Initializes a skill if it doesn't exist.
- * @param {string} skillId
- */
- #skillInit(skillId:string){
- if(!(skillId in this._skills))
- this._skills[skillId] = {
- lastUsed: 0,
- experience: 0,
- experienceHistory:[0],
- ceil: 0,
- floor: 0
- }
- }
- #skill_dayly(){
- let today = State.variables.time.daystart;
- for (const [skillId, skillData] of Object.entries(this._skills)){
- this._skills[skillId].experienceHistory.push(skillData.experience);
- if(this._skills[skillId].experienceHistory.length > 30)
- this._skills[skillId].experienceHistory.shift();
- this._skills[skillId].ceil = Math.max(skillData.experience, skillData.floor);
- this._skills[skillId].floor = Math.max(Math.ceil(skillData.experience/2), skillData.floor);
- let daysNotUsed = today - this._skills[skillId].lastUsed;
- let newexperience = skillData.experience;
- if(daysNotUsed > 14){
- let currentLevel = this.skill_exp2lvl(skillId,skillData.experience);
- let experienceForCurrentLevel = this.skill_lvl2exp(skillId,currentLevel);
- let experienceForPreviousLevel= this.skill_lvl2exp(skillId,currentLevel-1);
- let experienceDifferenceToPreviousLevel = experienceForCurrentLevel - experienceForPreviousLevel;
- let experienceLoss = Math.ceil(experienceDifferenceToPreviousLevel / 4);
- newexperience -= experienceLoss //* this.timeFactor;
- }
- this._skills[skillId].experience = Math.max(newexperience,this._skills[skillId].floor);
- }
- }
- skill_exp2lvl(skillId:string,experience:number){
- return Skill.get(skillId).experience2Skill(experience);
- }
- skill_lvl2exp(skillId:string,level:number){
- return Skill.get(skillId).skill2Experience(level);
- }
-
- /**
- * Increases the experience in one skill, taking daily limits into account. Returns the number of actual experience points gained.
- * @param {string} skillId
- * @param {number} inc
- * @returns {number}
- */
- skillExperienceGain(skillId:string,inc:number):number{
- if(inc == 0)
- return;
- this.#skillInit(skillId);
- const effectiveGain = this.#skillGain(skillId,inc);
- this._skills[skillId].experience += effectiveGain;
- console.log("Skill Experience Gain:",skillId,inc,effectiveGain,this._skills[skillId].experience );
- return effectiveGain;
- }
- skillsExperienceGain(skillObj:{[key: string]:number},factor=1){
- for(const [skillId,inc] of Object.entries(skillObj)){
- this.skillExperienceGain(skillId,inc*factor)
- }
- }
- skillExperienceHistory(skillId){
- this.#skillInit(skillId);
- let eh = clone(this._skills[skillId].experienceHistory);
- eh.push(this._skills[skillId].experience);
- return eh;
- }
- skillExperienceHistoryDayExpTuple(skillId){
- let today = State.variables.time.daystart;
- let eh = this.skillExperienceHistory(skillId);
- let result = [];
- let todayIndex = eh.length - 1;
- for(let i = 0; i < eh.length; i++){
- let day = -todayIndex + i + today;
- let tuple = [day,eh[i]];
- result.push(tuple);
- }
- return result;
- }
- skillFloor(skillId){
- this.#skillInit(skillId);
- return this._skills[skillId].floor;
- }
- skillLevel(skillId){
- this.#skillInit(skillId);
- return (
- this.skillLevelRaw(skillId) +
- this.#skillLevelBonusOfActiveEffect(skillId)
- );
- }
- skillLevelAtStartOfDay(skillId){
- return this.skill_exp2lvl(skillId,this.skill(skillId).experienceHistory.last());
- }
- #skillLevelBonusOfActiveEffect(skillId){
- let result = 0;
- for(const effect of Object.values(this.activeEffects))
- result += effect.skills?.[skillId] ?? 0;
- return result;
- }
- skillLevelRaw(skillId){
- return this.skill_exp2lvl(skillId,this.skill(skillId).experience);
- }
- //Is usually used for initialization, therefore we'll add one history entry if it doesn't exist
- skillSetLevel(skillId:string,lvl:number){
- this.#skillInit(skillId);
- let exp = this.skill_lvl2exp(skillId,lvl);
- this._skills[skillId].experience = exp;
- console.log("Setting skill level (Skill, Lvl, Exp)",skillId,lvl,exp);
- }
- //#endregion
- //#region Fetishes
- _fetishes = {};
- fetish(fetishId){
- return this._fetishes[fetishId] ?? 1;
- }
- fetishSet(fetishId,v){
- this._fetishes[fetishId] = v;
- }
- fetishes(fetishIds){
- if(typeof fetishIds == "string")
- return this.fetishes([fetishIds]);
- let result = 1;
- let indifferentCount = 0;
- let arouseCount = 0;
- for(const fetishId of fetishIds){
- const arousalOfFetish = this.fetish(fetishId);
- if(arousalOfFetish < 0)
- return arousalOfFetish;
- if(arousalOfFetish == 0)
- indifferentCount += 1;
- else if(arousalOfFetish > 1){
- result += arousalOfFetish - 1;
- arouseCount += 1;
- }
- }
- if(indifferentCount > 0 && arouseCount == 0)
- return 0;
- return result;
- }
- //#endregion
- //#region Sex-Stats
- /*
- Each Sex-Encounter has the following format:
- {
- 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.
- time:The time it happened (Date-Object (UTC-time))
- aware: Is Sveta aware of the sex-act? False in case of hypnosis, blackouts, etc.
- civ: Cum in vagina
- civa:Cum in vagina aware
- tags: Array of tags
- }
- */
- _sexEncounters = [];
- sexEncounterAdd(time,events){
- this._sexEncounters.push({
- time: time,
- events: events
- });
- }
- /*sexEncountersFiltered(maxage=Infinity,aware=undefined,civ=undefined,civa=undefined){
- let result = [];
- for (const sexEncounter of this._sexEncounters) {
- let ageOfEncounterInDays = Math.floor(State.variables.time.secondsSinceDate(sexEncounter.time) / 86400);
- if(ageOfEncounterInDays > maxage)
- continue;
- if(typeof aware === "boolean" && aware !== sexEncounter.aware) continue;
- if(typeof civ === "boolean" && civ !== sexEncounter.civ) continue;
- if(typeof civa === "boolean" && civa !== sexEncounter.civa) continue;
- result.push(sexEncounter);
- }
- return result;
- }*/
- sexEncountersFilteredByNpc(maxage=Infinity,aware=undefined){
- let result = {};
- for(const encounter of this._sexEncounters){
- for(const event of encounter.events){
- if(typeof aware === "boolean"){
- if((aware && event.unaware) || (!aware && !event.unaware))
- continue;
- }
- result[event.npc] ??= 0;
- result[event.npc] += 1;
- }
- }
- return result;
- }
- //Every Stat has two values: the number you are aware of and the number you are unaware of
- /** @access private */
- _sexStats = {
- vaginal: [0,0],
- vaginal_fist:[0,0],
- vaginal_dildo:[0,0],
- vaginal_strap:[0,0]
- }
- /**
- * Returns a sexstat or, it it isn't set, def. Returns undefined in case of an error.
- * @example
- * sexStat('vaginal_total','both',0);
- * @date 5/29/2023 - 9:32:32 AM
- *
- * @param {string} key
- * @param {string} [awareness='both'] - Either 'aware' or 'unaware' or 'both' (the sum of the former)
- * @param {number} [def=0]
- * @returns {number|undefined}
- */
- sexStat(key,awareness='both',def=0){
- switch(key){
- case 'vaginal_total':
- return 0;//return (this.stat('vaginal',awareness) + this.stat('vaginal_fist',awareness) + this.stat('vaginal_dildo',awareness) + this.stat('vaginal_strap',awareness));
- }
- if(!(key in this._sexStats))
- return def;
- switch(awareness){
- case 'both':
- return this._sexStats[key][0] + this._sexStats[key][1];
- case 'aware':
- return this._sexStats[key][0];
- case 'unaware':
- return this._sexStats[key][1];
- default:
- console.error('Invalid argument for PlayerCharacter.sexStat: awareness',awareness);
- }
- return undefined;
- }
- /**
- * Increases a sex-stat by inc.
- * @example
- * sexStatInc('vaginal','aware',1)
- * @date 5/29/2023 - 9:24:04 AM
- *
- * @param {string} key
- * @param {string} awareness - Either 'aware' or 'unaware'
- * @param {number} [inc=1]
- */
- sexStatInc(key,awareness,inc=1){
- let currentValue = this.sexStat(key,awareness);
- this.sexStatSet(key,awareness,currentValue+inc);
- }
- /**
- * Sets a sex-stat to v.
- * @example
- * sexStatSet('vaginal','aware',1)
- * @date 5/29/2023 - 9:30:58 AM
- *
- * @param {string} key
- * @param {string} awareness - Either 'aware' or 'unaware'
- * @param {number} v - The new value.
- * @returns {number|undefined} The new value or, if there was an error, undefined.
- */
- sexStatSet(key,awareness,v){
- this._sexStats[key] ??= [0,0];
- switch(awareness){
- case 'aware':
- this._sexStats[key][0] = v;
- return v;
- case 'unaware':
- this._sexStats[key][1] = v;
- return v;
- default:
- console.error('Invalid argument for PlayerCharacter.sexStatSet: awareness',awareness);
- return undefined;
- }
- }
- get isVirgin(){
- return (this.sexStat('vaginal','both') == 0) ? true : false;
- }
- get thinksIsVirgin(){
- return (this.sexStat('vaginal','aware') == 0) ? true : false;
- }
- //#endregion
- //#region Traits
- _traits = {
- nerd_status: 0
- }
- trait(key,def=0){
- if(key in this._traits)
- return this._traits[key];
- return def;
- }
- traitDec(key,v){
- this._traits[key] -= v;
- }
- traitInc(key,v){
- this._traits[key] += v;
- }
- traitSet(key,v){
- this._traits[key] = v;
- }
- //#endregion
- // ----- Arousal -----
- /*
- For checking arousal and when applicable triggering orgasms.
- action:
- All acts are from Sveta''s perspective and in cases of both giving and receiving, receiving should be used.
- It can be when receiving any of the following
- 'clit_finger' - Clit being stimulated directly by a finger
- 'clit_vibe' - Clit being stimulated directly by a vibrator (set low, use negative time and double/triple up for more power)
- 'porn' - viewing pornographic material
- 'voyeur_sex' - watching, usually as in spying on, other people have sex
- 'voyeur' - watching, usually as in spying on, erotic acts of others
- 'erotic' - being aroused by eroticism
- 'erotic_nudity' - being aroused by nudity of others
- 'trib' - rubbing pussy against another pussy
- 'massage' - rubbing your body, back, feet, etc. with their hands/arms
- 'cuni' - stimulation of your pussy by someones toungue
- 'rimming' - stimulation of your anus by someones toungue
- 'vaginal' 'vaginal_finger' 'vaginal_fist' 'vaginal_dildo' 'vaginal_strap' 'vaginal_vibe' - stimulation of your vagina with a penis and various others
- 'self_fisting' - fisting your own vagina
- 'anal' 'anal_finger' 'anal_fist' 'anal_dildo' 'anal_strap' 'anal_vibe' - stimulation of your anus with a penis and various others
- 'self_fisting_anal' - fisting your own anus
- 'kiss' - snogging, tonsil tennis, lip locking, etc.
- 'BDSM' - receiving candle wax, flogging, leash play, bondage etc
- 'pee' - being peed upon
- also when giving any of the following:
- 'flashlite' - flashing underwear
- 'flash' - flashing naked breasts/arse/vagina
- 'massage_give' - rubbing their body, back, feet, etc. with your hands/arms
- 'cuni_give' - stimulating someones pussy with your toungue
- 'rimming_give' - stimulating someones anus with your toungue
- 'vaginal_finger_give' 'vaginal_fist_give' 'vaginal_dildo_give' 'vaginal_strap_give' 'vaginal_vibe_give' - stimulating someones vagina in various ways
- 'clit_finger_give' - stimulating someones clit
- 'anal_finger_give' 'anal_fist_give' 'anal_dildo_give' 'anal_vibe_give' 'anal_strap_give' - stimulating someones anus in various ways
- 'hj' - jerking a guy off with your hand
- 'bj' - sucking a guy off
- 'dildo_suck' - simulating a bj on a dildo/strapon
- 'titjob' - using boobs to jerk off a guy
- 'footjob' - using feet to jerk off a guy
- 'BDSM_give' - giving candle wax, flogging, leash play, bondage etc
- 'pee_give' - peeing on somone
- finally
- 'foreplay' - receiving other stuff
- 'foreplay_give' - giving other stuff
- time
- 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.
- npcId
- specify the involved npc
- flags
- Are optional but can be themes involved in the act and can be any of the following:
- 'maso' 'bound' 'beast' 'exhibitionism' 'rough' 'prostitution' 'dom' 'sub' 'incest'
- 'feet' 'lesbian' 'group' 'gangbang' 'humiliation' 'deepthroat' 'unknown' 'gloryhole' 'rape' 'futa' 'masturbate'
- 'unaware'
- */
- _analCapacity = 0.5;
- get analCapacity(){
- return this._analCapacity;
- }
- _vaginalCapacity = 1;
- get vaginalCapacity(){
- return this._vaginalCapacity;
- }
- get vaginalLubrication(){
- return this.horny / 100;
- }
- analCapacityAdaptTo(v){
- if(v <= this._analCapacity)
- return;
- const sizeDifference = v - this._analCapacity;
- if(sizeDifference <= 1)
- this._analCapacity = Math.min(v, this._analCapacity + 0.1);
- else
- this._analCapacity = Math.min(v, this._analCapacity + sizeDifference / 10);
- }
- vaginalCapacityAdaptTo(v){
- if(v <= this._vaginalCapacity)
- return;
- const sizeDifference = v - this._vaginalCapacity;
- if(sizeDifference <= 1)
- this._vaginalCapacity = Math.min(v, this._vaginalCapacity + 0.1);
- else
- this._vaginalCapacity = Math.min(v, this._vaginalCapacity + sizeDifference / 10);
- }
-
- current_arousal_scene_actions = [];
- arouse(action,time,npcId=undefined,flags=undefined){
- console.warn("PlayerCharacter.arouse() is deprecated. Use <<arouse>> instead",action,time,npcId,flags);
- }
- //TODO
- arousalEnd(){
- console.warn("PlayerCharacter.arousalEnd() is deprecated");
- }
- //#region Cum
- /*
- Cum-Locations
- 0 = 'In your Vagina'
- 1 = 'On your labia'
- 2 = 'On your panties over your vagina'
- 3 = 'In your anus'
- 4 = 'On your butt'
- 5 = 'On your panties over your butt'
- 6 = 'On your clothes in your groin area'
- 7 = 'On your clothes'
- 8 = 'On your back'
- 9 = 'On your legs'
- 10 = 'On your arms'
- 11 = 'On your face'
- 12 = 'Inside your mouth'
- 13 = 'On your hands'
- 14 = 'On your stomach'
- 15 = 'On your breasts'
- 16 = 'In your hair'
- 17 = 'In a condom in your vagina
- */
- _cum:{ [key: string]: Array<any>} = {};
- get cums(){
- this._cumPurgeExpired();
- return this._cum;
- }
-
- /**
- * Removes cum-data on the inside of the PCs body.
- * @date 10/8/2023 - 2:16:00 PM
- */
- _cumPurgeExpired(){
- const bodyparts = setup.getBodyparts();
- let updatedCumData = {};
- const maxAgeInMinutes = 360; // 6 Hours
- let oldestPossibleDateTime = State.variables.time.now;
- oldestPossibleDateTime.setUTCMinutes(oldestPossibleDateTime.getUTCMinutes() - maxAgeInMinutes);
- for (const [bodypart, cumData] of Object.entries(this._cum)) {
- if(bodyparts[bodypart].inside)
- updatedCumData[bodypart] = cumData.filter(cum => cum.time.getTime() >= oldestPossibleDateTime.getTime());
- else
- updatedCumData[bodypart] = cumData;
- }
- this._cum = updatedCumData;
- }
- cum(npcId,bodypartId){
- let bodypartData = setup.getBodypart(bodypartId);
- if(!bodypartData || bodypartData.cumDisabled)
- console.warn('The following bodypart is not enabled for $pc.cum():',bodypartId);
- bodypartId = bodypartData.id;
- const time = State.variables.time;
- const now = time.now;
- const cumData = {
- npc: npcId,
- time: now
- };
- this._cum[bodypartId] ??= [];
- this._cum[bodypartId].push(cumData);
- }
- cumCleanAll(){
- this._cum={};
- }
- cumCleanByActivity(activityId){
- const bodyparts = Object.entries(setup.getBodyparts());
- const bodypartIdsToClean = bodyparts.filter(([id,bodypart]) => bodypart['clean'].includes(activityId)).map(([id,bodypart]) => id);
- for(let bodypartIdToClean of bodypartIdsToClean)
- this._cum[bodypartIdToClean] = [];
- }
- get cumBodypartIds(){
- return Object.entries(this.cums).filter(([bodypartId,cumArray]) => cumArray.length > 0).map(([bodypartId,cumArray]) => bodypartId);
- }
- get cumVisibleBodypartIds(){
- return this.cumBodypartIds.filter(bodypartId=>!this.bodyPartCovered(bodypartId));
- }
- //#endregion
-
- vgape = 0;
- agape = 0;
- spanked = 0;
- //#region Pain
- _pain = new setup.Pain();
- pain(bodypart:string){return this._pain.painByBodypart(bodypart)}
- get painByBodyparts(){return this._pain.painByBodyparts}
- get painByBodypartsSorted(){return this._pain.painByBodypartsSorted}
- painDeteriorate(timeToAdd:number){return this._pain.painDeteriorate(timeToAdd)}
- painInc(bodypart,reason='',v){return this._pain.painInc(bodypart,reason,v)}
- painReasonsByBodypartSorted(bodypart){return this._pain.reasonsByBodypartSorted(bodypart)}
- get painTotal(){return this._pain.painTotal}
- get painOfActiveEffects(){
- var painRaw = {};
- const activeEffects = this.activeEffects;
- for(const activeEffect of Object.values(activeEffects)){
- for(const [bodypartId,reasonData] of Object.entries(activeEffect.pain ?? {})){
- painRaw[bodypartId] ??= {};
- for(const [reasonId,painValue] of Object.entries(reasonData ?? {})){
- painRaw[bodypartId][reasonId] ??= 0;
- painRaw[bodypartId][reasonId] += painValue;
- }
- }
- }
- return painRaw;
- }
- get painReductionOfActiveEffects(){
- return this.#activeEffectValueByKey('painReduction','dim',0.5);
- }
- //#endregion
- timeFactor = 1; //Used for cheating
- //#region Fame
- _fame = {};
- fame(location,type=null){
- if(!type){
- console.warn('Calling $pc.fame in deprecated format.',location);
- let idParts = location.split('_');
- location = idParts[0];
- let type = idParts[1];
- return this.fame(location,type);
- }
- if(!(location in this._fame))
- return 0;
- if(type == 'slut')
- return this.fame(location,'sex') + this.fame(location,'prostitute');
- return this._fame[location][type] || 0;
- }
- fameDec(location,type,amount,local=false){
- this.fameInc(location,type,amount * -1,local);
- }
- fameInc(location,type,amount,local=false){
- this._fame[location] ??= {};
- this._fame[location][type] ??= 0;
- let current = this._fame[location][type];
- if(typeof amount == "string"){
- if(current > 900){ //The original says 1000... how is this possible if 1000 is supposed to be the soft cap?
- switch(amount){
- case 'tiny':
- case 'small':
- return;
- case 'medium': amount = rand(0,1);break;
- case 'large': amount = rand(1,2);break;
- case 'huge': amount = rand(1,4);break;
- case 'BronzeMedal': amount = rand(15,25);break;
- case 'SilverMedal': amount = rand(25,35);break;
- case 'GoldMedal': amount = rand(35,45);break;
- default:
- console.warn('Argument for amount not reckognized in $pc.fameInc:',amount);
- amount = 0;
- }
- }else if(current > 700){
- switch(amount){
- case 'tiny': amount = rand(0,1);break;
- case 'small': amount = rand(1,2);break;
- case 'medium': amount = rand(1,4);break;
- case 'large': amount = rand(6,12);break;
- case 'huge': amount = rand(10,24);break;
- case 'BronzeMedal': amount = rand(25,60);break;
- case 'SilverMedal': amount = rand(60,100);break;
- case 'GoldMedal': amount = rand(100,150);break;
- default:
- console.warn('Argument for amount not reckognized in $pc.fameInc:',amount);
- amount = 0;
- }
- }else if(current > 400){
- switch(amount){
- case 'tiny': amount = rand(1,2);break;
- case 'small': amount = rand(1,4);break;
- case 'medium': amount = rand(6,12);break;
- case 'large': amount = rand(10,24);break;
- case 'huge': amount = rand(20,50);break;
- case 'BronzeMedal': amount = rand(50,100);break;
- case 'SilverMedal': amount = rand(100,150);break;
- case 'GoldMedal': amount = rand(150,200);break;
- default:
- console.warn('Argument for amount not reckognized in $pc.fameInc:',amount);
- amount = 0;
- }
- }else{
- switch(amount){
- case 'tiny': amount = rand(1,4);break;
- case 'small': amount = rand(6,12);break;
- case 'medium': amount = rand(10,24);break;
- case 'large': amount = rand(20,50);break;
- case 'huge': amount = rand(40,70);break;
- case 'BronzeMedal': amount = rand(150,250);break;
- case 'SilverMedal': amount = rand(250,350);break;
- case 'GoldMedal': amount = rand(350,450);break;
- default:
- console.warn('Argument for amount not reckognized in $pc.fameInc:',amount);
- amount = 0;
- }
- }
- }else{
- if(current > 900)
- amount /= 10;
- else if(current > 700)
- amount /= 6;
- else if(current > 400)
- amount /= 3;
- }
- if(amount == 0)
- return;
- let target = Math.clamp(current+amount,0,1000);
- this._fame[location][type] = target;
- if(type in fameAlwaysLocale)
- local = true;
- }
- //#endregion
- //#region Effects
- _effects:{ [key: string]: { expiration:Date; [key: string]: any; } } = {};
-
- get activeEffectIds():string[]{
- return Object.keys(this.activeEffects);
- }
- get activeEffects():{[key: string]:any}{
- let result = {};
- const time = State.variables.time;
-
- for(const [effectid,effectData] of Object.entries(this._effects)){
- if(effectData.expiration === undefined)
- continue;
- if(effectData.expiration === null || time.isFuture(effectData.expiration))
- result[effectid] = effectData;
- }
- result = Object.assign({},this.drugsActiveEffects,result);
- return result;
- }
- get activeEffectsMoodlets():{ [key: string]: ActiveMoodlet; }{
- const time = State.variables.time;
- return Object.fromEntries(
- this.activeEffectsMoodletIDs.map(
- (moodletId) =>
- [
- moodletId,
- ActiveMoodlet.create(moodletId, {expiration: time.endTime})
- ]
- )
- );
- }
- get activeEffectsMoodletIDs():Array<string>{
- var result = [];
- const activeEffects = this.activeEffects;
- for(const activeEffect of Object.values(activeEffects)){
- if(activeEffect.moodlet)
- result.push(activeEffect.moodlet);
- }
- return result;
- }
- get activeEffectsSidebar(){
- return Object.values(this.activeEffects).filter(effect=>effect.sidebar);
- }
-
- /**
- * @param {string} key
- * @param {undefined|'+'|'*'|'dim'} [reduceMode='+']
- * @param {any[]} args Additional arguments.
- * @returns {number}
- */
- #activeEffectValueByKey(key,reduceMode='+',...args):number{
- const valueArray = this.#activeEffectValuesByKey(key);
- switch (reduceMode) {
- case '*':
- return valueArray.reduce((accumulator, currentValue) => accumulator * currentValue,1);
- case 'dim':
- const base = args[0] ?? 0.5;
- return valueArray.sort((a,b)=>b-a).reduce((accumulator, currentValue,index) => accumulator + Math.pow(base,index) * currentValue,0);
- case '+':
- default:
- return valueArray.reduce((accumulator, currentValue) => accumulator + currentValue,0);
- }
- }
-
- /**
- * @param {string} key
- * @returns {{}}
- */
- #activeEffectValuesByKey(key:string){
- var result =[];
- const activeEffects = this.activeEffects;
- for(const effect of Object.values(activeEffects)){
- if(key in effect)
- result.push(effect[key]);
- }
- return result;
- }
- /**
- * Description placeholder
- * @date 10/3/2023 - 1:07:07 PM
- *
- * @param {string} effectId
- * @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.
- */
- effectAdd(effectId, expirationOrDuration, metadata={}){
- const time = State.variables.time;
- let expiration = expirationOrDuration;
- if(typeof expirationOrDuration == 'number')
- expiration = time.nowWithMinutesOffset(expirationOrDuration);
- this._effects[effectId] = Object.assign({expiration:expiration},metadata) ;
- }
-
- /**
- * @param {string} effectId
- * @returns {boolean}
- */
- effectIsActive(effectId){
- return (effectId in this.activeEffects);
- }
- //#endregion
- //#region Timed Actions
- execute_every_15_minutes(){
- const time = State.variables.time;
- let timeMod = 0.25 * this.timeFactor; //We are doing some hourly calculations 4 times as often
- let change = {
- 'energy': 0,
- 'hydra': 0
- }
- if(this.isSleeping){
- change.energy = timed_stat_changes['energy']['sleep'] * timeMod;
- change.hydra = timed_stat_changes['hydra']['sleep'] * timeMod;
- }else{
- change.energy = timed_stat_changes['energy']['default'] * timeMod;
- change.hydra = timed_stat_changes['hydra']['default'] * timeMod;
- }
- this.pcs_energy -= change.energy;
- this.pcs_hydra -= change.hydra;
- this.energyDemand += change.energy;
- if(this.isSleeping){
- this.pcs_sleep -= timed_stat_changes['sleep']['sleep'] * timeMod ;
- this.pcs_stam += this.stammax / 10;
- this.pcs_willpwr += this.willpowermax / 100;
- }else{
- this.pcs_sleep -= timed_stat_changes['sleep']['default'] * timeMod ;
- this.pcs_stam += this.stammax / 20;
- this.pcs_willpwr += this.willpowermax / 300;
- }
- // ----- Dying -----
- for (const [riskId, riskData] of Object.entries(dieRisks)){
- this._death[riskId] ??= {stage:0};
- if(this[riskData['variable']] == 0){
- if(this._death[riskId].stage == 0 || !time.isFuture(this._death[riskId].nextStageDate)){
- if((this._death[riskId].stage ?? 0) >= riskData.durations.length){
- this.gameover = riskId;
- console.warn("GAMEOVER set",riskId);
- }else{
- this._death[riskId].nextStageDate = time.nowWithMinutesOffset(riskData.durations[this._death[riskId].stage]);
- this._death[riskId].stage += 1;
- this.moodletApplyById(riskId+'_'+this._death[riskId].stage);
- }
- }
- }
- }
- }
- execute_every_1_hour(){
- let timeFactor = this.timeFactor;
- if(this.mood <= 20)
- this.will_counter -= 2 * timeFactor;
- if(this.willpowermax > 100){
- if(this.pcs_willpwr < 25)
- this.will_counter -= 1 * timeFactor;
- }else{
- if(this.pcs_willpwr < this.willpowermax / 4)
- this.will_counter -= 1 * timeFactor;
- }
- if(this.will_counter <= -10){
- this.willpowermax -= 1;
- this.will_counter = 0;
- }
-
- this.pcs_lipbalm -= 1 * timeFactor;
- }
- execute_every_1_day(){
- this.bodyDailyUpdate();
- this.#skill_dayly();
- this.#moodletsClean();
- }
- execute_every_timeUpdate(timeToAdd:number){
- let timestamp = State.variables.time.minutesTimestamp;
- if(this.deodorant_on == 1 && timestamp > this.deodorant_time)
- this.deodorant_on = 0;
- //this.performShoePainAndExperience(timeToAdd);
- this.performInhibitionExperience(timeToAdd);
-
- this.horny -= timeToAdd * this.hornyDeteriorationRate;
- this.drugsDeteriorate(timeToAdd);
- this.painDeteriorate(timeToAdd);
- }
- //#endregion
- performInhibitionExperience(minutes){
- if(this.isSleeping)
- return;
- let balancingFactorExp = 0.5;
- let outsideFactor = 2;
- let location = State.variables.location;
- // You don't get any inhibition progress while being at home
- if(location.isHome)
- return;
- let totalExpFactor = balancingFactorExp * (location.isOutdoors ? outsideFactor : 1);
- let wardrobe = State.variables.wardrobe;
- let clothesInhib = wardrobe.clothes.inhibition;
- let playerInhib = this.pcs_inhib;
- if(playerInhib > clothesInhib)
- return;
- let inhibDiff = clothesInhib - playerInhib;
- let expRaw = Math.ceil(inhibDiff / 10) * 10; // Round up to 10, 20, etc.
- let experienceGained = expRaw * minutes / 60 * totalExpFactor;
- this.skillExperienceGain("inhibition",experienceGained);
- }
-
- //#region Deco
- // Deco includes tattoos, piercings, glasses, etc.
- _deco = {}
- _decoOwned = {}
- decoAdd(type,position,index){
- if(!this._decoOwned[type])
- this._decoOwned[type] = {};
- if(!this._decoOwned[type][position])
- this._decoOwned[type][position] = [];
- if(!this._decoOwned[type][position].includes(index))
- this._decoOwned[type][position].push(index);
- }
- decoHas(type:string,position:string,index:number){
- if(!(this._decoOwned[type]?.[position]))
- return false;
- return this._decoOwned[type][position].includes(index);
- }
-
- /**
- * Returns the index of a decoration of `type` worn at `position`.
- * @param {string} type
- * @param {string} [position='default']
- * @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.
- */
- decoGet(type:string,position: string='default'):number{
- return this._deco[type]?.[position] ?? 0;
- }
- decoImage(type:string,position:string){
- let constantDecoData = setup.getDeco(type,position,this.decoGet(type,position));
- return constantDecoData.image;
- }
- get decoOwned(){return this._decoOwned;}
- decoRemove(type,position='default'){
- this.decoSet(type,position,null);
- }
- decoSet(type,position='default',v){
- if(!(type in this._deco))
- this._deco[type] = {};
- this._deco[type][position] = v;
- }
- decoWear(type,position='default',v){
- this.decoSet(type,position,v);
- if(v && v != -1)
- this.decoAdd(type,position,v);
- }
- //#endregion
- // ----- Bodyparts -----
- bodyPartCovered(bodypartId){
- let bodypartData = setup.getBodypart(bodypartId);
- if(!bodypartData){
- console.error('Data not found by $pc.bodyPartCovered for bodypart',bodypartId);
- return false;
- }
- if(typeof bodypartData.covered === "undefined")
- return false;
- if(typeof bodypartData.covered === "boolean")
- return bodypartData.covered;
- if(typeof bodypartData.covered === "string")
- return Scripting.evalTwineScript(bodypartData.covered);
-
- console.error('Unsupported type of covered of bodypart in $pc.bodyPartCovered',bodypartId,bodypartData);
- return false;
- }
- // ----- Init -----
- init_final(){
- for (const [skillId, skillData] of Object.entries(this._skills)){
- this._skills[skillId].experienceHistory = [skillData.experience];
- }
- }
- //#region Occupation
- get isSchoolStudent(){
- return State.variables.q.questIsActive("school");
- }
- //#endregion
- //#region Vehicles
- _vehicleInUse = null;
- get vehicleInUse(){return this._vehicleInUse;}
- set vehicleInUse(v){this._vehicleInUse = v;}
- get vehicleData(){
- let inventory = State.variables.inventory;
- let vehicleData = inventory.metadata(this.vehicleInUse,'vehicles');
- return vehicleData;
- }
- vehicleCanEnterPassage(passage){
- var vehicleType = this.vehicleType;
- if(vehicleType != 'car')
- return true;
- var passageTags = tags(passage);
- return passageTags.includes('car');
- }
- get vehicleSpeed(){
- return this.vehicleData.speed ?? 1;
- }
- get vehicleType(){
- if(!this.vehicleInUse)
- return 'walk';
- let inventory = State.variables.inventory;
- let vehicleData = inventory.metadata(this.vehicleInUse,'vehicles');
- return vehicleData.type;
- }
- //#endregion
- //#region Menstruation Cycle
- _cycleStart = -7;
- _cycleLength = 28;
- _cycleLengthLast = 28;
- get cycleDay(){
- const time = State.variables.time;
- const daystart = time.daystart;
- return daystart - this.cycleStart;
- }
- get cycleLength(){return this._cycleLength;} // the length of the current cycle
- set cycleLength(v){this._cycleLength = v;}
- get cycleLengthLast(){return this._cycleLengthLast;}
- set cycleLengthLast(v){this._cycleLengthLast = v;}
- get cycleStart(){return this._cycleStart;} // the daystart of the current cycle
- set cycleStart(v){this._cycleStart = v;}
- cycleStartMessageSent = true;
- cycleStartHour = 17;
- get menstruationLength(){return 3;}
- get ovulationDay(){return this.cycleStart + this.cycleLength - 14;}
- ovulationHour = 19;
- get cyclePhase(){
- if(this.effectIsActive('cycleBlocked'))
- return 'blocked';
- const today = State.variables.time.daystart;
- if(today - this.cycleStart - 1 > this.cycleLength || (today - this.cycleStart > this.cycleLength && State.variables.time.hour >= this.cycleStartHour))
- this.cycleStartNew();
- if(today == this.cycleStart){
- //if(State.variables.time.hour < this.cycleStartHour)
- // return 'end';
- return 'menses';
- }else if(this.cycleStart + this.menstruationLength >= today){
- return 'menses';
- }else if(today < this.ovulationDay){
- return 'start';
- }else if(today == this.ovulationDay && State.variables.time.hour < this.ovulationHour){
- return 'start';
- }else{
- return 'end';
- }
- }
- cycleStartNew(){
- const today = State.variables.time.daystart;
- this.cycleStart = today;
- this.cycleStartMessageSent = false;
- this.cycleLengthLast = this.cycleLength;
- if(this.cycleLength >= 35)
- this.cycleLength += rand(-3,0);
- else if(this.cycleLength >= 33)
- this.cycleLength += rand(-2,1);
- else if(this.cycleLength >= 26)
- this.cycleLength += rand(-1,1);
- else if(this.cycleLength >= 24)
- this.cycleLength += rand(-1,2);
- else
- this.cycleLength += rand(0,3);
- this.cycleStartHour = rand(0,23);
- this.ovulationHour = rand(0,23);
- }
- get cycleArousalModificator(){
- switch(this.cyclePhase){
- case 'end':
- return {factor: 0.5, target: 0};
- case 'menses':
- return {factor: 1, target: 0};
- case 'start':
- default:
- const ovulationDay = this.ovulationDay;
- const daystart = State.variables.time.daystart;
- const daysTilOvolationDay = ovulationDay - daystart;
- if(daysTilOvolationDay <= 1)
- return {factor: 2, target: 30};
- if(daysTilOvolationDay <= 2)
- return {factor: 1.5, target: 10};
- return {factor: 1, target: 0};
- }
- }
- get cycleProductsEnabled(){
- const today = State.variables.time.daystart;
- if(this.cyclePhase == 'menses')
- return true;
- if(today - this.cycleStart >= this.cycleLength)
- return true;
- return false;
- }
- _cycleProduct = null;
- get cycleProduct() {
- return this._cycleProduct;
- }
- set cycleProduct(value) {
- this._cycleProduct = value;
- }
- cycleProductSince = null;
-
- get cycleProductsUsed(){
- if(State.variables.time.secondsSinceDate(this.cycleProductSince) > 43200)
- return false;
- return !(!(this._cycleProduct));
- }
- //#endregion
- //#region Indecencies
- get indecencies(){
- let result = [];
- if(this.cyclePhase == 'menses' && !this.cycleProductsUsed)
- result.push('menses_blood');
- if(State.variables.wardrobe.isNaked){
- if(!State.variables.wardrobe.bra.isValidItem)
- result.push('naked_breast');
- if(!State.variables.wardrobe.panties.isValidItem)
- result.push('naked_pussy');
-
- }
- if(this.cumVisibleBodypartIds.length > 0)
- result.push('cum');
- return result;
- }
- get isIndecent(){
- return (!(this.indecencies.length == 0));
- }
- //#endregion
- //#region SYSTEM
- constructor(){}
- _init(playerCharacter: { [x: string]: any; }){
- Object.keys(playerCharacter).forEach(function (pn) {
- this[pn] = clone(playerCharacter[pn]);
- }, this);
- return this;
- }
- clone = function () {
- return (new setup.PlayerCharacter())._init(this);
- };
- toJSON = function () {
- var ownData = {};
- Object.keys(this).forEach(function (pn) {
- if(typeof this[pn] !== "function")
- ownData[pn] = clone(this[pn]);
- }, this);
- return JSON.reviveWrapper('(new setup.PlayerCharacter())._init($ReviveData$)', ownData);
- };
- //#endregion
- }
- setup.PlayerCharacter = PlayerCharacter;
|