Skip to content
Snippets Groups Projects
Forked from pregmodfan / fc-pregmod
16093 commits behind the upstream repository.
wombJS.js 27.71 KiB
/*
This is a womb processor/simulator script. It takes care of calculation of belly sizes based on individual fetus sizes, with full support of broodmothers implant random turning on and off possibility. Also this can be expanded to store more parents data in each individual fetus in future.
Design limitations:
- Mother can't gestate children with different speeds at same time. All speed changes apply to all fetuses.
- Sizes of individual fetuses updated only on call of WombGetVolume - not every time as called WombProgress. This is for better overall code speed.
- For broodmothers we need actual "new ova release" code now. But it's possible to control how many children will be added each time, and so - how much children is ready to birth each time.

Usage from SugarCube code (samples):

WombInit($slave) - before first pregnancy, at slave creation, of as backward compatibility update.

WombImpregnate($slave, $fetus_count, $fatherID, $initial_age) - should be added after normal impregnation code, with already calculated fetus count. ID of father - can be used in future for processing children from different fathers in one pregnancy. Initial age normally 1 (as .preg normally set to 1), but can be raised if needed. Also should be called at time as broodmother implant add another fetus(es), or if new fetuses added from other sources in future (transplanting maybe?)

WombProgress($slave, $time_to_add_to_fetuses, $real_time_to_add_to_fetuses) - after code that update $slave.preg, time to add should be the same.

$isReady = WombBirthReady($slave, $birth_ready_age) - how many children ready to be birthed if their time to be ready is $birth_ready_age (40 is for normal length pregnancy). Return int - count of ready to birth children, or 0 if no ready exists.

$children = WombBirth($slave, $birth_ready_age) - for actual birth. Return array with fetuses objects that birthed (can be used in future) and remove them from womb array of $slave. Should be called at actual birth code in SugarCube. fetuses that not ready remained in womb (array).

WombFlush($slave) - clean womb (array). Can be used at broodmother birthstorm or abortion situations in game. But birthstorm logically should use WombBirth($slave, 35) or so before - some children in this event is live capable, others is not.

$slave.bellyPreg = WombGetVolume($slave) - return double, with current womb volume in CC - for updating $slave.bellyPreg, or if need to update individual fetuses sizes.

*/

/**
 * Init womb system.
 * @param {FC.HumanState} actor
 */
globalThis.WombInit = function(actor) {
	let i;

	if (!Array.isArray(actor.womb)) {
		// alert("creating new womb"); // debugging
		actor.womb = [];
	}

	// console.log("broodmother:" + typeof actor.broodmother);

	if (typeof actor.broodmother !== "number") {
		actor.broodmother = 0;
		actor.broodmotherFetuses = 0;
	}

	if (typeof actor.readyOva !== "number") {
		actor.readyOva = 0;
	}

	if (actor.pregData === undefined) {
		actor.pregData = clone(setup.pregData.human);
		// Setup should be through deep copy, so in future, if we like, these values can be changed individually. Gameplay expansion possibilities. But for dev time to simplify debugging:
		// actor.pregData = setup.pregData.human;  // any changes in setup pregData template will be applied immediately to all. But can't be made separate changes.
	}

	if (typeof actor.eggType !== 'string') {
		actor.eggType = "human";
	}

	// backward compatibility setup. Fully accurate for normal pregnancy only.
	if (actor.womb.length > 0 && actor.womb[0].genetics === undefined) {
		i = 0;
		actor.womb.forEach(function(ft) {
			if (typeof ft.reserve !== 'string') {
				ft.reserve = "";
			}
			if (typeof ft.motherID !== 'number') { // setting missing biological mother ID for fetus.
				ft.motherID = actor.ID;
			}
			if (ft.ID === undefined) {
				ft.ID = generateNewID();
			}
			if (typeof ft.realAge !== 'number') { // setting missing chronological age
				ft.realAge = ft.age;
			}

			ft.genetics = generateGenetics(actor, actor.pregSource, i);
			i++;
		});
	} else if (actor.womb.length === 0 && actor.pregType > 0 && actor.broodmother === 0) {
		WombImpregnate(actor, actor.pregType, actor.pregSource, actor.preg);
	} else if (actor.womb.length === 0 && actor.pregType > 0 && actor.broodmother > 0 && actor.broodmotherOnHold < 1) {
		// sorry but for already present broodmothers it's impossible to calculate fully, approximation used.
		let pw = actor.preg,
			bCount, bLeft;
		if (pw > actor.pregData.normalBirth) { pw = actor.pregData.normalBirth; } // to avoid disaster.
		bCount = Math.floor(actor.pregType / pw);
		bLeft = actor.pregType - (bCount * pw);
		if (pw > actor.pregType) {
			pw = actor.pregType; // low children count broodmothers not supported here. It's emergency/backward compatibility code, and they not in game anyway. So minimum is 1 fetus in week.
			actor.preg = pw; // fixing initial pregnancy week.
		}
		for (i = 0; i < pw; i++) {
			WombImpregnate(actor, bCount, actor.pregSource, i); // setting fetuses for every week, up to 40 week at max.
		}

		if (bLeft > 0) {
			WombImpregnate(actor, bLeft, actor.pregSource, i + 1); // setting up leftover of fetuses.
		}
	}

	actor.womb.forEach(f => {
		if (!jsDef(f.genetics.inbreedingCoeff)) {
			f.genetics.inbreedingCoeff = ibc.coeff(
				{ID: null, mother: f.genetics.mother, father: f.genetics.father}
			);
		}
	});
};

