Sfoglia il codice sorgente

changed several files to `TS`

Stephan Fuchs 4 mesi fa
parent
commit
98a2cc1b7f

+ 14 - 0
sugarcube/src/_shared/Initializing.ts

@@ -0,0 +1,14 @@
+class Initializing{
+
+	constructor(initialData:object={}){
+		this.init(initialData);
+	}
+
+    protected init(data:object){
+		Object.keys(data).forEach(function (pn) {
+			this[pn] = clone(data[pn]);
+		}, this);
+
+		return this;
+	}
+}

+ 22 - 3
sugarcube/src/interfaces.d.ts

@@ -1,7 +1,12 @@
 declare module "twine-sugarcube" {
 	export interface SugarCubeSetupObject {
+		Drugs: any;
 		Events: { new(): Events };
+		GameTime: { new(): GameTime };
 		Inventory: { new(): Inventory };
+		Mood: { new(): Mood };
+		Pain: { new(): Pain };
+		PlayerCharacter: { new(): PlayerCharacter };
 		Quest: {
 			handler: object; new(questId?: any): Quest 
 		};
@@ -9,16 +14,26 @@ declare module "twine-sugarcube" {
 		VersionControl: { new(): VersionControl };
 
 		items: object;
+		moodlets: { [key: string]: Moodlet; };
+		moodletGroups: { [key: string]: MoodletGroup; };
 
-		func: (passage: string, ...arguments: Array<string|number>)=>(number|string);
+		durationToString: any;
+		func: (passage: string, ...arguments: Array<string|number>)=>any;
+		getBodyparts: any;
+		getBodypart:any;
+		weekDayIntToString: any;
 	}
 	
 	export interface SugarCubeStoryVariables {
 		events: Events;
+		finances: any;
 		inventory: Inventory;
-		pc: object;
+		jobs: any;
+		location: any;
+		pc: PlayerCharacter;
 		q: QuestsDict;
-		time: any;
+		time: GameTime;
+		wardrobe:any;
 		
 		ARGS: Array<string>;
 		here: string;
@@ -38,6 +53,10 @@ declare global {
 	export interface Array<T>{
 		filterByObject(filter: object): Array<T>;
 	}
+
+	export interface Window{
+		rand(x:number,y:number): number;
+	}
 }
 
 export { };

+ 31 - 38
sugarcube/src/js/Time.js → sugarcube/src/js/Time.ts

@@ -56,12 +56,12 @@ class GameTime{
 	get minutes(){
 		return this._now.getUTCMinutes();
 	}
-	set minutes(v){
+	set minutes(v:number){
 		if(typeof v !== "number"){
 			console.error("Time Increase by invalid value",v);
 			return;
 		}
-		v = parseInt(v);
+		v = Math.ceil(v);
 
 		let minutes = this._now.getUTCMinutes();
 		let timeToAdd = v - minutes;
@@ -114,7 +114,7 @@ class GameTime{
 		const now = this.now;
 		let targetTime = new Date(now);
 		targetTime.setUTCHours(v,0);
-		let millisecondDifference = targetTime - now;
+		let millisecondDifference = targetTime.getTime() - now.getTime();
 		if(millisecondDifference < 0)
 			millisecondDifference += 86400000;
 		const minuteDifference = Math.floor(millisecondDifference / 60000);
@@ -126,7 +126,7 @@ class GameTime{
 	}
 
 	_startDate = null;
-	get startDate(){return this._startDate;}
+	get startDate():Date{return this._startDate;}
 
 	get year(){return this._now.getUTCFullYear();}
 	set year(v){this._now.setUTCFullYear(v);}
@@ -172,7 +172,7 @@ class GameTime{
 	//Days since the game started. 1 is the first day.
 	get daystart(){
 		const oneDay = 86400000;
-		return Math.floor(Math.abs((this.today - this.startDate) / oneDay)) + 1;
+		return Math.floor(Math.abs((this.today.getTime() - this.startDate.getTime()) / oneDay)) + 1;
 	}
 
 	daystartNextAtNthOfMonth(n){
@@ -181,7 +181,7 @@ class GameTime{
 		if(n <= now.getUTCDate())
 			nextDate.setUTCMonth(now.getUTCMonth() + 1);
 		nextDate.setUTCDate(n);
-		const timeDifference = nextDate - now;
+		const timeDifference = nextDate.getTime() - now.getTime();
 		const daysDifference = Math.floor(timeDifference / 86400000);
 
 		return daysDifference + this.daystart;
@@ -227,8 +227,8 @@ class GameTime{
 				typeof day == "number" &&
 				typeof hour == "number" &&
 				typeof minute == "number"){
-			this._startDate = setup.DateUTC(yearOrDate,month,day);
-			this._now = setup.DateUTC(yearOrDate,month,day,hour,minute);
+			this._startDate = new Date(Date.UTC(yearOrDate,month-1,day));
+			this._now = new Date(Date.UTC(yearOrDate,month-1,day,hour,minute));
 		}else{
 			console.error('Arguments for time.initDate not supported:',yearOrDate,month,day,hour,minute);
 		}
@@ -345,7 +345,7 @@ class GameTime{
 	 * @readonly
 	 * @type {Date}
 	 */
-	get now(){
+	get now():Date{
 		return clone(this._now);
 	}
 
@@ -393,7 +393,7 @@ class GameTime{
 		}
 	}
 
-	get today(){
+	get today():Date{
 		let now = clone(this.now);
 		now.setUTCHours(0,0,0,0);
 		return now;
@@ -443,7 +443,7 @@ class GameTime{
 		console.log("Time: advanceTo",hour,minute,advanceToNextDay,minuteDifference,this.now);
 	}
 
-	ageOfDate(dayOrDate,month,year){
+	ageOfDate(dayOrDate:number|Date,month=undefined,year=undefined){
 		let day = dayOrDate;
 
 		if(dayOrDate instanceof Date){
@@ -481,8 +481,8 @@ class GameTime{
 			year = this.year;
 			return_positive = true;
 		}
-		let testDate = setup.DateUTC(year, month, day);
-		let diffDays = Math.round((testDate - currentDate) / oneDay);
+		let testDate = new Date(Date.UTC(year, month, day));
+		let diffDays = Math.round((testDate.getTime() - currentDate.getTime()) / oneDay);
 		if(diffDays < 0 && return_positive)
 			return this.daysDifference(day,month,this.year+1);
 		return diffDays;
@@ -546,7 +546,7 @@ class GameTime{
 
 	//executed whenever the time changes after everything else has been calculated
 	execute_every_timeUpdate_post(){
-		let missedEvents = this.getPastTimedEvents(undefined,true);
+		/*let missedEvents = this.getPastTimedEvents(undefined,true);
 		for(let missedEventIndex in missedEvents){
 			let missedEvent = missedEvents[missedEventIndex];
 			switch(missedEvent.key){
@@ -574,7 +574,7 @@ class GameTime{
 					}
 			}
 			missedEvents[missedEventIndex].handled = true;
-		}
+		}*/
 	}
 
 
@@ -637,7 +637,7 @@ class GameTime{
 
 	// ----- timed events -----
 	// Timed events have a start date and time, duration and meta-informationen, such as a scheduled date
-	_timedEvents = {_system:{nextId:0},events:{}}
+	_timedEvents:any = {_system:{nextId:0},events:{}}
 
 	addTimedEvent(key,day,month,year,hour,minute,duration,data={}){
 		let startTime = new Date(Date.UTC(year, month - 1, day, hour, minute, 0));
@@ -666,8 +666,8 @@ class GameTime{
 		let result = [];
 		let now = this.now;
 		for(let timedEvent of Object.values(this._timedEvents.events)){
-			if(key === undefined || timedEvent.key == key){
-				if(now.getTime() >= timedEvent.startTime.getTime() && now.getTime() <= timedEvent.endTime.getTime())
+			if(key === undefined || timedEvent['key'] == key){
+				if(now.getTime() >= timedEvent['startTime'].getTime() && now.getTime() <= timedEvent['endTime'].getTime())
 					result.push(timedEvent);
 			}
 		}
@@ -678,10 +678,10 @@ class GameTime{
 		let result = [];
 		let now = this.now;
 		for(let timedEvent of Object.values(this._timedEvents.events)){
-			if(ignoreHandled && timedEvent.handled)
+			if(ignoreHandled && timedEvent['handled'])
 				continue;
-			if(key === undefined || timedEvent.key == key){
-				if(now.getTime() > timedEvent.endTime.getTime())
+			if(key === undefined || timedEvent['key'] == key){
+				if(now.getTime() > timedEvent['endTime'].getTime())
 					result.push(timedEvent);
 			}
 		}
@@ -699,19 +699,19 @@ class GameTime{
 
 	rescheduleTimedEventByKey(key,offsetMode='day',offsetCount=1){
 		for(let timedEvent of Object.values(this._timedEvents.events)){
-			if(timedEvent.key == key){
+			if(timedEvent['key'] == key){
 				switch(offsetMode){
 					case 'day':
-						timedEvent.startTime.setUTCDate(timedEvent.startTime.getUTCDate() + offsetCount);
-						timedEvent.endTime.setUTCDate(timedEvent.endTime.getUTCDate() + offsetCount);
+						timedEvent['startTime'].setUTCDate(timedEvent['startTime'].getUTCDate() + offsetCount);
+						timedEvent['endTime'].setUTCDate(timedEvent['endTime'].getUTCDate() + offsetCount);
 						break;
 					case 'month':
-						timedEvent.startTime.setUTCMonth(timedEvent.startTime.getUTCMonth() + offsetCount);
-						timedEvent.endTime.setUTCMonth(timedEvent.endTime.getUTCMonth() + offsetCount);
+						timedEvent['startTime'].setUTCMonth(timedEvent['startTime'].getUTCMonth() + offsetCount);
+						timedEvent['endTime'].setUTCMonth(timedEvent['endTime'].getUTCMonth() + offsetCount);
 						break;
 					case 'year':
-						timedEvent.startTime.setUTCFullYear(timedEvent.startTime.getUTCFullYear() + offsetCount);
-						timedEvent.endTime.setUTCFullYear(timedEvent.endTime.getUTCFullYear() + offsetCount);
+						timedEvent['startTime'].setUTCFullYear(timedEvent['startTime'].getUTCFullYear() + offsetCount);
+						timedEvent['endTime'].setUTCFullYear(timedEvent['endTime'].getUTCFullYear() + offsetCount);
 						break;
 				}
 			}
@@ -721,10 +721,10 @@ class GameTime{
 
 	unscheduleTimedEventsByKeyAndData(key,data){
 		this._timedEvents = Object.values(this._timedEvents.events).filter((e)=>{
-			if(e.key != key)
+			if(e['key'] != key)
 				return true;
 			for (const [data_key, data_value] of Object.entries(data)) {
-				if(data_value != e.data[data_key])
+				if(data_value != e['data'][data_key])
 					return true;
 			}
 			return false;
@@ -907,18 +907,11 @@ class GameTime{
 
 setup.GameTime = GameTime;
 
-setup.DateUTC = function(year, month, day, hour = undefined, minutes=undefined, seconds=undefined){
-	hour = hour || 0;
-	minutes = minutes || 0;
-	seconds = seconds || 0;
-	return new Date(Date.UTC(year, month - 1, day, hour, minutes, seconds));
-}
-
 setup.durationToString = function(duration){
 	return Math.floor(duration/60).toString()+':'+(duration%60).toString().padStart(2, '0');
 }
 
-setup.weekDayIntToString ??= function(day){
+setup.weekDayIntToString ??= function(day:number){
 	switch(day){
 		case 1: return 'Monday';
 		case 2: return 'Tuesday';

+ 65 - 457
sugarcube/src/playerCharacter/PlayerCharacter.js → sugarcube/src/playerCharacter/PlayerCharacter.ts

@@ -1,4 +1,6 @@
 
+declare let rand: Window["rand"];
+
 const bmi_attractive_range = [18.5,30];
 const timed_stat_changes ={ //Hourly
 	energy: {
@@ -68,12 +70,12 @@ class PlayerCharacter{
 		this.birthyear -= difference;
 		console.log("AGE set to "+v);
 	}
-	get ageDays(){
+	/*get ageDays(){
 		const oneDay = 86400000;
 		let now =  State.variables.time.now;
 		let bday = this.birthdayDate;
 		return Math.floor((now - bday)/oneDay);
-	}
+	}*/
 
 	_visualAgeDaysOffset = 0;
 	get visualAgeDays(){return this._visualAgeDaysOffset}
@@ -107,32 +109,27 @@ class PlayerCharacter{
 	_mood = new setup.Mood();
 
 	get mood(){return this._mood.mood;}
-	set mood(v){return (this._mood.mood = v)}
-	get moodlets(){return this._mood.moodlets}
+	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)}
+	//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)}
+	//moodletIsActive(moodletId){return this._mood.moodletIsActive(moodletId)}
 	moodletUpdate(moodletId,updateObject){return this._mood.moodletUpdate(moodletId,updateObject)}
 	#moodletsClean(){return this._mood._moodletsClean();}
 	
-	get moodletsSpecial(){return Object.assign({},this.moodletPain,this.activeEffectsMoodlets);}
-
-	get moodletPain(){
-		let painTotal = this.painTotal;
-		if(painTotal > 0){
-			const time = State.variables.time;
-			let painMoodlet = this.moodletCombinedData('pain');
-			painMoodlet.effect = Math.round(painTotal * -1);
-			painMoodlet.expiration = time.endTime;
-			return {'pain':painMoodlet};
-		}
-		return {};
+	get moodletsSpecial():{[key: string]: ActiveMoodlet}{
+		//return Object.assign({},this.moodletPain,this.activeEffectsMoodlets);
+		return {pain:this.moodletPain};
+	}
+
+	get moodletPain():ActiveMoodlet{
+		return PainMoodlet.createPainMoodlet(this.painTotal);
 	}
 
 	
@@ -170,8 +167,8 @@ class PlayerCharacter{
 	set pcs_energy(v){
 		this._pcs_energy = Math.clamp(v,0,100);
 		
-		if(v > 0 && this._death.hunger?.stage){
-			this._death.hunger = {stage:0};
+		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);
 		}
@@ -213,9 +210,9 @@ class PlayerCharacter{
 	_energyHistoryLengthTarget = 7;
 	get energyHistoryLengthTarget(){return this._energyHistoryLengthTarget;}
 	set energyHistoryLengthTarget(v){
-		_energyHistoryLengthTarget = v;
-		while(_energyHistory.length > v)
-			_energyHistory.shift();
+		this._energyHistoryLengthTarget = v;
+		while(this._energyHistory.length > v)
+			this._energyHistory.shift();
 	}
 
 	dailyEnergyUpdate(){
@@ -278,8 +275,8 @@ class PlayerCharacter{
 		*/
 		this._pcs_hydra = Math.clamp(v,0,100);
 
-		if(v > 0 && this._death.thirst?.stage){
-			this._death.thirst = {stage:0};
+		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);
 		}
@@ -407,7 +404,7 @@ class PlayerCharacter{
 	drugVol(drugId){return this._drugs.vol(drugId)}
 
 	get alko(){return this.drugVol('alcohol')}
-	set alko(v){return this.drugVolSet('alcohol',v)}
+	set alko(v){this.drugVolSet('alcohol',v)}
 
 	
 
@@ -474,7 +471,7 @@ class PlayerCharacter{
 
 	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
-	get wratio(){			   // waist to hips ratio set in body_shape
+	/*get wratio(){			   // waist to hips ratio set in body_shape
 		let wrtemp = ((2 * this.healthiness + this.muscularity + this.dexterity) / 4);
 		let result = 85;
 		if(wrtemp < 11)
@@ -523,7 +520,7 @@ class PlayerCharacter{
 		else
 			return 56;
 
-	}
+	}*/
 
 	_fat = 0;				   //the fat the character consumed recently
 	get fat(){
@@ -553,11 +550,11 @@ class PlayerCharacter{
 
 	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_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_butt(){return (this.pcs_hips / 10) + this.silicone_butt + this.butt_cheat;}*/
 	//get pcs_cupsize(){return (this.pcs_bust - this.pcs_band);}
 
 	_cupsize = 15;
@@ -703,14 +700,14 @@ class PlayerCharacter{
 	}
 
 	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.isPregnancyAware == 1) return 3;
 		if (this.muscularity >= 70) return 2;
-		if (this.muscularity <= 40) return 0;
+		if (this.muscularity <= 40) return 0;*/
 		return 1;
 	}
 
 	get body(){
-		let bodimgsets = State.variables.bodimgsets;
+		/*let bodimgsets = State.variables.bodimgsets;
 		if(this.isPregnancyAware){
 			if(this.PregChem > 6216)
 				return bodimgsets[(this.bodset * 10) + 8];
@@ -720,7 +717,8 @@ class PlayerCharacter{
 		}
 		if(this.salocatnow <= 7)
 			return bodimgsets[((this.bodset * 10) + this.salocatnow)];
-		return bodimgsets[(this.bodset * 10) + 7];
+		return bodimgsets[(this.bodset * 10) + 7];*/
+		return null;
 	}
 
 	/*pcs_teeth = 0;  //-1: perfectly white
@@ -756,7 +754,7 @@ class PlayerCharacter{
 	}
 
 	get teethMissingCount(){
-		return this._teeethMissing.count();
+		return this._teeethMissing.length;
 	}
 
 	//pcs_breath = 0;
@@ -849,7 +847,7 @@ class PlayerCharacter{
 	bodyDailyUpdate(){
 		this.dailyEnergyUpdate();
 
-		if(this.muscularity > this.strength)
+		/*if(this.muscularity > this.strength)
 			this.muscularity -= 1;
 		else if(this.muscularity < this.strength)
 			this.muscularity += 1;
@@ -862,7 +860,7 @@ class PlayerCharacter{
 		if(this.dexterity > this.agility)
 			this.dexterity -= 1;
 		else if(this.dexterity < this.agility)
-			this.dexterity += 1;
+			this.dexterity += 1;*/
 
 
 		/*if(this.fat > (17 + this.healthiness / 25)){
@@ -902,9 +900,10 @@ class PlayerCharacter{
 		}*/
 
 		// Hair colour change
-		if(this.pcs_haircol != this.nathcol){
+		//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
@@ -991,161 +990,6 @@ class PlayerCharacter{
 	get vitality(){return this.skillLevel('vitality');} set vitality(v){this.skillSetLevel('vitality',v);}
 
 
-	bodyInit(){
-		this.bodySoftReset();
-		/*<<set $normbuffpick =  - 1>> <<set $gmstrtflag = 1>>
-		<<set $salocatlast = $salocatnow>>
-		<<set $normbuffpick = 0>> <<set $magf2bdo = 0>>
-		<<set $gmstrtflag to null>>
-		<<set $newbdsp = 1>>*/
-	}
-
-	bodySaloCalc(){
-		this.bodySaloCalcBreast();
-
-		/*<!-- !!This is if a Succubus has salo < 1-->
-		if(this.succubusflag") == 1 and this.salo < 1>>
-			<<set $sucexcess -= 1>>
-			<<set $salo += 3>>
-		<</if>>*/
-
-
-		//<!-- !!This is if salo is still < 1-->
-		if(this.salo < 1){
-			if(this.fat >= 1){
-				this.salo = 1;
-				this.fat -= 1;
-			}
-			else if(this.fat <= 0 && this.strength + this.vitality > 0){
-				this.skillExperienceGain('strength',-1000);
-				this.skillExperienceGain('vitality',-1000);
-				this.salo = 1;
-			}
-			else
-			{
-				//TODO: GameOver
-				/*
-					if(this.Enable_nogameover") == 0>>
-						<<set $over = 3>>
-						<<gt 'gameover'>>
-						$exit
-					<<else>>
-						<font color="red"><B>You starved to death</B><font>
-						<<set $salo = 1>>
-					<</if>>
-				*/
-			}
-
-		}
-
-		this.vhtmp = (this.salo - 80) / 2;
-		if(this.vhips > this.vhtmp)
-			this.vhips -= 1
-		if(this.vhips < this.vhtmp)
-			this.vhips += 1
-
-		if((this.pcs_hgt * this.hratio) / 100 + this.vhips > (this.pcs_hgt * 72) / 100){
-			this.vofat = ((this.pcs_hgt * this.hratio) / 100 + this.vhips - (this.pcs_hgt * 72) / 100) / 2;
-			this.vhips -= this.vofat * 2;
-		}
-		//<!-- !!This will trigger the warning notices in the bathing code (the +/- 12 should always be +/- 11 + the max change to salo w/ fat)-->
-		if(this.salolast > this.salo && this.salo <= 12 + (20 * (this.salocatnow - 1)))
-			State.variables.btwarn = 1;
-		else if(this.salolast < this.salo && this.salo >= (20 * (this.salocatnow + 1)) - 12)
-			State.variables.btwarn = 2;
-
-		//<!-- !!This will trigger the dream for the option to use magic to increase bust-->
-		//<!-- !!Three nos at the dream will lock it out (1 yes resets the count)-->
-		/*if(this.pcs_magik >= 5 and this.MagikDostup") == 0 and this.magf2bdo") == 0>>
-			<<if $salolast < $salo and $salo >= (20 * (salocatnow + 1)) - 11 and this.tits < 10>>
-				if(this.mgf2bnocnt < 3>>
-					<<set $magf2bdo = 2>>
-				<<else>>
-					<<set $magf2bdo = 3>>
-				<</if>>
-			<</if>>
-		<</if>>*/
-		//<!-- !!This is to deal with the possibility that salocatnow changed by more than 1 (fat burners, vitamins, plastic surgery, etc.)-->
-
-		if(this.salocatlast > this.salocatnow)
-			this.salocatlast -= 1
-		if(this.salocatlast < this.salocatnow)
-			this.salocatlast += 1
-
-
-		//<!-- !!This is for use in the warning code and as part of the reset routines-->
-		if(this.salolast > this.salo)
-			this.salolast -= 1
-		if(this.salolast < this.salo)
-			this.salolast += 1
-
-	}
-
-	bodySaloCalcBreastPre(){
-		if(this.salo < 10)
-			this.salocatnow = 0
-		else
-			this.salocatnow = 1 + (this.salo - 10) / 20
-	}
-
-	bodySaloCalcBreast(){
-		this.bodySaloCalcBreastPre();
-
-		//This controls the movement of salo to/from bust in order of precedence
-
-			if(this.nbsize < this.genbsize && this.salocatnow > 2){
-
-				//if sftrstflag = 0:'<b>Your breasts seem fuller.</b>'
-				this.nbsize += 1;
-				this.salo -= 3;
-				this.bodySaloCalcBreastPre();
-			}
-
-			if(this.magf2bdo == 1 && this.salocatnow > this.salocatlast && this.pcs_mana >= this.manamax / 2 && this.magikDostup == 0){
-				//if sftrstflag = 0:'<b>Your breasts seem fuller.</b>'
-				//this.magicf2b += 1
-				this.salo -= 3
-				/*if magicf2b >= 2 + magtarcup * 5: magf2bdo = 0
-				if pcs_magik < 20:
-					pcs_mana -= 2000 / pcs_magik
-				else
-					mana -= 100
-				end*/
-				this.bodySaloCalcBreastPre();
-			}
-
-			if(this.salocatnow < 2 && this.salocatlast >= 2 && this.magicf2b > 0 && this.magikDostup == 0){
-				//if sftrstflag = 0:'<b>Your breasts seem to be getting smaller.</b>'
-				//magicf2b -= 1
-				this.salo += 3
-				//magf2bdo = 1
-				this.bodySaloCalcBreastPre();
-			}
-
-			if(this.salocatnow < 1 && this.salocatlast >= 1 && this.nbsize > 0){
-				//if sftrstflag = 0:'<b>Your breasts seem to be getting smaller.</b>'
-				this.nbsize -= 1
-				this.salo += 3
-				this.bodySaloCalcBreastPre();
-			}
-
-	}
-
-
-
-	vhips = 50; //derived from salo in body_shape
-	vhtmp = 50; //slows the change to vhips in body_shape
-
-
-	bodySoftReset(){
-		this.fat = 0;
-		this.vhips = (this.salo - 80) / 2;
-		let i = 0;
-		while(this.salo != this.salolast && i++ < 1000){
-			this.bodySaloCalc();
-		}
-	}
-
 	// ---- Appearance ------
 	get pcs_makeup(){
 		/*if(this.cosmetic_tattoo > 0)
@@ -1180,260 +1024,13 @@ class PlayerCharacter{
 	cosmetic_tattoo = 0;
 
 	get appearance(){
-		return func('Appearance','appearance');
+		return setup.func('Appearance','appearance');
 	}
 
 	get hotcat(){
 		return this.appearance.hotcat.temp;
 	}
 
-	faceGeneticAttractiveness = 0;
-	faceSurgeries = 0;
-	get faceAttractiveness(){
-		return Math.clamp(this.faceGeneticAttractiveness+this.faceSurgeries,-3,3);
-	}
-
-	get appearance_accessoriesBonus(){
-		let wardrobe = State.variables.wardrobe;
-
-		let coatQualityBonus = wardrobe.coat_appearanceBonus;
-		let shoesQualityBonus = wardrobe.shoes_appearanceBonus;
-
-		let braBonus = wardrobe.bra_appearanceBonus;
-		let pantyBonus = wardrobe.panties_appearanceBonus;
-
-		return coatQualityBonus + shoesQualityBonus + pantyBonus + braBonus;
-	}
-
-	get appearance_clothingBonus(){
-		let wardrobe = State.variables.wardrobe;
-		let tempRevealing = 0;
-		if(wardrobe.clothingIsNude){
-			if (this.bmi_is_attractive)
-				tempRevealing = 405;
-		}else{
-			if(this.bmi < bmi_attractive_range[0])
-				tempRevealing = ((400 - wardrobe.PXCloThinness) + (500 - wardrobe.PXCloTopCut) + (400 - wardrobe.PXCloBottomShortness)) / 2;
-			else if(this.bmi_is_attractive)
-				tempRevealing = (wardrobe.PXCloThinness + wardrobe.PXCloTopCut + wardrobe.PXCloBottomShortness) / 2;
-			else
-				tempRevealing = ((400 - wardrobe.PXCloThinness) + (500 - wardrobe.PXCloTopCut) + (400 - wardrobe.PXCloBottomShortness)) * 3 / 4;
-		}
-		return tempRevealing / 76 * wardrobe.PCloQuality;
-
-	}
-
-	get appearance_groomingBonus(){
-		let makeupBonus = (this.skillLevel('makeup') / 5) - 5;
-		if(this.pcs_makeup == 0)
-			makeupBonus = -5;
-
-		if(this.pcs_makeup == 1)
-			makeupBonus = 0;
-
-		if(this.pcs_makeup == 5)
-			makeupBonus = 30;
-
-		let breathBonus = this.pcs_breath * 5;
-		let tempGroomingBonus = makeupBonus + breathBonus;
-
-		return this.appearanceAdjustFromBMI(tempGroomingBonus);
-	}
-
-	glass = 0;
-
-	get appearance_groomingPenalty(){
-		let lipBalmPenalty = 0;
-		if(this.pcs_lipbalm > 0)
-			lipBalmPenalty = 0
-		else
-			lipBalmPenalty = 5
-
-
-		let hairPenalty = (1 - this.pcs_hairbsh) * 10;
-
-		let buzzCutPenalty = 0;
-		if(this.pcs_hairlng < 10)
-			buzzCutPenalty = 10
-
-
-		//Small penalty for not wearing deodorant, if pcs_sweat is low enough
-		let deodorantPenalty = 0;
-		if(this.deodorant_on == 0 || this.pcs_sweat >= 20)
-			deodorantPenalty = 5
-
-		let sweatPenalty = 0;
-		if(this.pcs_sweat < 22)
-			sweatPenalty = 0
-		else if(this.pcs_sweat < 38)
-			sweatPenalty = (this.pcs_sweat - 10) / 4
-		else if(this.pcs_sweat < 54)
-			sweatPenalty = (this.pcs_sweat - 10) / 2
-		else
-			sweatPenalty = 3 * (this.pcs_sweat - 10) / 4
-
-
-		//Glasses Penalty
-		let glassesPenalty = 0
-		if(this.glass == 1)
-			glassesPenalty = 10
-
-		let hairDyePenalty = 0;
-		//hair color fade penalty
-		/*if pcs_haircol ! nathcol:
-			if dyefade > 0 and dyefade < 7: hairDyePenalty = 5
-			if dyefade = 0: hairDyePenalty = 15
-		end*/
-		console.info("Hair-fading deactivated in appearance_groomingPenalty");
-
-		//Leg hair penalty
-		let legPenalty = 0;
-		if(this.pcs_leghair <= 0)
-			legPenalty = 0
-		else if(this.pcs_leghair <= 3)
-			legPenalty = 3
-		else if(this.pcs_leghair <= 5)
-			legPenalty = 6
-		else
-			legPenalty = 9
-
-		return sweatPenalty + glassesPenalty + hairDyePenalty + buzzCutPenalty + legPenalty + lipBalmPenalty + hairPenalty + deodorantPenalty;
-	}
-
-	get pcs_apprnc(){
-		return Math.clamp(this.pcs_apprncbase + this.appearance_clothingBonus + this.appearance_accessoriesBonus + this.appearance_groomingBonus - this.appearance_groomingPenalty,0,200);
-	}
-
-	get pcs_apprncbase(){
-		return this.skinBonus + this.bodyShapeBonus + this.attributeBonus - this.visibleAgePenalty - this.teethPenalty; //+ $supnatvnesh;
-	}
-
-	get attributeBonus(){
-		return this.appearanceAdjustFromBMI((this.agility / 5) + (this.vitality / 5));
-	}
-
-	get bodyShapeBonus(){
-		/*<<if getvar("$dounspell") == 1>>
-		<<set $pc.bodytipe = $pc.pcs_hips - $pc.pcs_waist>>
-		<<if getvar("$pc.bodytipe") < 20>>
-			<<set $result = 0>>
-		<<else if getvar("$pc.bodytipe") >= 20 and getvar("$pc.bodytipe") < 25>>
-			<<set $result = 2>>
-		<<else if (getvar("$pc.bodytipe") >= 25 and getvar("$pc.bodytipe") < 30) or getvar("$pc.bodytipe") >= 35>>
-			<<set $result = 4>>
-		<<else if getvar("$pc.bodytipe") >= 30 and getvar("$pc.bodytipe") < 35>>
-			<<set $result = 8>>
-		<</if>>
-		<!-- !!Setting the pcs_apprnc bonus based on fat and strength-->
-	<<else>>*/
-		let tempBodyShapeBonus = 0;
-		if(this.bmi < 16){
-			// severely underweight
-			tempBodyShapeBonus = -10
-		}else if(this.bmi < 19){
-			//!! underweight
-			tempBodyShapeBonus = 25
-		}else if(this.bmi < 25){
-			//!! healthy weight
-			tempBodyShapeBonus = 50
-		}else if(this.bmi < 30){
-			//!! overweight
-			tempBodyShapeBonus = 25
-		}else if(this.bmi < 35){
-			//!! moderately obese
-			tempBodyShapeBonus = 10
-		}else if(this.bmi < 40){
-			//!! severely obese
-			tempBodyShapeBonus = -15
-		}else if(this.bmi < 45){
-			//!! very severely obese
-			tempBodyShapeBonus = -40
-		}else{
-			//!! morbidly obese
-			tempBodyShapeBonus = -80
-		}
-
-		/*if succubusflag = 1:
-			tempBodyShapeBonus += 10
-		else*/
-			if(this.muscularity > 180)
-				tempBodyShapeBonus -= 70
-			else if(this.muscularity > 160)
-				tempBodyShapeBonus -= 50
-			else if(this.muscularity > 140)
-				tempBodyShapeBonus -= 30
-			else if(this.muscularity <= 5 || this.muscularity > 120)
-				tempBodyShapeBonus -= 20
-			else if(this.muscularity <= 10 || this.muscularity > 100)
-				tempBodyShapeBonus -= 15
-			else if(this.muscularity <= 15 || this.muscularity > 95)
-				tempBodyShapeBonus -= 10
-			else if(this.muscularity <= 25 || this.muscularity > 85)
-				tempBodyShapeBonus -= 5
-			else if(this.muscularity <= 35 || this.muscularity > 75)
-				tempBodyShapeBonus += 0
-			else if(this.muscularity <= 45 || this.muscularity > 60)
-				tempBodyShapeBonus += 5
-			else
-				tempBodyShapeBonus += 10
-
-		//end
-
-		//!!This modifies bodykoef for high or low salo values
-		if(this.salocatnow == 0 || this.salocatnow >= 7)
-			tempBodyShapeBonus -= 8
-		else if(this.salocatnow = 1 || this.salocatnow == 6)
-			tempBodyShapeBonus -= 4
-
-		if (this.vofat > 0)
-			tempBodyShapeBonus -= this.vofat
-
-		return tempBodyShapeBonus;
-	}
-
-
-	get teethPenalty(){
-		let tempAttributePenalty = 0;
-		if (this.pcs_teeth > 0)
-			tempAttributePenalty =  10 * this.pcs_teeth;
-		else if(this.pcs_teeth == 0)
-			tempAttributePenalty = 5
-
-		if(this.pcs_missing_teeth > 0)
-			tempAttributePenalty +=  10 * this.pcs_missing_teeth;
-
-
-		return this.appearanceAdjustFromBMI(tempAttributePenalty);
-	}
-
-	get visibleAgePenalty(){
-		let tempAttributePenalty = 0;
-		if (this.vidage < 20)
-			tempAttributePenalty = Math.round(5 * (20 - this.vidage) / 2);
-		else
-			tempAttributePenalty = 0;
-		return this.appearanceAdjustFromBMI(tempAttributePenalty);
-
-	}
-
-	appearanceAdjustFromBMI(a){
-		let bmi = this.bmi;
-		if(bmi < 16) // severely underweight
-			return a * 0.5;
-		if(bmi < 19) // underweight
-			return a * 0.95;
-		if(bmi < 25) // healthy
-			return a;
-		if(bmi < 30) // overweight
-			return a * 0.95;
-		if(bmi < 35) // moderately obese
-			return a * 0.8;
-		if(bmi < 40) // severely obese
-			return a * 0.55;
-		if(bmi < 45) // very severely obese
-			return a * 0.5;
-		return a * 0.4; //morbidly obese
-	}
 
 	// ----- Toys -----
 	analplugin = 0;
@@ -1445,7 +1042,7 @@ class PlayerCharacter{
 		return this.skillLevel('intelligence');
 	}
 
-	_skills = {
+	_skills:{[key: string]:any} = {
 	}
 
 	get skillsAll(){
@@ -1523,7 +1120,7 @@ class PlayerCharacter{
 		console.log("Skill Experience Gain:",skillId,inc,this._skills[skillId].experience );
 	}
 
-	skillsExperienceGain(skillObj,factor=1){
+	skillsExperienceGain(skillObj:{[key: string]:number},factor=1){
 		for(const [skillId,inc] of Object.entries(skillObj)){
 			this.skillExperienceGain(skillId,inc*factor)
 		}
@@ -1691,7 +1288,7 @@ class PlayerCharacter{
 
 		switch(key){
 			case 'vaginal_total':
-				return (this.stat('vaginal',awareness) + this.stat('vaginal_fist',awareness) + this.stat('vaginal_dildo',awareness) + this.stat('vaginal_strap',awareness));
+				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))
@@ -1909,7 +1506,7 @@ class PlayerCharacter{
 		17 = 'In a condom in your vagina
 	*/
 
-	_cum = {};
+	_cum:{ [key: string]: Array<any>} = {};
 
 	get cums(){
 		this._cumPurgeExpired();
@@ -1960,7 +1557,7 @@ class PlayerCharacter{
 
 	cumCleanByActivity(activityId){
 		const bodyparts = Object.entries(setup.getBodyparts());
-		const bodypartIdsToClean = bodyparts.filter(([id,bodypart]) => bodypart.clean.includes(activityId)).map(([id,bodypart]) => id);
+		const bodypartIdsToClean = bodyparts.filter(([id,bodypart]) => bodypart['clean'].includes(activityId)).map(([id,bodypart]) => id);
 		for(let bodypartIdToClean of bodypartIdsToClean)
 			this._cum[bodypartIdToClean] = [];
 	}
@@ -2148,7 +1745,7 @@ class PlayerCharacter{
 	get painRaw(){return this._pain.painRaw}
 	painInc(bodypart,v){return this._pain.painInc(bodypart,v)}
 	painIncAll(v){return this._pain.painIncAll(v)}
-	get painRelief(){return this._pain.painRelief}
+	//get painRelief(){return this._pain.painRelief}
 	painSet(bodypart,v){return this._pain.painSet(bodypart,v)}
 	get painTotal(){return this._pain.painTotal}
 
@@ -2315,28 +1912,37 @@ class PlayerCharacter{
 
 
 	// ----- Effects -----
-	_effects = {};
+	_effects:{ [key: string]: any } = {};
 	
-	get 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(this._effects[effectId] === null || time.isFuture(this._effects[effectId]))
+			if(this._effects[effectid] === null || time.isFuture(this._effects[effectid]))
 				result[effectid] = effectData;
 		}
 		result = Object.assign({},this.drugsActiveEffects,result);
 		return result;
 	}
 
-	get activeEffectsMoodlets(){
+	get activeEffectsMoodlets():{ [key: string]: ActiveMoodlet; }{
 		const time = State.variables.time;
-		return Object.fromEntries(this.activeEffectsMoodletIDs.map((moodletId) => [moodletId,Object.assign({expiration: time.endTime},setup.getMoodlet(moodletId))]));
+		return Object.fromEntries(
+			this.activeEffectsMoodletIDs.map(
+				(moodletId) => 
+					[
+						moodletId,
+						ActiveMoodlet.create(moodletId, {expiration: time.endTime})
+					]
+			)
+		);
+
 	}
 
-	get activeEffectsMoodletIDs(){
+	get activeEffectsMoodletIDs():Array<string>{
 		var result = [];
 		const activeEffects = this.activeEffects;
 		for(const activeEffect of Object.values(activeEffects)){
@@ -2444,6 +2050,8 @@ class PlayerCharacter{
 
 
 		// ----- Dying -----
+		//TODO
+		/*
 		for (const [riskId, riskData] of Object.entries(dieRisks)){
 			this._death[riskId] ??= {stage:0};
 			if(this[riskData['variable']] == 0){
@@ -2458,7 +2066,7 @@ class PlayerCharacter{
 					}
 				}
 			}
-		}
+		}*/
 
 	}
 
@@ -2692,7 +2300,7 @@ class PlayerCharacter{
 	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){return this._cycleLengthLast = v;}
+	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;
@@ -2844,4 +2452,4 @@ class PlayerCharacter{
 	};
 }
 
-window.PlayerCharacter = PlayerCharacter;
+setup.PlayerCharacter = PlayerCharacter;

+ 113 - 0
sugarcube/src/playerCharacter/_submodules/mood/moodlet.ts

@@ -0,0 +1,113 @@
+/// <reference path="../../../_shared/Initializing.ts"/>
+
+const enum moodletTimeMode{
+	Continuous,
+	Resetting,
+	Lasting
+}
+
+const enum moodletAccumulationMode{
+	ADD,
+	HIGHEST,
+	LOWEST,
+	DIMINISHING,
+	DIMINISHING_REVERSE
+}
+
+
+
+
+class Moodlet extends Initializing{
+	id:string;
+	title:string='Title Missing';
+	groupId:string= 'none';
+	description:string= 'Description Missing';
+	effect:number=0;
+	timeConversion:number=0;
+	timeMode:moodletTimeMode= moodletTimeMode.Resetting;
+	duration:number=60;
+	maxDuration:number=0;
+	includes:Array<string>=[];
+
+	static get(moodletId:string):Moodlet{
+		let result = setup.moodlets[moodletId] ?? new NullMoodlet();
+		result.id = moodletId;
+		return result;
+	}
+
+	
+
+	constructor(initialData:object={}){
+		super(initialData);
+	}
+
+	
+}
+
+class ActiveMoodlet extends Moodlet{
+
+	//expiration:Date;
+	private dynamicDataReferecne;
+
+	static create(moodletId:string,dynamicData:object):ActiveMoodlet{
+		let staticMoodlet = Moodlet.get(moodletId) as ActiveMoodlet;
+		let dynamicMoodlet = new ActiveMoodlet(staticMoodlet);
+		dynamicMoodlet.dynamicDataReferecne = dynamicData;
+		return dynamicMoodlet;
+	}
+
+	get expiration():Date{
+		return this.dynamicDataReferecne.expiration ?? State.variables.time.endTime;
+	}
+
+	get isActive(){
+		const time = State.variables.time;
+		return time.isFuture(this.expiration);
+	}
+}
+
+class PainMoodlet extends ActiveMoodlet{
+	static createPainMoodlet(pain:number):ActiveMoodlet{
+		if(pain <= 0)
+			return new NullMoodlet();
+		let result = new PainMoodlet(Moodlet.get('pain'));
+		result.effect = Math.round(pain * -1);
+		return result;
+	}
+}
+
+class NullMoodlet extends ActiveMoodlet{
+
+}
+
+class MoodletGroup extends Initializing{
+
+	id:string;
+	accumulationMode: moodletAccumulationMode;
+	diminishFactor:0;
+
+	static get(moodletGroupId:string):MoodletGroup{
+		let result = setup.moodletGroups[moodletGroupId] ?? new NullMoodletGroup();
+		result.id = moodletGroupId;
+		return result;
+	}
+
+	constructor(initialData:object={}){
+		super(initialData);
+	}
+}
+
+class NullMoodletGroup extends MoodletGroup{
+
+}
+
+setup.moodlets ??= {};
+setup.moodletGroups ??= {};
+
+setup.moodletGroups.none = new MoodletGroup({
+	accumulationMode: moodletAccumulationMode.ADD
+});
+
+setup.moodletGroups.pain = new MoodletGroup({
+	accumulationMode: moodletAccumulationMode.LOWEST
+});

+ 41 - 33
sugarcube/src/playerCharacter/_submodules/pc_mood.js → sugarcube/src/playerCharacter/_submodules/mood/pc_mood.ts

@@ -20,20 +20,38 @@ class Mood{
 		};
 	}
 
-	_moodlets = {};
-	get moodlets(){
+	_moodlets:{[key: string]: object} = {};
+
+	private moodlet(moodletId:string):ActiveMoodlet{
+		return ActiveMoodlet.create(moodletId,this._moodlets[moodletId] ?? {});
+	}
+
+
+	/**
+	 * All moodlets that are currently stored as active. Use moodletsActive() to get only the ones which haven't expired yet.
+	 * @private
+	 * @readonly
+	 * @type {{[key: string]: ActiveMoodlet}}
+	 */
+	private get moodlets():{[key: string]: ActiveMoodlet} {
 		let result = {};
-		for (const moodletId of Object.keys(this._moodlets)) {
-			result[moodletId] = this.moodletCombinedData(moodletId);
+		for (const [moodletId, moodletDynamicData] of Object.entries(this._moodlets)) {
+			result[moodletId] = ActiveMoodlet.create(moodletId,moodletDynamicData);
 		}
 		return result;
 	}
 
-	get moodletsActive(){
+	
+	/**
+	 * The moodlets that are currently active.
+	 * @readonly
+	 * @type {{[key: string]: ActiveMoodlet}}
+	 */
+	get moodletsActive():{[key: string]: ActiveMoodlet}{
 		let defaultMoodlets= Object.fromEntries(
 			Object.entries(this.moodlets).
 				filter(
-					([moodletId,moodletData])=>this.moodletIsActive(moodletId)
+					([moodletId,moodlet])=>moodlet.isActive
 				)
 		);
 		
@@ -45,7 +63,7 @@ class Mood{
 	get moodletsActiveByGroup(){
 		let result = {};
 		for (const [moodletId,moodletData] of Object.entries(this.moodletsActive)) {
-			const moodletGroup = moodletData.group ?? 'none';
+			const moodletGroup = moodletData.groupId ?? 'none';
 			result[moodletGroup] ??= {};
 			result[moodletGroup][moodletId] = moodletData;
 		}
@@ -58,12 +76,12 @@ class Mood{
 	 * @readonly
 	 * @type {number}
 	 */
-	get moodletsActiveEffect(){
+	get moodletsActiveEffect():number{
 		return Object.values(this.moodletsActiveByGroupAccumulationApplied).reduce(
 			(accumulator, currentValue) => accumulator + Object.values(currentValue).reduce(
-				(innerAccumulator,innerCurrentValue) => innerAccumulator + innerCurrentValue.effect ?? 0
+				(innerAccumulator,innerCurrentValue) => innerAccumulator + innerCurrentValue.effect
 			,0)
-		,0);
+		,0) as number;
 	}
 
 	get moodletsActiveByGroupAccumulationApplied(){
@@ -71,29 +89,29 @@ class Mood{
 		let activeEffectsByGroup = this.moodletsActiveByGroup;
 		for (const [moodletGroupId,moodletGroup] of Object.entries(activeEffectsByGroup)) {
 			if(!Object.keys(moodletGroup).length) continue;
-			const groupData = setup.getMoodletGroup(moodletGroupId);
+			const groupData = MoodletGroup.get(moodletGroupId);
 
 			
 			let moodletsSortedByEffect = Object.entries(moodletGroup).map(([moodletId,moodletData])=>([moodletId,moodletData.effect ?? 0])).sort((a,b)=>b[1]-a[1]);
 			switch(groupData.accumulationMode){
-				case setup.moodletAccumulationMode.ADD:
+				case moodletAccumulationMode.ADD:
 					break;
-				case setup.moodletAccumulationMode.HIGHEST:
+				case moodletAccumulationMode.HIGHEST:
 					for(let moodletIndex = 0;moodletIndex < moodletsSortedByEffect.length; moodletIndex++){
 						if(moodletIndex)
 							activeEffectsByGroup[moodletGroupId][moodletsSortedByEffect[moodletIndex][0]].effect = 0;
 					}
 					break;
-				case setup.moodletAccumulationMode.LOWEST:
+				case moodletAccumulationMode.LOWEST:
 					moodletsSortedByEffect.reverse();
 					for(let moodletIndex = 0;moodletIndex < moodletsSortedByEffect.length; moodletIndex++){
 						if(moodletIndex)
 							activeEffectsByGroup[moodletGroupId][moodletsSortedByEffect[moodletIndex][0]].effect = 0;
 					}
 					break;
-				case setup.moodletAccumulationMode.DIMINISHING_REVERSE:
+				case moodletAccumulationMode.DIMINISHING_REVERSE:
 					moodletsSortedByEffect.reverse();
-				case setup.moodletAccumulationMode.DIMINISHING:
+				case moodletAccumulationMode.DIMINISHING:
 					const diminishFactor = groupData.diminishFactor ?? 0.5;
 					for(let moodletIndex = 0;moodletIndex < moodletsSortedByEffect.length; moodletIndex++){
 						activeEffectsByGroup[moodletGroupId][moodletsSortedByEffect[moodletIndex][0]].effect *= Math.pow(diminishFactor,moodletIndex);
@@ -115,39 +133,35 @@ class Mood{
 		this._moodlets = Object.fromEntries(
 			Object.entries(this._moodlets).
 				filter(
-					([moodletId,moodletData])=>this.moodletIsActive(moodletId)
+					([moodletId,moodletData])=>ActiveMoodlet.create(moodletId,moodletData).isActive
 				)
 		);
 
 	}
 
-	moodletApplyById(moodletId,minutes=0){
-		const moodletData = setup.getMoodlet(moodletId);
+	moodletApplyById(moodletId:string,minutes:number=0){
+		const moodletData = Moodlet.get(moodletId);
 		const time = State.variables.time;
 		switch(moodletData.timeMode){
-			case setup.moodletTimeMode.Continuous:
+			case moodletTimeMode.Continuous:
 				this.moodletIncTime(moodletId,minutes);
 				break;
-			case setup.moodletTimeMode.Lasting:
+			case moodletTimeMode.Lasting:
 				this.moodletIncTime(moodletId,525600000);
 				break;
-			case setup.moodletTimeMode.Resetting:
+			case moodletTimeMode.Resetting:
 				let expirationTimeResetting = time.nowWithMinutesOffset(moodletData.duration);
 				this.moodletUpdate(moodletId,{expiration: expirationTimeResetting});
 				break;
 		}
 	}
 
-	moodletCombinedData(moodletId){
-		return Object.assign({},setup.getMoodlet(moodletId),this._moodlets[moodletId] ?? {});
-	}
-
 	moodletDeactivateById(moodletId){
 		delete this._moodlets[moodletId];
 	}
 
 	moodletIncTime(moodletId,minutes,skipIncludedMoodlets=false){
-		const moodletData = this.moodletCombinedData(moodletId);
+		const moodletData = this.moodlet(moodletId);
 		const time = State.variables.time;
 		let expiration;
 
@@ -174,12 +188,6 @@ class Mood{
 		}
 	}
 
-	moodletIsActive(moodletId){
-		const moodletData = this.moodletCombinedData(moodletId);
-		const time = State.variables.time;
-		return time.isFuture(moodletData.expiration);
-	}
-
 	moodletUpdate(moodletId,updateObject){
 		this._moodlets[moodletId] = Object.assign({},this._moodlets[moodletId],updateObject);
 	}

+ 5 - 5
sugarcube/src/playerCharacter/_submodules/pc_pain.js → sugarcube/src/playerCharacter/_submodules/pc_pain.ts

@@ -1,16 +1,16 @@
 class Pain{
 	get OWNER(){return State.variables.pc}
 
-    _pain = {
+    _pain:{[key: string]:number} = {
 	}
 
 	pain(bodypart){
 		return this._pain[bodypart] || 0;
 	}
 
-	get painByBodyparts(){
+	get painByBodyparts():{[key: string]:number}{
 		var result = {};
-		var painRaw = {};
+		var painRaw:{[key: string]:Array<number>} = {};
 		for (const [bodypart, currentPain] of Object.entries(this._pain)) {
 			painRaw[bodypart] = [currentPain];
 		}
@@ -21,7 +21,7 @@ class Pain{
 		}
 
 		for(const [bodypartId,painValues] of Object.entries(painRaw)){
-			let painValuesSorted = painValues.toSorted((a,b) => b - a);
+			let painValuesSorted:Array<number> = painValues.toSorted((a,b) => b - a);
 			let i=0;
 			let sum = 0;
 			for(const painValue of painValuesSorted){
@@ -42,7 +42,7 @@ class Pain{
         return 1/120;
     }
 
-	get painOfActiveEffects(){
+	get painOfActiveEffects():{[key: string]:number}{
 		return this.OWNER.painOfActiveEffects;
 	}
 

+ 0 - 262
sugarcube/src/playerCharacter/mood/moodlets.js

@@ -1,262 +0,0 @@
-setup.moodletTimeMode = {
-	Continuous: 0,
-	Resetting: 1,
-	Lasting: 2
-}
-
-setup.moodletAccumulationMode = {
-	ADD: 0,
-	HIGHEST: 1,
-	LOWEST: 2,
-	DIMINISHING: 3,
-	DIMINISHING_REVERSE: 4
-}
-
-setup.moodlets ??= {};
-setup.moodletGroups ??= {};
-setup.moodletGroups.none = {
-	accumulationMode: setup.moodletAccumulationMode.ADD
-}
-
-setup.moodletGroups.pain = {
-	accumulationMode: setup.moodletAccumulationMode.LOWEST
-}
-
-setup.moodlets.pain = {
-	title: "Pain",
-	group: 'pain',
-	description: '',
-	effect: 0,
-	timeMode: setup.moodletTimeMode.Lasting
-};
-
-setup.moodletGroups.pleasure = {
-	accumulationMode: setup.moodletAccumulationMode.HIGHEST
-}
-setup.moodlets.orgasm = {
-	title: "Orgasm",
-	group: 'pleasure',
-	description: '',
-	effect: 30,
-	timeMode: setup.moodletTimeMode.Resetting,
-	duration: 240
-};
-
-setup.moodletGroups.socialNegative = {
-	accumulationMode: setup.moodletAccumulationMode.LOWEST
-}
-setup.moodlets.annoyed = {
-	title: "Annoyed",
-	group: 'socialNegative',
-	description: 'Somebody has annoyed you with their behavior.',
-	effect: -5,
-	timeMode: setup.moodletTimeMode.Resetting,
-	duration: 30
-};
-setup.moodlets.embarrassed = {
-	title: "Embarrassed",
-	group: 'socialNegative',
-	description: 'You have been embarrassed.',
-	effect: -30,
-	timeMode: setup.moodletTimeMode.Resetting,
-	duration: 240
-};
-
-setup.moodlets.ignored = {
-	title: "Ignored",
-	group: 'socialNegative',
-	description: 'You have been ignored where you would have liked to get attention.',
-	effect: -5,
-	timeMode: setup.moodletTimeMode.Resetting,
-	duration: 60
-};
-
-setup.moodlets.insulted = {
-	title: "Insulted",
-	group: 'socialNegative',
-	description: 'You have been insulted.',
-	effect: -10,
-	timeMode: setup.moodletTimeMode.Resetting,
-	duration: 240
-};
-
-
-
-setup.moodletGroups.socialPositive = {
-	accumulationMode: setup.moodletAccumulationMode.HIGHEST
-}
-
-setup.moodlets.acknowledged = {
-	title: "Acknowledged",
-	group: 'socialPositive',
-	description: 'You got the positive attention you wanted.',
-	effect: 5,
-	timeMode: setup.moodletTimeMode.Resetting,
-	duration: 60
-};
-
-setup.moodlets.littlePraise = {
-	title: "Little Praise",
-	group: 'socialPositive',
-	description: 'You received a minor praise.',
-	effect: 10,
-	timeMode: setup.moodletTimeMode.Resetting,
-	duration: 120
-};
-
-setup.moodlets.niceChat = {
-	title: "Nice Chat",
-	group: 'socialPositive',
-	description: '',
-	effect: 15,
-	timeMode: setup.moodletTimeMode.Resetting,
-	duration: 120
-};
-
-setup.moodletGroups.vanity = {
-	accumulationMode: setup.moodletAccumulationMode.DIMINISHING
-}
-
-setup.moodletGroups.victim = {
-	accumulationMode: setup.moodletAccumulationMode.LOWEST
-}
-
-// ----- Hunger -----
-setup.moodletGroups.hunger = {
-	accumulationMode: setup.moodletAccumulationMode.LOWEST
-}
-
-setup.moodlets.hunger_1 = {
-	title: "Very Hungry",
-	group: 'hunger',
-	description: '',
-	effect: -30,
-	timeMode: setup.moodletTimeMode.Lasting
-};
-
-setup.moodlets.hunger_2 = {
-	title: "Extremely Hungry",
-	group: 'hunger',
-	description: '',
-	effect: -60,
-	timeMode: setup.moodletTimeMode.Lasting
-};
-
-setup.moodlets.hunger_3 = {
-	title: "Dying from Hunger",
-	group: 'hunger',
-	description: '',
-	effect: -120,
-	timeMode: setup.moodletTimeMode.Lasting
-};
-
-// ----- Thirst -----
-setup.moodletGroups.thirst = {
-	accumulationMode: setup.moodletAccumulationMode.LOWEST
-}
-setup.moodlets.thirst_1 = {
-	title: "Very Thirsty",
-	group: 'thirst',
-	description: '',
-	effect: -30,
-	timeMode: setup.moodletTimeMode.Lasting
-};
-
-setup.moodlets.thirst_2 = {
-	title: "Extremely Thirsty",
-	group: 'thirst',
-	description: '',
-	effect: -60,
-	timeMode: setup.moodletTimeMode.Lasting
-};
-
-setup.moodlets.thirst_3 = {
-	title: "Dying from Thirst",
-	group: 'thirst',
-	description: '',
-	effect: -120,
-	timeMode: setup.moodletTimeMode.Lasting
-};
-
-// ----- Phone -----
-setup.moodletGroups.phone = {
-	accumulationMode: setup.moodletAccumulationMode.HIGHEST
-}
-setup.moodlets.phone_0 = {
-	title: "Played with phone",
-	group: 'phone_0',
-	description: 'You played with your phone.',
-	effect: 20,
-	timeMode: setup.moodletTimeMode.Resetting,
-	duration: 120,
-	maxDuration: 480
-};
-
-// ----- Reading -----
-setup.moodletGroups.read = {
-	accumulationMode: setup.moodletAccumulationMode.HIGHEST
-}
-setup.moodlets.read_finish = {
-	title: "Finished Book",
-	group: 'read',
-	description: 'You finished a book.',
-	effect: 15,
-	timeMode: setup.moodletTimeMode.Resetting,
-	duration: 240
-};
-setup.moodlets.read = {
-	title: "Read",
-	group: 'read',
-	description: 'You read a book.',
-	effect: 10,
-	timeMode: setup.moodletTimeMode.Continuous,
-	timeConversion: 1,
-	maxDuration: 480
-};
-
-// ----- TV -----
-setup.moodletGroups.tv = {
-	accumulationMode: setup.moodletAccumulationMode.HIGHEST
-}
-setup.moodlets.tv_0 = {
-	title: "Watched TV (Low Quality)",
-	group: 'tv',
-	description: 'You watched TV on a low quality TV.',
-	effect: 5,
-	timeMode: setup.moodletTimeMode.Continuous,
-	timeConversion: 1,
-	maxDuration: 480
-};
-
-setup.moodlets.tv_1 = {
-	title: "Watched TV (Medium Quality)",
-	group: 'tv',
-	description: 'You watched TV on a medium quality TV.',
-	effect: 10,
-	timeMode: setup.moodletTimeMode.Continuous,
-	timeConversion: 1,
-	maxDuration: 480,
-	includes:['tv_0']
-};
-
-// ----- work -----
-setup.moodletGroups.work = {
-	accumulationMode: setup.moodletAccumulationMode.ADD
-}
-setup.moodlets.housework = {
-	title: "Housework",
-	group: 'work',
-	description: 'You performed housework.',
-	effect: -20,
-	timeMode: setup.moodletTimeMode.Continuous,
-	timeConversion: 2,
-	maxDuration: 480
-};
-
-setup.getMoodlet ??= function(moodletId){
-	return setup.moodlets[moodletId] ?? {};
-}
-
-setup.getMoodletGroup ??= function(moodletGroupId){
-	return setup.moodletGroups[moodletGroupId] ?? {};
-}

+ 234 - 0
sugarcube/src/playerCharacter/mood/moodlets.ts

@@ -0,0 +1,234 @@
+setup.moodlets.pain = new Moodlet({
+	title: "Pain",
+	group: 'pain',
+	description: '',
+	effect: 0,
+	timeMode: moodletTimeMode.Lasting
+});
+
+setup.moodletGroups.pleasure = new MoodletGroup({
+	accumulationMode: moodletAccumulationMode.HIGHEST
+});
+setup.moodlets.orgasm = new Moodlet({
+	title: "Orgasm",
+	group: 'pleasure',
+	description: '',
+	effect: 30,
+	timeMode: moodletTimeMode.Resetting,
+	duration: 240
+});
+
+setup.moodletGroups.socialNegative = new MoodletGroup({
+	accumulationMode: moodletAccumulationMode.LOWEST
+});
+setup.moodlets.annoyed = new Moodlet({
+	title: "Annoyed",
+	group: 'socialNegative',
+	description: 'Somebody has annoyed you with their behavior.',
+	effect: -5,
+	timeMode: moodletTimeMode.Resetting,
+	duration: 30
+});
+setup.moodlets.embarrassed = new Moodlet({
+	title: "Embarrassed",
+	group: 'socialNegative',
+	description: 'You have been embarrassed.',
+	effect: -30,
+	timeMode: moodletTimeMode.Resetting,
+	duration: 240
+});
+
+setup.moodlets.ignored = new Moodlet({
+	title: "Ignored",
+	group: 'socialNegative',
+	description: 'You have been ignored where you would have liked to get attention.',
+	effect: -5,
+	timeMode: moodletTimeMode.Resetting,
+	duration: 60
+});
+
+setup.moodlets.insulted = new Moodlet({
+	title: "Insulted",
+	group: 'socialNegative',
+	description: 'You have been insulted.',
+	effect: -10,
+	timeMode: moodletTimeMode.Resetting,
+	duration: 240
+});
+
+
+
+setup.moodletGroups.socialPositive = new MoodletGroup({
+	accumulationMode: moodletAccumulationMode.HIGHEST
+});
+
+setup.moodlets.acknowledged = new Moodlet({
+	title: "Acknowledged",
+	group: 'socialPositive',
+	description: 'You got the positive attention you wanted.',
+	effect: 5,
+	timeMode: moodletTimeMode.Resetting,
+	duration: 60
+});
+
+setup.moodlets.littlePraise = new Moodlet({
+	title: "Little Praise",
+	group: 'socialPositive',
+	description: 'You received a minor praise.',
+	effect: 10,
+	timeMode: moodletTimeMode.Resetting,
+	duration: 120
+});
+
+setup.moodlets.niceChat = new Moodlet({
+	title: "Nice Chat",
+	group: 'socialPositive',
+	description: '',
+	effect: 15,
+	timeMode: moodletTimeMode.Resetting,
+	duration: 120
+});
+
+setup.moodletGroups.vanity = new MoodletGroup({
+	accumulationMode: moodletAccumulationMode.DIMINISHING
+});
+
+setup.moodletGroups.victim = new MoodletGroup({
+	accumulationMode: moodletAccumulationMode.LOWEST
+});
+
+// ----- Hunger -----
+setup.moodletGroups.hunger = new MoodletGroup({
+	accumulationMode: moodletAccumulationMode.LOWEST
+});
+
+setup.moodlets.hunger_1 = new Moodlet({
+	title: "Very Hungry",
+	group: 'hunger',
+	description: '',
+	effect: -30,
+	timeMode: moodletTimeMode.Lasting
+});
+
+setup.moodlets.hunger_2 = new Moodlet({
+	title: "Extremely Hungry",
+	group: 'hunger',
+	description: '',
+	effect: -60,
+	timeMode: moodletTimeMode.Lasting
+});
+
+setup.moodlets.hunger_3 = new Moodlet({
+	title: "Dying from Hunger",
+	group: 'hunger',
+	description: '',
+	effect: -120,
+	timeMode: moodletTimeMode.Lasting
+});
+
+// ----- Thirst -----
+setup.moodletGroups.thirst = new MoodletGroup({
+	accumulationMode: moodletAccumulationMode.LOWEST
+});
+
+setup.moodlets.thirst_1 = new Moodlet({
+	title: "Very Thirsty",
+	group: 'thirst',
+	description: '',
+	effect: -30,
+	timeMode: moodletTimeMode.Lasting
+});
+
+setup.moodlets.thirst_2 = new Moodlet({
+	title: "Extremely Thirsty",
+	group: 'thirst',
+	description: '',
+	effect: -60,
+	timeMode: moodletTimeMode.Lasting
+});
+
+setup.moodlets.thirst_3 = new Moodlet({
+	title: "Dying from Thirst",
+	group: 'thirst',
+	description: '',
+	effect: -120,
+	timeMode: moodletTimeMode.Lasting
+});
+
+// ----- Phone -----
+setup.moodletGroups.phone = new MoodletGroup({
+	accumulationMode: moodletAccumulationMode.HIGHEST
+});
+
+setup.moodlets.phone_0 = new Moodlet({
+	title: "Played with phone",
+	group: 'phone_0',
+	description: 'You played with your phone.',
+	effect: 20,
+	timeMode: moodletTimeMode.Resetting,
+	duration: 120,
+	maxDuration: 480
+});
+
+// ----- Reading -----
+setup.moodletGroups.read = new MoodletGroup({
+	accumulationMode: moodletAccumulationMode.HIGHEST
+});
+
+setup.moodlets.read_finish = new Moodlet({
+	title: "Finished Book",
+	group: 'read',
+	description: 'You finished a book.',
+	effect: 15,
+	timeMode: moodletTimeMode.Resetting,
+	duration: 240
+});
+setup.moodlets.read = new Moodlet({
+	title: "Read",
+	group: 'read',
+	description: 'You read a book.',
+	effect: 10,
+	timeMode: moodletTimeMode.Continuous,
+	timeConversion: 1,
+	maxDuration: 480
+});
+
+// ----- TV -----
+setup.moodletGroups.tv = new MoodletGroup({
+	accumulationMode: moodletAccumulationMode.HIGHEST
+});
+setup.moodlets.tv_0 = new Moodlet({
+	title: "Watched TV (Low Quality)",
+	group: 'tv',
+	description: 'You watched TV on a low quality TV.',
+	effect: 5,
+	timeMode: moodletTimeMode.Continuous,
+	timeConversion: 1,
+	maxDuration: 480
+});
+
+setup.moodlets.tv_1 = new Moodlet({
+	title: "Watched TV (Medium Quality)",
+	group: 'tv',
+	description: 'You watched TV on a medium quality TV.',
+	effect: 10,
+	timeMode: moodletTimeMode.Continuous,
+	timeConversion: 1,
+	maxDuration: 480,
+	includes:['tv_0']
+});
+
+// ----- work -----
+setup.moodletGroups.work = new MoodletGroup({
+	accumulationMode: moodletAccumulationMode.ADD
+});
+
+setup.moodlets.housework = new Moodlet({
+	title: "Housework",
+	group: 'work',
+	description: 'You performed housework.',
+	effect: -20,
+	timeMode: moodletTimeMode.Continuous,
+	timeConversion: 2,
+	maxDuration: 480
+});

+ 3 - 3
sugarcube/src/playerCharacter/mood/moodlets.tw

@@ -58,19 +58,19 @@
 					<</textWithTooltip>>
 			<</switch>>
 			<<switch _moodletData.timeMode>>
-				<<case setup.moodletTimeMode.Continuous>>
+				<<case 0>>
 					<<textWithTooltip "🔁">>
 						<h3>Repeatable</h3>
 						This mood effect can be gained multiple times. This will increase the time it is active, up to a maximum number of minutes.
 						For example, you can make the effect of watching TV last longer by watching more TV.
 					<</textWithTooltip>>
-				<<case setup.moodletTimeMode.Resetting>>
+				<<case 1>>
 					<<textWithTooltip "🔂">>
 						<h3>Resetting</h3>
 						When this mood effect is gained, it resets the time it is active.
 						For example, the effect of gaining a compliment lasts for 4 hours. If you get another compliment 2 hours after you got the first one, the effect will last for 6 hours (2 of the first and 4 of the second compliment).
 					<</textWithTooltip>>
-				<<case setup.moodletTimeMode.Lasting>>
+				<<case 2>>
 					<<textWithTooltip "🔚">>
 						<h3>Lasting</h3>
 						This mood effect will last until canceled. Depending on the effect, this might be an interaction with a NPC, sleeping for a specified amount of time, random chance or anything else.

+ 1 - 1
sugarcube/src/start/initialization/init_pre_intro.tw

@@ -1,7 +1,7 @@
 :: init_pre_intro[include]
     <!-- Only the initialization that MUST happen for the intro to function. -->
     <<script>>
-		State.variables.pc = new PlayerCharacter();
+		State.variables.pc = new setup.PlayerCharacter();
 		State.variables.time = new setup.GameTime();
 		State.variables.wardrobe = new Wardrobe();
 		State.variables.npcs = new NPCsDict();

+ 4 - 4
sugarcube/src/version/VersionControl.ts

@@ -25,13 +25,13 @@ class VersionControl{
 				variables.q['school'].groups[['cool','jocks','nerds','gopniks','outcasts','teachers'][variables['grupTipe'] - 1]].member = true;
 			}
 		}
-		/*
+		
 		if(versionOfVariables < 14){
-			variables.pc._makeupAmount = variables.pc._pcs_makeup;
+			variables.pc._makeupAmount = variables.pc['_pcs_makeup'];
 			variables.pc._makeupQuality= 2;
-			variables.pc._leghair = variables.pc.pcs_leghair;
+			variables.pc._leghair = variables.pc['pcs_leghair'];
 		}
-
+		/*
 		if(versionOfVariables < 16){
 			if(variables.housing._home == 'parents_home')			variables.housing._home = 'homeParents';
 			if(variables.housing._access.includes('parents_home'))	variables.housing._access.push('homeParents');

+ 1 - 1
sugarcube/tsconfig.json

@@ -1,6 +1,6 @@
 {
 	"compilerOptions": {
-		"target": "ES2022", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */
+		"target": "ESNext", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */
 		"module": "amd", /* Specify module code generation: */
 		"sourceMap": false, 
 		"outFile": "temp/typescript.js",