1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686 |
- /// <reference path="../wardrobe/wardrobe.ts" />
- 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 implements Character{
- 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 Description
- get desc():BodyDescription{
- return new setup.BodyDescription(this);
- }
- //#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();
- }
- get outfit(){
- return State.variables.wardrobe.wornItems;
- }
- /**
- * Is the character currently wearing shoes? They could be taken manually or autoatically, because the character is inside.
- * @type {boolean}
- */
- get isWearingShoes():boolean{
- return State.variables.wardrobe.isWearingShoes;
- }
- //#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
-
- /**
- * Dominance
- * @deprecated
- * @type {number}
- */
- 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
-
- /**
- * Submissiveness
- * @deprecated
- * @type {number}
- */
- 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
- _personalityValues:{
- [key:string]:{
- /**
- * Index 0: DaystartS, Index 1: Current at that day
- * @type {number[][]}
- */
- history:number[][]
- }
- } = {};
- personalityScale(scaleId:string):PersonalityScale{
- if(scaleId in this._personalityValues)
- return PersonalityScale.get(scaleId,this);
- return this.personalityScaleInitialize(scaleId);
- }
- get personalityScales():{[key:string]:PersonalityScale}{
- let result:{[key:string]:PersonalityScale} = {};
- for(const scaleId of Object.keys(this._personalityValues))
- result[scaleId] = this.personalityScale(scaleId);
- return result;
- }
- personalityScaleInitialize(scaleId:string,v:number=undefined){
- let start:number;
- if(v === undefined){
- const scaleData = PersonalityScale.get(scaleId,this);
- start = scaleData.start;
- }else
- start = v;
- const daystart = State.variables.time.daystart;
- this._personalityValues[scaleId] = {history:[[daystart,start],[daystart-1,start]]};
- return PersonalityScale.get(scaleId,this);
- }
- //#endregion
- //#region Mental Capacity
- get consciousness(){
- return this.#activeEffectValueByKey('consciousness','*');
- }
- _mentalHealth = 1000;
- get mentalHealth(){return this._mentalHealth;}
- set mentalHealth(v){
- if(v <= 0)
- this.gameover = 'mentalHealth';
- this._mentalHealth = Math.clamp(v,0,1000);
- }
- get mentalHealthRegenerationRate(){
- // Regenerate 10 willpower in 1 day
- const regenRate =10 / (24 * 60);
- return regenRate;
- }
- #mentalHealthRegenerate(minutes:number){
- this.mentalHealth += minutes * this.mentalHealthRegenerationRate;
- }
- _pcs_willpwr = 70;
-
- get willpower(){return this._pcs_willpwr;}
- set willpower(v){
- if(v < 0){
- this._pcs_willpwr = 0;
- this.mentalHealth += v;
- }else{
- this._pcs_willpwr = Math.min(v,this.willpowermax);
- }
- }
- get willpowermax(){return 100}
-
- /**
- * Willpower regeneration per minute
- * @readonly
- * @type {void}
- */
- get willpowerRegenerationRate(){
- // Regenerate 100 willpower in 2 days
- const regenRate =100 / (2 * 24 * 60);
- return regenRate;
- }
- #willpowerRegenerate(minutes:number){
- this.willpower += minutes * this.willpowerRegenerationRate;
- }
- /*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
-
- /**
- * @deprecated
- * @type {number}
- */
- get pcs_inhib(){
- return this.inhibition;
- }
- set pcs_inhib(v){
- this.inhibition = v;
- }
- get inhibition(){
- return this.personalityScale('exhibitionism').current;
- }
- set inhibition(v){
- this.personalityScale('exhibitionism').current = 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():{[key: string]:Effect}{return this._drugs.activeEffects}
- get drugsActiveEffectIds(){return this._drugs.activeEffectIds}
- drugsDeteriorate(minutes){return this._drugs.deteriorate(minutes)}
- drugVolInc(drugId:string, inc:number){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}> = [];
- a*/
- _appearanceHistory:AppearanceChange[] = [];
- get appearanceHistory():AppearanceChange[]{
- return this._appearanceHistory;
- }
- appearanceHistoryFiltered(time:Date):{[key:string]:AppearanceChange}{
- const appearanceHisotryRaw = this.appearanceHistory.
- filter(
- (entry)=>entry.time.getTime() > time.getTime()
- ).
- sort(
- (a,b)=>a.time.getTime()-b.time.getTime()
- );
-
- let result:{[key:string]:AppearanceChange} = {};
- for(const historyEntry of appearanceHisotryRaw){
- if(result[historyEntry.key]){
- result[historyEntry.key].to = historyEntry.to;
- }else{
- result[historyEntry.key] = historyEntry;
- }
- }
- return result;
- }
- appearanceHistoryPush(key:string,from:any,to:any){
- this._appearanceHistory.push({
- time: State.variables.time.now,
- key: key,
- from: from,
- to: to,
- });
- }
- //#endregion
- //#region Face
- //#region Eyes
-
- eyecolor:EEyeColor = EEyeColor.BLUE;
- eyesize:EEyeSize = EEyeSize.MEDIUM;
- //#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;}
- get hairLengthCategory():EHairLength{
- const categories = [
- 30,80,160,260,400,600,800
- ];
- for(let index = 0; index < categories.length; index++){
- let maxLengthOfCategory = categories[index];
- if(this.hairLength <= maxLengthOfCategory)
- return index;
- }
- return categories.length;
- }
- hairColor:EHairColor = 0;
- hairColorNatural:EHairColor = 0;
- hairDyeFade = 0;
- hairDye(newColor:EHairColor,fadeDuration=undefined){
- this.appearanceHistoryPush('hairColor',this.hairColor,newColor);
- this.hairColor = newColor;
- this.hairDyeFade = fadeDuration ?? 30;
- }
- //#endregion
- //#region Legs
- _leghair = 0;
- /**
- * The length of the hair on the legs in mm.
- * @type {number}
- */
- get legHair(){return this._leghair;}
- set legHair(v){this._leghair = v;}
- _legHairState:ELegHairState = ELegHairState.DEFAULT;
- _legHairWaxExpiration:Date;
- get legHairState():ELegHairState{
- if(this._legHairState == ELegHairState.WAXED && !this._legHairWaxExpiration.isFuture)
- this._legHairState = ELegHairState.DEFAULT;
- return this._legHairState;
- }
- set legHairState(v:ELegHairState){
- if(v != ELegHairState.DEFAULT)
- this.legHair = 0;
- if(v == ELegHairState.WAXED)
- this._legHairWaxExpiration = State.variables.time.dayWithOffsetS(28);
- this._legHairState = v;
- }
- get legHairGrowth(){
- switch(this.legHairState){
- case ELegHairState.DEFAULT: return (this.age < 18 ? 0.14 : 0.21);
- case ELegHairState.WAXED: return 0;
- case ELegHairState.LASERED: return 0;
- }
- }
- get legHairVisibility():ELegHairVisibility{
- if(this.legHair <= 0.25)
- return ELegHairVisibility.SMOOTH;
- else if(this.legHair <= 0.5) // ~3 Days
- return ELegHairVisibility.INVISIBLE;
- else if(this.legHair <= 1.5) // ~1 Week
- return ELegHairVisibility.NOTICABLE;
- else if(this.legHair <= 6) // ~1 Month
- return ELegHairVisibility.LONG;
- return ELegHairVisibility.MANLY;
- }
- set legHairVisibility(v){
- switch (v) {
- case ELegHairVisibility.SMOOTH: this._leghair = 0; return;
- case ELegHairVisibility.INVISIBLE: this._leghair = 0.2; return;
- case ELegHairVisibility.NOTICABLE: this._leghair = 1; return;
- case ELegHairVisibility.LONG: 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 activeEffectSkillGain():{[skillId:string]:number}{
- return this.#activeEffectValueDictByKey('skillGain','*');
- }
- get activeEffectSkillTagGain():{[skillId:string]:number}{
- return this.#activeEffectValueDictByKey('skillTagGain','*');
- }
- skillGainModifier(skillIdOrSkill:string|Skill):number{
- let modifier:number = 1;
- let skill:Skill;
- if(typeof skillIdOrSkill == "string")
- skill = Skill.get(skillIdOrSkill);
- else
- skill = skillIdOrSkill;
- const activeEffectSkillGain = this.activeEffectSkillGain;
- if(typeof activeEffectSkillGain[skill.id] == "number"){
- modifier *= activeEffectSkillGain[skill.id];
- }
- return modifier;
- }
- skillTagGainModifier(tag:string):number{
- const activeEffectSkillGain = this.activeEffectSkillTagGain;
- if(typeof activeEffectSkillGain[tag] != "number")
- return 1;
- return activeEffectSkillGain[tag];
- }
-
- /**
- * How much experiences to gain depending on the experience already received today.
- * @param {string} skillId
- * @param {number} experience
- * @returns {number}
- */
- #skillGain(skillId:string,experience:number):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,tags:string[]=[]):number{
- if(inc == 0)
- return;
- this.#skillInit(skillId);
- const gainModifier = this.skillGainModifier(skillId);
- inc *= gainModifier;
- for(const tag of tags){
- const tagGainModifier = this.skillTagGainModifier(tag);
- inc *= tagGainModifier;
- }
- 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:string){
- return this.#activeEffectValueDictByKey('skills','+')[skillId] ?? 0;
- }
- 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
- _sexStatistics:SexStatistics = {};
- sexStatistic(activity:ESexEncounterType|ESexEncounterType[],familiarity:ESexEncounterFamiliarity|ESexEncounterFamiliarity[],awareness:('aware'|'unaware'|'both')):number{
- let result = 0;
- if(!activity)
- activity = Object.keys(this._sexStatistics) as ESexEncounterType[];
- else if(!Array.isArray(activity))
- activity = [activity];
- if(!familiarity)
- familiarity = [ESexEncounterFamiliarity.ACQUAINTANCE,ESexEncounterFamiliarity.FRIEND,ESexEncounterFamiliarity.LOVE,ESexEncounterFamiliarity.STRANGER];
- else if(!Array.isArray(familiarity))
- familiarity = [familiarity];
- for(const activityKey of activity){
- const activityData:SexStatistic = this._sexStatistics[activityKey] ?? {};
- for(const familiarityKey of familiarity){
- const familiarityData = activityData[familiarityKey] ?? [0,0];
- switch(awareness){
- case "aware": result += familiarityData[0]; continue;
- case "unaware": result += familiarityData[1]; continue;
- case "both": result += familiarityData[0] + familiarityData[1]; continue;
- }
- }
- }
- return result;
- }
-
- /**
- * As far as the character is aware of. The correct father will be stored in the pregnancy data.
- * @type {{npcId:string,time:Date}[]}
- */
- _sexPossibleFathers:{npcId:string,time:Date}[] = [];
- _sexEncounters:SexEncounter[] = [];
- sexEncounterRegister(npc:NPC|string,types:ESexEncounterType|ESexEncounterType[],familiarity:ESexEncounterFamiliarity,aware:boolean,time:Date){
- if(typeof npc == 'string')
- npc = State.variables.npcs.npc(npc);
- let npcId:string;
- if(npc){
- npcId = npc.id;
- }else
- npcId = '';
- if(!Array.isArray(types))
- types = [types];
- const awareIndex = aware ? 0 : 1;
- for(const type of types){
- this._sexStatistics[type] ??= {};
- this._sexStatistics[type][familiarity] ??= [0,0];
- this._sexStatistics[type][familiarity][awareIndex] += 1;
- if(npc){
- npc.lastSex ??= {};
- npc.lastSex[type] = time;
- }
- }
- this._sexEncounters.push({
- npcId: npcId,
- types: types,
- time: time,
- aware: aware,
- tags: []
- });
- }
- get isVirgin(){
- return (this.sexStatistic(ESexEncounterType.VAGINAL,null,'both') == 0);
- }
- get thinksIsVirgin(){
- return (this.sexStatistic(ESexEncounterType.VAGINAL,null,'aware') == 0);
- }
- //#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,this.activeEffectPainRegen)}
-
- get activeEffectPainGain():{[bodypartId:string]:number}{
- return this.#activeEffectValueDictByKey('painGain','*');
- }
- get activeEffectPainRegen():{[bodypartId:string]:number}{
- return this.#activeEffectValueDictByKey('painRegen','*');
- }
- painGainModifier(bodypartId:string,reason=''):number{
- const activeEffectPainGain = this.activeEffectPainGain;
- if(typeof activeEffectPainGain[bodypartId] != "number")
- return 1;
- return activeEffectPainGain[bodypartId];
- }
-
- painInc(bodypartId:string,reason='',increase:number){
- increase *= this.painGainModifier(bodypartId);
- return this._pain.painInc(bodypartId,reason,increase);
- }
- 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());
- }
- #activeEffects(fields:string[]=[]):{[key: string]:Effect}{
- let result:{[key: string]:Effect} = {};
- const time = State.variables.time;
-
- for(const [effectid,effect] of Object.entries(this.drugsActiveEffects)){
- if(fields.length && !Object.keys(effect).includesAny(fields))
- continue;
- result[effectid] = effect;
- }
- for(const [effectid,effectData] of Object.entries(this._effects)){
- if(effectData.expiration === undefined)
- continue;
- if(
- (!fields.length || Object.keys(effectData).includesAny(fields)) &&
- (effectData.expiration === null || time.isFuture(effectData.expiration))
- )
- result[effectid] = Object.assign({},setup.getEffect(effectid),effectData);
- }
- for(const [effectid,effectMetaData] of Object.entries(setup.activeEffects)){
- let effect:Effect;
- if(typeof effectMetaData.effect == "string")
- effect = setup.getEffect(effectMetaData.effect);
- else
- effect = clone(effectMetaData.effect);
- //We need to check the field first to avoid infinite recursions
- if(fields.length && !Object.keys(effect).includesAny(fields))
- continue;
-
- if(typeof effectMetaData.isActive == "boolean"){
- if(!effectMetaData.isActive)
- continue;
- }else{
- if(!effectMetaData.isActive(this))
- continue;
- }
-
- result[effectid] = effect;
- }
- 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);
- }
- }
- #activeEffectValueDictByKey(key:string,reduceMode:('+'|'*')='*',...args:any):{[key:string]:number}{
- const result:{[skillId:string]:number} = {};
- const valueArray:{[skillId:string]:number}[] = this.#activeEffectValuesByKey(key);
- for(const skillModifiers of valueArray){
- for(const [skillId, skillModifier] of Object.entries(skillModifiers)){
- switch(reduceMode){
- case '*':
- result[skillId] ??= 1;
- result[skillId] *= skillModifier;
- continue;
- case '+':
- result[skillId] ??= 0;
- result[skillId] += skillModifier;
- continue;
- }
- }
- }
- return result;
- }
-
-
- /**
- * @param {string} key
- * @returns {{}}
- */
- #activeEffectValuesByKey(key:string){
- var result =[];
- const activeEffects = this.#activeEffects([key]);
- for(const effect of Object.values(activeEffects)){
- 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:string, 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) ;
- }
- effectDelete(effectId:string){
- delete this._effects[effectId];
- }
-
- /**
- * @param {string} effectId
- * @returns {boolean}
- */
- effectIsActive(effectId:string):boolean{
- 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;
- }else{
- this.pcs_sleep -= timed_stat_changes['sleep']['default'] * timeMod ;
- this.pcs_stam += this.stammax / 20;
- }
- // ----- 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.horny -= timeToAdd * this.hornyDeteriorationRate;
- this.drugsDeteriorate(timeToAdd);
- this.painDeteriorate(timeToAdd);
- this.#mentalHealthRegenerate(timeToAdd);
- this.#willpowerRegenerate(timeToAdd);
- }
- //#endregion
- //#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);*/
- if(typeof bodypartData.covered === "function")
- return bodypartData.covered(this);
-
- 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;
|