App.Entity.Fetus = class {
	/** Construct a new fetus
	 * @param {number} age - initial age, after conception, in weeks
	 * @param {number} fatherID
	 * @param {FC.HumanState} mother
	 * @param {string} name - name of ovum (generally ovumNN where NN is the number in the batch)
	 */
	constructor(age, fatherID, mother, name) {
		/** Unique identifier for this fetus */
		this.ID = generateNewID();
		/** Week since conception */
		this.age = age;
		/** Week in mother (age since implantation) */
		this.realAge = 1;
		this.fatherID = fatherID;
		this.volume = 1;
		this.reserve = "";
		/** All identical multiples share the same twinID */
		this.twinID = "";
		this.motherID = mother.ID;
		this.genetics = generateGenetics(mother, fatherID, name);
	}
};

/**
 * @param {FC.HumanState} actor
 * @param {number} fCount
 * @param {number} fatherID
 * @param {number} age
 * @param {FC.HumanState} [surrogate] genetic mother
 */
globalThis.WombImpregnate = function(actor, fCount, fatherID, age, surrogate) {
	for (let i = 0; i < fCount; i++) {
		const tf = new App.Entity.Fetus(age, fatherID, surrogate || actor, `ovum${i}`);
		try {
			if (actor.womb.length === 0) {
				actor.pregWeek = age;
				actor.preg = age;
			}
			actor.womb.push(tf);
		} catch (err) {
			WombInit(actor);
			actor.womb.push(tf);
			alert("WombImpregnate warning - " + actor.slaveName + " " + err);
		}
	}
	MissingParentIDCorrection(actor);
	WombUpdatePregVars(actor);
};

/**
 * @param {FC.HumanState} actor (surrogate mother)
 * @param {number} fCount
 * @param {FC.HumanState} mother (genetic mother)
 * @param {number} fatherID
 * @param {number} age
 */
globalThis.WombSurrogate = function(actor, fCount, mother, fatherID, age) {
	WombImpregnate(actor, fCount, fatherID, age, mother);
};

/**
 * @param {FC.HumanState} actor (surrogate mother)
 * @param {number} fCount
 * @param {FC.HumanState} mother (genetic parent being cloned)
 * @param {number} age
 */
globalThis.WombImpregnateClone = function(actor, fCount, mother, age) {
	setMissingParents(mother);
	const motherOriginal = V.genePool.find(s => s.ID === mother.ID) || mother;

	for (let i = 0; i < fCount; i++) {
		const tf = new App.Entity.Fetus(age, mother.ID, mother, `ovum${i}`);

		// gene corrections
		tf.fatherID = -7;
		tf.genetics.gender = mother.genes;
		tf.genetics.mother = mother.mother;
		tf.genetics.father = mother.father;
		if (mother.ID === -1) {
			tf.genetics.motherName = mother.slaveName;
			tf.genetics.fatherName = mother.slaveName;
			tf.genetics.clone = PlayerName();
			tf.genetics.cloneID = -1;
		} else {
			tf.genetics.motherName = mother.slaveName;
			tf.genetics.fatherName = mother.slaveName;
			// @ts-ignore - mother isn't the player, so must be a slave
			tf.genetics.clone = SlaveFullName(mother);
			tf.genetics.cloneID = mother.ID;
		}
		tf.genetics.inbreedingCoeff = mother.inbreedingCoeff;
		tf.genetics.intelligence = motherOriginal.intelligence;
		tf.genetics.face = motherOriginal.face;
		tf.genetics.faceShape = motherOriginal.faceShape;
		tf.genetics.geneticQuirks = clone(mother.geneticQuirks);
		tf.genetics.skin = motherOriginal.skin;

		try {
			if (actor.womb.length === 0) {
				actor.pregWeek = age;
				actor.preg = age;
				actor.pregSource = -7;
			}
			actor.womb.push(tf);
		} catch (err) {
			WombInit(actor);
			actor.womb.push(tf);
			alert("WombImpregnate warning - " + actor.slaveName + " " + err);
		}
	}
	WombUpdatePregVars(actor);
};

// Should be used to set biological age for fetus (ageToAdd), AND chronological (realAgeToAdd). Speed up or slow down gestation drugs should affect ONLY biological.
globalThis.WombProgress = function(actor, ageToAdd, realAgeToAdd = ageToAdd) {
	ageToAdd = Math.ceil(ageToAdd * 10) / 10;
	realAgeToAdd = Math.ceil(realAgeToAdd * 10) / 10;
	try {
		actor.womb.forEach(ft => {
			ft.age += ageToAdd;
			ft.realAge += realAgeToAdd;
		});
	} catch (err) {
		WombInit(actor);
		alert("WombProgress warning - " + actor.slaveName + " " + err);
	}
};

globalThis.WombBirth = function(actor, readyAge) {
	try {
		WombSort(actor); // For normal processing fetuses that more old should be first. Now - they are.
	} catch (err) {
		WombInit(actor);
		alert("WombBirth warning - " + actor.slaveName + " " + err);
	}

	let birthed = [];
	let ready = WombBirthReady(actor, readyAge);
	let i;

	for (i = 0; i < ready; i++) { // here can't be used "for .. in .." syntax.
		birthed.push(actor.womb.shift());
	}

	return birthed;
};

globalThis.WombFlush = function(actor) {
	actor.womb = [];
	WombUpdatePregVars(actor);
};

globalThis.WombBirthReady = function(actor, readyAge) {
	let readyCnt = 0;
	try {
		readyCnt += actor.womb.filter(ft => ft.age >= readyAge).length;
	} catch (err) {
		WombInit(actor);
		alert("WombBirthReady warning - " + actor.slaveName + " " + err);
		return 0;
	}

	return readyCnt;
};

globalThis.WombGetVolume = function(actor) { // most legacy code from pregJS.tw with minor adaptation.
	if (actor.pregData.sizeType === 0) {
		return getVolByLen(actor);
	} else if (actor.pregData.sizeType === 1) {
		return getVolByWeight(actor);
	} else if (actor.pregData.sizeType === 2) {
		return getVolByRaw(actor);
	} else {
		return 0;
	}

	function getCurData(actor, age) {
		let i = 0;
		let min, max, ageMin, ageMax, rateMin, rateMax, one, rateOne, rate, cAge, cSize;
		let data = {};

		while (actor.pregData.fetusWeek[i + 1] < age && i < actor.pregData.fetusWeek.length - 1) {
			i++;
		}

		min = actor.pregData.fetusSize[i];
		max = actor.pregData.fetusSize[i + 1];
		ageMin = actor.pregData.fetusWeek[i];
		ageMax = actor.pregData.fetusWeek[i + 1];
		rateMin = actor.pregData.fetusRate[i];
		rateMax = actor.pregData.fetusRate[i + 1];

		cAge = age - ageMin;

		one = (max - min) / (ageMax - ageMin);
		rateOne = (rateMax - rateMin) / (ageMax - ageMin);

		rate = rateMin + (rateOne * cAge);

		cSize = (min + (one * cAge));
		// console.log("min:"+min+"  max:"+max+"  ageMin:"+ageMin+"  ageMax:"+ageMax+"  one:"+one+"  rateOne:"+rateOne+"  cAge:"+cAge+"  rate:"+rate+"  cSize:"+cSize+"  final size:"+cSize*rate);

		data.size = cSize;
		data.rate = rate;

		return data; // cSize * rate;
		// maybe not very effective code, but simple and easy to debug. May be optimized more in future.
	}

	function getVolByLen(actor) {
		let phi = 1.618;
		let targetData, targetLen;
		let wombSize = 0;

		try {
			actor.womb.forEach(ft => {
				/* legacy block for debug only
				let gestationWeek = ft.age;
				let oldLen;
				let oldVol;
				if (gestationWeek <= 32) {
					oldLen = (0.00006396 * Math.pow(gestationWeek, 4)) -
						(0.005501 * Math.pow(gestationWeek, 3)) +
						(0.161 * Math.pow(gestationWeek, 2)) -
						(0.76 * gestationWeek) +
						0.208;
				} else if (gestationWeek <= 106) {
					oldLen = (-0.0000004675 * Math.pow(gestationWeek, 4)) +
						(0.0001905 * Math.pow(gestationWeek, 3)) -
						(0.029 * Math.pow(gestationWeek, 2)) +
						(2.132 * gestationWeek) -
						16.575;
				} else {
					oldLen = (-0.00003266 * Math.pow(gestationWeek,2)) +
						(0.076 * gestationWeek) +
						43.843;
				}
				*/

				targetData = getCurData(actor, ft.age);
				targetLen = targetData.size * targetData.rate;

				ft.volume = ((4 / 3) * (Math.PI) * (phi / 2) * (Math.pow((targetLen / 2), 3)));
				// wombSize += ft.volume;
				wombSize += ft.genetics.geneticQuirks.polyhydramnios === 2 ? ft.volume * 2 : ft.volume;

				// oldVol = ((4 / 3) * (Math.PI) * (phi / 2) * (Math.pow((oldLen / 2), 3))); // for debug

				// console.log("fetus.age:" + ft.age + "  oldLen:"+oldLen+"  targetLen:"+targetLen+"  ft.volume:"+ft.volume+ "  old volume:"+oldVol );
				/*
					I found, that previous targetLen calculation not exactly accurate if compared to the actual medical data chart for fetal length. It's been rough approximation based only on pregnancy week (giving smaller fetus size then it should in most cases). So I need all this debug code to compare data and verify calculations. After final tweaking I will remove or comment out legacy code. Please not touch this before it.
					Pregmodfan.
				*/
			});
		} catch (err) {
			WombInit(actor);
			alert("WombGetVolume warning - " + actor.slaveName + " " + err);
		}
		if (wombSize < 0) { // catch for strange cases, to avoid messing with outside code.
			wombSize = 0;
		}

		return wombSize;
	}


	function getVolByWeight(actor) {
		let targetData;
		let wombSize = 0;

		actor.womb.forEach(ft => {
			targetData = getCurData(actor, ft.age);

			// wombSize += targetData.size * targetData.rate;
			wombSize += ft.genetics.geneticQuirks.polyhydramnios === 2 ? targetData.size * targetData.rate * 2 : targetData.size * targetData.rate;
		});

		if (wombSize < 0) { // catch for strange cases, to avoid messing with outside code.
			wombSize = 0;
		}

		return wombSize;
	}


	function getVolByRaw(actor) {
		let targetData;
		let wombSize = 0;

		actor.womb.forEach(ft => {
			targetData = getCurData(actor, ft.age);

			// wombSize += targetData.size;
			wombSize += ft.genetics.geneticQuirks.polyhydramnios === 2 ? targetData.size * 2 : targetData.size;
		});

		if (wombSize < 0) { // catch for strange cases, to avoid messing with outside code.
			wombSize = 0;
		}

		return wombSize;
	}
};

globalThis.FetusGetPrediction = function(actor, age) {
	let vol = 0.1;
	if (actor.pregData.sizeType === 0) {
		vol = getVolByLen(actor, age);
	} else if (actor.pregData.sizeType === 1) {
		vol = getVolByWeight(actor, age);
	} else if (actor.pregData.sizeType === 2) {
		vol = getVolByRaw(actor);
	}

	if (vol === 0) {
		vol = 0.1;
	}

	return vol;

	function getCurData(actor, age) {
		let i = 0;
		let min, max, ageMin, ageMax, rateMin, rateMax, one, rateOne, rate, cAge, cSize;
		let data = {};

		while (actor.pregData.fetusWeek[i + 1] < age && i < actor.pregData.fetusWeek.length - 1) {
			i++;
		}

		min = actor.pregData.fetusSize[i];
		max = actor.pregData.fetusSize[i + 1];
		ageMin = actor.pregData.fetusWeek[i];
		ageMax = actor.pregData.fetusWeek[i + 1];
		rateMin = actor.pregData.fetusRate[i];
		rateMax = actor.pregData.fetusRate[i + 1];

		cAge = age - ageMin;

		one = (max - min) / (ageMax - ageMin);
		rateOne = (rateMax - rateMin) / (ageMax - ageMin);

		rate = rateMin + (rateOne * cAge);

		cSize = (min + (one * cAge));
		// console.log("min:"+min+"  max:"+max+"  ageMin:"+ageMin+"  ageMax:"+ageMax+"  one:"+one+"  rateOne:"+rateOne+"  cAge:"+cAge+"  rate:"+rate+"  cSize:"+cSize+"  final size:"+cSize*rate);

		data.size = cSize;
		data.rate = rate;

		return data; // cSize * rate;
		// maybe not very effective code, but simple and easy to debug. May be optimized more in future.
	}

	function getVolByLen(actor, age) {
		let phi = 1.618;
		let targetData, targetLen;
		let volume = 0;

		targetData = getCurData(actor, age);
		targetLen = targetData.size * targetData.rate;

		volume = ((4 / 3) * (Math.PI) * (phi / 2) * (Math.pow((targetLen / 2), 3)));
		/*
		if (targetData.genetics.geneticQuirks.polyhydramnios === 2) {
			volume *= 2;
		}
		*/

		if (volume < 0) { // catch for strange cases, to avoid messing with outside code.
			volume = 0;
		}

		return volume;
	}

	function getVolByWeight(actor, age) {
		let targetData;
		let volume = 0;

		targetData = getCurData(actor, age);

		volume += targetData.size * targetData.rate;
		/*
		if (targetData.genetics.geneticQuirks.polyhydramnios === 2) {
			volume *= 2;
		}
		*/

		if (volume < 0) { // catch for strange cases, to avoid messing with outside code.
			volume = 0;
		}

		return volume;
	}

	function getVolByRaw(actor) {
		let targetData;
		let volume = 0;

		targetData = getCurData(actor, age);

		volume += targetData.size;
		/*
		if (targetData.genetics.geneticQuirks.polyhydramnios === 2) {
			volume *= 2;
		}
		*/

		if (volume < 0) { // catch for strange cases, to avoid messing with outside code.
			volume = 0;
		}

		return volume;
	}
};

globalThis.WombUpdatePregVars = function(actor) {
	WombSort(actor);
	if (actor.womb.length > 0) {
		if (actor.preg > 0 && actor.womb[0].age > 0) {
			actor.preg = actor.womb[0].age;
		}
		actor.pregType = actor.womb.length;
		actor.bellyPreg = WombGetVolume(actor);

		if (actor.womb[0].age >= 10 && actor.pregKnown === 0) {
			actor.pregKnown = 1;
		}
	} else {
		actor.pregType = 0;
		WombNormalizePreg(actor);
	}
};

globalThis.WombMinPreg = function(actor) {
	WombSort(actor);
	if (actor.womb.length > 0) {
		return actor.womb[actor.womb.length - 1].age;
	} else {
		return 0;
	}
};

globalThis.WombMaxPreg = function(actor) {
	WombSort(actor);
	if (actor.womb.length > 0) {
		return actor.womb[0].age;
	} else {
		return 0;
	}
};

globalThis.WombNormalizePreg = function(actor) {
	// console.log("New actor: " + actor.slaveName + " ===============" + actor.name);
	WombInit(actor);

	// this is broodmother on hold.
	if (actor.womb.length === 0 && actor.broodmother >= 1) {
		actor.pregType = 0;
		actor.pregKnown = 0;

		// to avoid legacy code conflicts - broodmother on hold
		// can't be impregnated, but she is not on normal contraceptives.
		// So we set this for special case.
		if (actor.preg >= 0) {
			actor.preg = 0.1;
		}

		if (actor.pregSource !== 0) {
			actor.pregSource = 0;
		}

		if (actor.pregWeek > 0) {
			actor.pregWeek = 0;
		}

		actor.broodmotherCountDown = 0;
	}

	if (actor.womb.length > 0) {
		let max = WombMaxPreg(actor);
		// console.log("max: " + max);
		// console.log(".preg: "+ actor.preg);
		if (actor.pregWeek < 1) {
			actor.pregWeek = 1;
		}

		if (max < actor.preg) {
			WombProgress(actor, actor.preg - max, actor.preg - max);
			// console.log("progress in womb");
		} else if (max > actor.preg) {
			actor.preg = max;
			// console.log("advancing .preg");
		}

		if (actor.womb[0].age >= 10 && actor.pregKnown === 0) {
			actor.pregKnown = 1;
		}

		actor.pregType = actor.womb.length;
		actor.pregSource = actor.womb[0].fatherID;
	} else if (actor.womb.length === 0 && actor.broodmother < 1) {
		// not broodmother
		// console.log("preg fixing");
		actor.pregType = 0;
		actor.pregKnown = 0;

		if (actor.preg > 0) {
			actor.preg = 0;
		}

		if (actor.pregSource !== 0) {
			actor.pregSource = 0;
		}

		// We can't properly set postpartum here,
		// but can normalize obvious error with forgotten property.
		if (actor.pregWeek > 0) {
			actor.pregWeek = 0;
		}
	}
	actor.bellyPreg = WombGetVolume(actor);
};

globalThis.WombZeroID = function(actor, id) {
	WombInit(actor);
	actor.womb
		.filter(ft => ft.fatherID === id)
		.forEach(ft => ft.fatherID = 0);
	WombNormalizePreg(actor);
};

globalThis.WombChangeID = function(actor, fromID, toID) {
	WombInit(actor);
	actor.womb
		.filter(ft => ft.fatherID === fromID)
		.forEach(ft => ft.fatherID = toID);
	WombNormalizePreg(actor);
};

globalThis.WombChangeGeneID = function(actor, fromID, toID) {
	WombInit(actor);
	actor.womb
		.filter(ft => ft.genetics.father === fromID)
		.forEach(ft => ft.genetics.father = toID);
	actor.womb
		.filter(ft => ft.genetics.mother === fromID)
		.forEach(ft => ft.genetics.mother = toID);
	WombNormalizePreg(actor);
};

/* Sorts the womb object by age with oldest and thus soonest to be born, first. This will be needed in the future once individual fertilization is a possibility.*/
globalThis.WombSort = function(actor) {
	actor.womb.sort((a, b) => {
		return b.age - a.age;
	});
};

/** Split fetuses into identical twins based on chance
 * @param {FC.HumanState} actor
 * @param {number} chance
 */
globalThis.fetalSplit = function(actor, chance) {
	for (const s of actor.womb) {
		if (jsRandom(1, chance) >= chance) {
			// if this fetus is not already an identical, generate a new twin ID before cloning it
			if (s.twinID === "" || s.twinID === undefined) {
				s.twinID = generateNewID();
			}

			// clone the fetus with a new fetus ID
			const nft = clone(s);
			nft.ID = generateNewID();
			nft.reserve = ""; // new fetus does not inherit reserve status

			actor.womb.push(nft);
		}
	}
	WombNormalizePreg(actor);
};

// safe alternative to .womb.length.
globalThis.WombFetusCount = function(actor) {
	WombInit(actor);
	return actor.womb.length;
};

// give reference to fetus object, but not remove fetus, use for manipulation in the womb.
globalThis.WombGetFetus = function(actor, fetusNum) {
	WombInit(actor);
	if (actor.womb.length >= fetusNum) {
		return actor.womb[fetusNum];
	} else {
		return null;
	}
};

// give reference to fetus object, and remove it form the womb.
globalThis.WombRemoveFetus = function(actor, fetusNum) {
	WombInit(actor);
	if (actor.womb.length >= fetusNum) {
		let ft = actor.womb[fetusNum];
		actor.womb.splice(fetusNum, 1);
		WombUpdatePregVars(actor);
		return ft;
	} else {
		return null;
	}
};

/* to add fetus object in the womb. Be warned - you can add one single fetus to many wombs, or even add it many times to one womb. It will not show error, but behavior becomes strange, as fetus object will be the same - it's reference, not full copies. If this is not desired - use clone() on fetus before adding.*/
globalThis.WombAddFetus = function(actor, fetus) {
	WombInit(actor);
	actor.womb.push(fetus);
	WombSort(actor);
};

// change property for all fetuses. Like fetus.age = X.
globalThis.WombChangeFetus = function(actor, propName, newValue) {
	WombInit(actor);
	actor.womb.forEach(ft => ft[propName] = newValue);
};

// change genetic property of all fetuses. Like fetus.genetic.intelligence = X
globalThis.WombChangeGene = function(actor, geneName, newValue) {
	WombInit(actor);
	actor.womb.forEach(ft => ft.genetics[geneName] = newValue);
};

// change genetic property of all fetuses based on race
globalThis.WombFatherRace = function(actor, raceName) {
	let skinColor = randomRaceSkin(raceName);
	let eyeColor = randomRaceEye(raceName);
	let hairColor = randomRaceHair(raceName);
	WombChangeGene(actor, "race", raceName);
	WombChangeGene(actor, "skin", skinColor);
	WombChangeGene(actor, "eyeColor", eyeColor);
	WombChangeGene(actor, "hColor", hairColor);
};

// replaces untraceable fatherIDs with missingParentID. Required for concurrent pregnancy to differentiate between siblings.
globalThis.MissingParentIDCorrection = function(actor) {
	WombInit(actor);
	actor.womb
		.filter(ft => (ft.genetics.father === 0 || (ft.genetics.father < -1 && ft.genetics.father >= -20 && ft.genetics.father !== -3)))
		.forEach(ft => ft.genetics.father = V.missingParentID);
	V.missingParentID--;
};

globalThis.WombCleanYYFetuses = function(actor) {
	let reserved = [];

	let i = actor.womb.length - 1;
	let ft;

	while (i >= 0) {
		ft = actor.womb[i];

		if (ft.genetics.gender === "YY") {
			reserved.push(ft);
			actor.womb.splice(i, 1);
		}

		i--;
	}
	WombUpdatePregVars(actor);

	return reserved;
};

globalThis.FetusGlobalReserveCount = function(reserveType) {
	let cnt = 0;

	if (typeof reserveType !== 'string') {
		return 0;
	}

	V.slaves.forEach(function(slave) {
		slave.womb.forEach(function(ft) {
			if (ft.reserve === reserveType) {
				cnt++;
			}
		});
	});

	V.PC.womb.forEach(function(ft) {
		if (ft.reserve === reserveType) {
			cnt++;
		}
	});

	return cnt;
};

globalThis.WombSetGenericReserve = function(actor, type, count) {
	// console.log ("actor: " + actor + "  type: " + type + "  typeof: " + typeof type + "  count: " + count);
	actor.womb.forEach(function(ft) {
		// console.log ("  type: " + ft.reserve + "  typeof: " + typeof ft.reserve);
		if ((ft.reserve === "" || ft.reserve === type) && count > 0) {
			// console.log ("!trigger");
			ft.reserve = type;
			count--;
		}
	});
};

globalThis.WombAddToGenericReserve = function(actor, type, count) {
	WombSetGenericReserve(actor, type, (WombReserveCount(actor, type) + count));
};

globalThis.WombChangeReserveType = function(actor, oldType, newType) {
	let count = 0;

	actor.womb.forEach(function(ft) {
		if (ft.reserve === oldType) {
			ft.reserve = newType;
			count++;
		}
	});

	return count;
};

globalThis.WombCleanGenericReserve = function(actor, type, count) {
	actor.womb.forEach(function(ft) {
		if (ft.reserve === type && count > 0) {
			ft.reserve = "";
			count--;
		}
	});
};

globalThis.WombReserveCount = function(actor, type) {
	let cnt = 0;

	actor.womb.forEach(function(ft) {
		if (ft.reserve === type) /* the lazy equality will catch "" case */ {
			cnt++;
		}
	});

	return cnt;
};

globalThis.WombGetReservedFetuses = function(actor, type) {
	let reserved = [];

	actor.womb.forEach(function(ft) {
		if (ft.reserve === type) {
			reserved.push(ft);
		}
	});

	return reserved;
};

globalThis.WombRemoveReservedFetuses = function(actor, type) {
	let reserved = [];

	let i = actor.womb.length - 1;
	let ft;

	while (i >= 0) {
		ft = actor.womb[i];

		if (ft.reserve === type) {
			reserved.push(ft);
			actor.womb.splice(i, 1);
		}

		i--;
	}

	return reserved;
};

globalThis.WombCleanAllReserve = function(actor) {
	actor.womb.forEach(function(ft) {
		ft.reserve = "";
	});
};

/*
Function return object with data about litters in actor womb. This data can be used for descriptions of pregnancy with complicated structure. What it contain:

data.litters.length = summary count of separate litters in the womb.
data.litters[x] = age (.realAge) of litter "x".
data.countLitter[x] = count of fetuses in "x" litter.

data.litterData[x] = contain array with actual fetuses that belong to a litter "x". Can be used to check anything related to fetus. (This is not copy, but reference to actual fetuses, so be careful with changes of this array).

Sample of usage in SugarScript:
---
<<set _wd = WombGetLittersData(_slave)>>
$He is _wd.litters[0] weeks pregnant with $his first set of _wd.countLitter[0] children<<if _wd.litters > 1>>, _wd.litters[1] weeks along with $his second set<</if>><<if _wd.litters > 2>>, _wd.litters[2] and _wd.litters[2] weeks along with $his third<</if>>.
In summary $he carry _wd.litters.length separate sets of children. $His most progressed fetus of second pregnancy is already reached _wd.litterData[1][0].age biological week of gestation.
---
*/
globalThis.WombGetLittersData = function(actor) {
	let data = {};
	let unicLiters = []; // array with realAges of separate litters.
	let countLitter = [];
	let litterData = [];
	let tmp;

	// in first place we need to know how many litters here (Assuming that unique litter is have similar .realAge). Also we will know their ages.
	actor.womb.forEach(function(ft) {
		if (!unicLiters.includes(Math.ceil(ft.realAge))) {
			unicLiters.push(Math.ceil(ft.realAge));
		}
	});

	// now we should find and store separate litters data (count of fetuses):
	unicLiters.forEach(function(litter) {
		tmp = actor.womb.filter(ft => Math.ceil(ft.realAge) === litter);
		countLitter.push(tmp.length);
		litterData.push(tmp);
	});

	data.litters = unicLiters;
	data.countLitter = countLitter;
	data.litterData = litterData;

	return data;
};

// simple function used for splitting actual size from total size due to polyhydramnios.
globalThis.WombGetFetalSizeSum = function(actor) {
	let sum = 0;

	actor.womb.forEach((ft) => sum += ft.volume);

	return sum;
};