code:
package com.lilithsthrone.game.dialogue.utils;

import java.time.Month;
import java.time.format.TextStyle;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import com.lilithsthrone.controller.eventListeners.tooltips.TooltipInformationEventListener;
import com.lilithsthrone.game.PropertyValue;
import com.lilithsthrone.game.character.attributes.Attribute;
import com.lilithsthrone.game.character.body.Arm;
import com.lilithsthrone.game.character.body.Breast;
import com.lilithsthrone.game.character.body.BreastCrotch;
import com.lilithsthrone.game.character.body.Covering;
import com.lilithsthrone.game.character.body.Eye;
import com.lilithsthrone.game.character.body.Horn;
import com.lilithsthrone.game.character.body.Tail;
import com.lilithsthrone.game.character.body.Testicle;
import com.lilithsthrone.game.character.body.types.AbstractArmType;
import com.lilithsthrone.game.character.body.types.AbstractAssType;
import com.lilithsthrone.game.character.body.types.AbstractBreastType;
import com.lilithsthrone.game.character.body.types.AbstractEarType;
import com.lilithsthrone.game.character.body.types.AbstractHornType;
import com.lilithsthrone.game.character.body.types.AbstractLegType;
import com.lilithsthrone.game.character.body.types.AntennaType;
import com.lilithsthrone.game.character.body.types.ArmType;
import com.lilithsthrone.game.character.body.types.AssType;
import com.lilithsthrone.game.character.body.types.BodyCoveringType;
import com.lilithsthrone.game.character.body.types.BreastType;
import com.lilithsthrone.game.character.body.types.EarType;
import com.lilithsthrone.game.character.body.types.EyeType;
import com.lilithsthrone.game.character.body.types.FaceType;
import com.lilithsthrone.game.character.body.types.FootStructure;
import com.lilithsthrone.game.character.body.types.HairType;
import com.lilithsthrone.game.character.body.types.HornType;
import com.lilithsthrone.game.character.body.types.LegType;
import com.lilithsthrone.game.character.body.types.PenisType;
import com.lilithsthrone.game.character.body.types.SkinType;
import com.lilithsthrone.game.character.body.types.TailType;
import com.lilithsthrone.game.character.body.types.VaginaType;
import com.lilithsthrone.game.character.body.types.WingType;
import com.lilithsthrone.game.character.body.valueEnums.AreolaeSize;
import com.lilithsthrone.game.character.body.valueEnums.AssSize;
import com.lilithsthrone.game.character.body.valueEnums.BodyHair;
import com.lilithsthrone.game.character.body.valueEnums.BodySize;
import com.lilithsthrone.game.character.body.valueEnums.BreastShape;
import com.lilithsthrone.game.character.body.valueEnums.Capacity;
import com.lilithsthrone.game.character.body.valueEnums.ClitorisSize;
import com.lilithsthrone.game.character.body.valueEnums.CoveringModifier;
import com.lilithsthrone.game.character.body.valueEnums.CoveringPattern;
import com.lilithsthrone.game.character.body.valueEnums.CumProduction;
import com.lilithsthrone.game.character.body.valueEnums.CupSize;
import com.lilithsthrone.game.character.body.valueEnums.EyeShape;
import com.lilithsthrone.game.character.body.valueEnums.Femininity;
import com.lilithsthrone.game.character.body.valueEnums.FluidFlavour;
import com.lilithsthrone.game.character.body.valueEnums.FluidRegeneration;
import com.lilithsthrone.game.character.body.valueEnums.GenitalArrangement;
import com.lilithsthrone.game.character.body.valueEnums.HairLength;
import com.lilithsthrone.game.character.body.valueEnums.HairStyle;
import com.lilithsthrone.game.character.body.valueEnums.HipSize;
import com.lilithsthrone.game.character.body.valueEnums.HornLength;
import com.lilithsthrone.game.character.body.valueEnums.LabiaSize;
import com.lilithsthrone.game.character.body.valueEnums.Lactation;
import com.lilithsthrone.game.character.body.valueEnums.LegConfiguration;
import com.lilithsthrone.game.character.body.valueEnums.LipSize;
import com.lilithsthrone.game.character.body.valueEnums.Muscle;
import com.lilithsthrone.game.character.body.valueEnums.NippleShape;
import com.lilithsthrone.game.character.body.valueEnums.NippleSize;
import com.lilithsthrone.game.character.body.valueEnums.OrificeElasticity;
import com.lilithsthrone.game.character.body.valueEnums.OrificeModifier;
import com.lilithsthrone.game.character.body.valueEnums.OrificePlasticity;
import com.lilithsthrone.game.character.body.valueEnums.PenetrationModifier;
import com.lilithsthrone.game.character.body.valueEnums.PenisGirth;
import com.lilithsthrone.game.character.body.valueEnums.PenisSize;
import com.lilithsthrone.game.character.body.valueEnums.PiercingType;
import com.lilithsthrone.game.character.body.valueEnums.TesticleSize;
import com.lilithsthrone.game.character.body.valueEnums.TongueLength;
import com.lilithsthrone.game.character.body.valueEnums.TongueModifier;
import com.lilithsthrone.game.character.body.valueEnums.Wetness;
import com.lilithsthrone.game.character.body.valueEnums.WingSize;
import com.lilithsthrone.game.character.effects.StatusEffect;
import com.lilithsthrone.game.character.markings.AbstractTattooType;
import com.lilithsthrone.game.character.markings.Tattoo;
import com.lilithsthrone.game.character.markings.TattooCountType;
import com.lilithsthrone.game.character.markings.TattooCounter;
import com.lilithsthrone.game.character.markings.TattooCounterType;
import com.lilithsthrone.game.character.markings.TattooType;
import com.lilithsthrone.game.character.markings.TattooWriting;
import com.lilithsthrone.game.character.markings.TattooWritingStyle;
import com.lilithsthrone.game.character.persona.PersonalityTrait;
import com.lilithsthrone.game.character.persona.SexualOrientation;
import com.lilithsthrone.game.character.race.Race;
import com.lilithsthrone.game.character.race.RacialBody;
import com.lilithsthrone.game.dialogue.places.dominion.shoppingArcade.SuccubisSecrets;
import com.lilithsthrone.game.inventory.InventorySlot;
import com.lilithsthrone.game.sex.SexAreaOrifice;
import com.lilithsthrone.game.sex.SexAreaPenetration;
import com.lilithsthrone.game.sex.SexParticipantType;
import com.lilithsthrone.game.sex.SexType;
import com.lilithsthrone.main.Main;
import com.lilithsthrone.rendering.RenderingEngine;
import com.lilithsthrone.rendering.SVGImages;
import com.lilithsthrone.utils.BaseColour;
import com.lilithsthrone.utils.Colour;
import com.lilithsthrone.utils.Units;
import com.lilithsthrone.utils.Units.ValueType;
import com.lilithsthrone.utils.Util;

/**
 * @since 0.1.7?
 * @version 0.3.5
 * @author Innoxia
 */
public class CharacterModificationUtils {

	private static StringBuilder contentSB = new StringBuilder();

	public static final int FLUID_INCREMENT_SMALL = 5;
	public static final int FLUID_INCREMENT_AVERAGE = 50;
	public static final int FLUID_INCREMENT_LARGE = 500;

	public static final int FLUID_REGEN_INCREMENT_SMALL = 100;
	public static final int FLUID_REGEN_INCREMENT_AVERAGE = 1000;
	public static final int FLUID_REGEN_INCREMENT_LARGE = 10_000;

	public static Map<String, TooltipInformationEventListener> informationTooltips = new HashMap<>();

	public static String getInformationDiv(String id, TooltipInformationEventListener information) {
		informationTooltips.put(id, information);
		return "
"+SVGImages.SVG_IMAGE_PROVIDER.getInformationIcon()+"
";
	}

	public static String getStartDateDiv() {
		contentSB.setLength(0);

		contentSB.append(
				"
"
						+"
"
						+ "
"
							+"Start Date"
						+"
"
						+ "
"
							+ "Select the month in which the game starts."
						+ "
");

		for(Month month : Month.values()) {
			if(Main.game.getStartingDate().getMonth() == month) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(month.getDisplayName(TextStyle.FULL, Locale.ENGLISH))+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(month.getDisplayName(TextStyle.FULL, Locale.ENGLISH))+""
						+ "
");
			}
		}

		contentSB.append("

");

		return contentSB.toString();
	}

	// Basics:

	public static String getGenderChoiceDiv() {
		contentSB.setLength(0);

		contentSB.append("
"
						+ "
"
							+"Gender"
						+"
"
						+ "
"
							+ "Your gender is used to determine what genitals you start the game with."
						+ "
");

		if(Main.game.getPlayer().getGender().getGenderName().isHasVagina()) {
			contentSB.append(
					"
"
						+ "Male"
					+ "
"
					+ "
"
						+ "[style.boldFeminine(Female)]"
					+ "
");
		} else {
			contentSB.append(
					"
"
						+ "[style.colourMasculine(Male)]"
					+ "
"
					+ "
"
						+ "Female"
					+ "
");
		}

		contentSB.append("
");

		return contentSB.toString();
	}

	public static String getFemininityChoiceDiv() {
		contentSB.setLength(0);

		contentSB.append(
				"
"
						+ "
"
							+"Femininity"
						+"
"
						+ "
"
							+ "Femininity is a measure of how masculine or feminine your face and body are."
						+ "
");

		if(Main.game.getPlayer().getGender().getGenderName().isHasVagina()) {
			if(Main.game.getPlayer().getFemininity()==Femininity.ANDROGYNOUS) {
				contentSB.append(
						"
"
							+ "[style.colourAndrogynous(Androgynous)]"
						+ "
");
			} else {
				contentSB.append(
						"
"
							+ "Androgynous"
						+ "
");
			}
			if(BodyChanging.getTarget().getFemininity()==Femininity.FEMININE) {
				contentSB.append(
						"
"
							+ "[style.colourFeminine(Feminine)]"
						+ "
");
			} else {
				contentSB.append(
						"
"
							+ "Feminine"
						+ "
");
			}
			if(BodyChanging.getTarget().getFemininity()==Femininity.FEMININE_STRONG) {
				contentSB.append(
						"
"
							+ "[style.colourFeminineStrong(Very Feminine)]"
						+ "
");
			} else {
				contentSB.append(
						"
"
							+ "Very Feminine"
						+ "
");
			}

		} else {
			if(BodyChanging.getTarget().getFemininity()==Femininity.ANDROGYNOUS) {
				contentSB.append(
						"
"
							+ "[style.colourAndrogynous(Androgynous)]"
						+ "
");
			} else {
				contentSB.append(
						"
"
							+ "Androgynous"
						+ "
");
			}
			if(BodyChanging.getTarget().getFemininity()==Femininity.MASCULINE) {
				contentSB.append(
						"
"
							+ "[style.colourMasculine(Masculine)]"
						+ "
");
			} else {
				contentSB.append(
						"
"
							+ "Masculine"
						+ "
");
			}
			if(BodyChanging.getTarget().getFemininity()==Femininity.MASCULINE_STRONG) {
				contentSB.append(
						"
"
							+ "[style.colourMasculineStrong(Very Masculine)]"
						+ "
");
			} else {
				contentSB.append(
						"
"
							+ "Very Masculine"
						+ "
");
			}
		}

		contentSB.append("
");

		return contentSB.toString();
	}

	public static String getPersonalityChoiceDiv() {
		contentSB.setLength(0);

		contentSB.append(
				"
"
				+ "
"
						+ "
"
							+"Personality"
						+"
"
						+ "
"
							+ "Your personality will have a minor influence in some situations."
							+ " It will not lock out any options during the game, and is more for roleplaying purposes."
						+ "
");

		for(PersonalityTrait trait : PersonalityTrait.values()) {
			if(BodyChanging.getTarget().hasPersonalityTrait(trait)) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(trait.getName())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
								+ "[style.colourDisabled("+Util.capitaliseSentence(trait.getName())+")]"
						+ "
");
			}
		}

		contentSB.append("

");

		return contentSB.toString();
	}

	public static void performPlayerAgeCheck(int ageTarget) {
		int ageDiff = Math.abs(Main.game.getPlayer().getAgeValue()-ageTarget);

		if(ageDiff>0) {
			if(Main.game.getPlayer().getAgeValue()>ageTarget) {
				Main.game.getPlayer().setBirthday(Main.game.getPlayer().getBirthday().plusYears(ageDiff));

			} else if(Main.game.getPlayer().getAgeValue()<ageTarget) {
				Main.game.getPlayer().setBirthday(Main.game.getPlayer().getBirthday().minusYears(ageDiff));
			}
		}
	}

	public static String getBirthdayChoiceDiv() {
		contentSB.setLength(0);

		contentSB.append(
				"
"
						+ "
"
							+"Birthday"
						+"
"
						+ "
"
						+ "You were born on the "
						+Units.date(Main.game.getPlayer().getBirthday(), Units.DateType.LONG)+", making you "+Util.intToString(Main.game.getPlayer().getAgeValue())+" years old."
						+ "
");

			//copypasting this entire shit from body.java, surely there's a better method
			if(Main.game.getPlayer().getAppearsAsAgeValue()<=4) {
				contentSB.append(" You appear like a toddler");

			} else if(Main.game.getPlayer().getAppearsAsAgeValue()<=9) {
				contentSB.append(" You appear like a little [pc.girl]");

			} else if(Main.game.getPlayer().getAppearsAsAgeValue()<=13) {
				contentSB.append(" You appear like a preteen");

			} else if(Main.game.getPlayer().getAppearsAsAgeValue()<=16) {
				contentSB.append(" You appear like a early teens");

			} else if(Main.game.getPlayer().getAppearsAsAgeValue()<=18) {
				contentSB.append(" You appear like a teenager");

			} else if(Main.game.getPlayer().getAppearsAsAgeValue()<20) {
				contentSB.append(" You appear to be in your late teens");

			} else if(Main.game.getPlayer().getAppearsAsAgeValue()<=23) {
				contentSB.append(" You appear to be in your early twenties");

			} else if(Main.game.getPlayer().getAppearsAsAgeValue()<=27) {
				contentSB.append(" You appear to be in your mid-twenties");

			} else if(Main.game.getPlayer().getAppearsAsAgeValue()<30) {
				contentSB.append(" You appear to be in your late twenties");

			} else if(Main.game.getPlayer().getAppearsAsAgeValue()<=33) {
				contentSB.append(" You appear to be in your early thirties");

			} else if(Main.game.getPlayer().getAppearsAsAgeValue()<=37) {
				contentSB.append(" You appear to be in your mid-thirties");

			} else if(Main.game.getPlayer().getAppearsAsAgeValue()<40) {
				contentSB.append(" You appear to be in your late thirties");

			} else if(Main.game.getPlayer().getAppearsAsAgeValue()<=43) {
				contentSB.append(" You appear to be in your early forties");

			} else if(Main.game.getPlayer().getAppearsAsAgeValue()<=47) {
				contentSB.append(" You appear to be in your mid-forties");

			} else {
				contentSB.append(" You appear to be in your late forties");
			}
			contentSB.append(".
");

			contentSB.append("
");

				contentSB.append(applyDateWrapper("Day", "BIRTH_DAY", "", "", String.valueOf(Main.game.getPlayer().getBirthday().getDayOfMonth()), false, false));

				contentSB.append(applyDateWrapper("Month", "BIRTH_MONTH", "", "", Main.game.getPlayer().getBirthday().getMonth().getDisplayName(TextStyle.FULL, Locale.ENGLISH), false, false));

				contentSB.append(applyDateWrapper("Age", "AGE", "", "",
						String.valueOf(Main.game.getPlayer().getAgeValue()),
						Main.game.getPlayer().getAgeValue()<=5,
						Main.game.getPlayer().getAgeValue()>=50));
			contentSB.append("
");

		contentSB.append("
");

		return contentSB.toString();
	}

	private static String applyDateWrapper(String title, String id, String measurement, String measurementPlural, String value, boolean decreaseDisabled, boolean increaseDisabled) {
		return "
"
					+ "
"
						+ ""+title+""
					+ "
"
					+ "
"
						+ "
"
							+ (decreaseDisabled?"[style.boldDisabled(-1"+measurement+")]":"[style.boldBadMinor(-1"+measurement+")]")
						+ "
"
						+ "
"
							+ (decreaseDisabled?"[style.boldDisabled(-5"+measurementPlural+")]":"[style.boldBad(-5"+measurementPlural+")]")
						+ "
"
					+ "
"
					+ "
"
						+ value
					+ "
"
					+ "
"
						+ "
"
							+ (increaseDisabled?"[style.boldDisabled(+1"+measurement+")]":"[style.boldGoodMinor(+1"+measurement+")]")
						+ "
"
						+ "
"
							+ (increaseDisabled?"[style.boldDisabled(+5"+measurementPlural+")]":"[style.boldGood(+5"+measurementPlural+")]")
						+ "
"
					+ "
"
				+ "
";
	}

	public static String getOrientationChoiceDiv() {
		contentSB.setLength(0);

		contentSB.append(
				"
"
						+"
"
							+ "
"
								+"Sexual Orientation"
							+"
"
							+ "
"
								+ "Sexual orientation in Lilith's Throne is determined by your attraction towards femininity or masculinity."
								+ " Each orientation has a related status effect (hover over the icon in the left-hand panel to see the effects)."
							+ "
");

		for(SexualOrientation orientation : SexualOrientation.values()) {
			if(BodyChanging.getTarget().getSexualOrientation() == orientation) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(orientation.getName())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(orientation.getName())+""
						+ "
");
			}
		}

		contentSB.append("

");

		return contentSB.toString();
	}

	public static int[] soSilly = new int[] {0, 1, 2, 3, 4}; // Apparently just using normalValues.length isn't allowed (in MainController) :s
	public static int[] normalSexExperienceValues = new int[] {0, 5, 25, 50, 100};
	private static Colour[] sexColours = new Colour[] {Colour.GENERIC_EXCELLENT, Colour.BASE_PINK_LIGHT, Colour.BASE_PINK, Colour.BASE_PINK_DEEP, Colour.ATTRIBUTE_CORRUPTION};
	public static String[] feminineNames = new String[] {"Virgin", "Inexperienced", "Experienced", "Expert", "Slut"};
	public static String[] masculineNames = new String[] {"Virgin", "Inexperienced", "Experienced", "Expert", "Stud"};

//	public static String[] virginityLossesGynephilic = new String[] {"your girlfriend", "", "some girl in your apartment", "some girl in a club's restroom"};
//	public static String[] virginityLossesAmbiphilic = new String[] {"your girlfriend in her apartment", "your girlfriend in your apartment", "some girl in her apartment", "some girl in your apartment", "some girl in a club's restroom",
//			"your boyfriend in his apartment", "your boyfriend in your apartment", "some guy in his apartment", "some guy in your apartment", "some guy in a club's restroom"};
//	public static String[] virginityLossesAndrophilic = new String[] {"your boyfriend in his apartment", "your boyfriend in your apartment", "some guy in his apartment", "some guy in your apartment", "some guy in a club's restroom"};
//

	public static String getSexualExperienceDiv() {
		contentSB.setLength(0);

		contentSB.append("
"
							+ "

Sex Actions Performed

");

			contentSB.append(
							getSexExperienceEntry("HANDJOBS_GIVEN", "Handjobs Given",
									new SexType(SexParticipantType.NORMAL, SexAreaPenetration.FINGER, SexAreaOrifice.URETHRA_PENIS),
									normalSexExperienceValues, Main.game.getPlayer().isFeminine()?feminineNames:masculineNames)

							+ getSexExperienceEntry("FINGERINGS_GIVEN", "Fingerings Performed",
									new SexType(SexParticipantType.NORMAL, SexAreaPenetration.FINGER, SexAreaOrifice.VAGINA),
									normalSexExperienceValues, Main.game.getPlayer().isFeminine()?feminineNames:masculineNames)

							+ getSexExperienceEntry("BLOWJOBS_GIVEN", "Blowjobs Given",
									new SexType(SexParticipantType.NORMAL, SexAreaOrifice.MOUTH, SexAreaPenetration.PENIS),
									normalSexExperienceValues, Main.game.getPlayer().isFeminine()?feminineNames:masculineNames)

							+ getSexExperienceEntry("CUNNILINGUS_GIVEN", "Cunnilingus Performed",
									new SexType(SexParticipantType.NORMAL, SexAreaPenetration.TONGUE, SexAreaOrifice.VAGINA),
									normalSexExperienceValues, Main.game.getPlayer().isFeminine()?feminineNames:masculineNames)

							+ getSexExperienceEntry("VAGINAL_GIVEN", "Vaginal Sex Performed",
									new SexType(SexParticipantType.NORMAL, SexAreaPenetration.PENIS, SexAreaOrifice.VAGINA),
									normalSexExperienceValues, Main.game.getPlayer().isFeminine()?feminineNames:masculineNames)

							+ getSexExperienceEntry("ANAL_GIVEN", "Anal Sex Performed",
									new SexType(SexParticipantType.NORMAL, SexAreaPenetration.PENIS, SexAreaOrifice.ANUS),
									normalSexExperienceValues, Main.game.getPlayer().isFeminine()?feminineNames:masculineNames));
		contentSB.append("
");

		contentSB.append("
"
							+ "

Sex Actions Received

");
			contentSB.append(
							(Main.game.getPlayer().hasVagina()
									?""
									:getSexExperienceEntry("HANDJOBS_TAKEN", "Handjobs Received",
										new SexType(SexParticipantType.NORMAL, SexAreaOrifice.URETHRA_PENIS, SexAreaPenetration.FINGER),
										normalSexExperienceValues, Main.game.getPlayer().isFeminine()?feminineNames:masculineNames))

							+ (Main.game.getPlayer().hasVagina()
									?getSexExperienceEntry("FINGERINGS_TAKEN", "Fingerings Received",
										new SexType(SexParticipantType.NORMAL, SexAreaOrifice.VAGINA, SexAreaPenetration.FINGER),
										normalSexExperienceValues, Main.game.getPlayer().isFeminine()?feminineNames:masculineNames)
									:"")

							+ (Main.game.getPlayer().hasVagina()
									?""
									:getSexExperienceEntry("BLOWJOBS_TAKEN", "Blowjobs Received",
										new SexType(SexParticipantType.NORMAL, SexAreaPenetration.PENIS, SexAreaOrifice.MOUTH),
										normalSexExperienceValues, Main.game.getPlayer().isFeminine()?feminineNames:masculineNames))

							+ (Main.game.getPlayer().hasVagina()
									?getSexExperienceEntry("CUNNILINGUS_TAKEN", "Cunnilingus Received",
										new SexType(SexParticipantType.NORMAL, SexAreaOrifice.VAGINA, SexAreaPenetration.TONGUE),
										normalSexExperienceValues, Main.game.getPlayer().isFeminine()?feminineNames:masculineNames)
									:"")

							+ (Main.game.getPlayer().hasVagina()
									?getSexExperienceEntry("VAGINAL_TAKEN", "Vaginal Sex Received",
										new SexType(SexParticipantType.NORMAL, SexAreaOrifice.VAGINA, SexAreaPenetration.PENIS),
										normalSexExperienceValues, Main.game.getPlayer().isFeminine()?feminineNames:masculineNames)
									:"")

							+ getSexExperienceEntry("ANAL_TAKEN", "Anal Sex Received",
									new SexType(SexParticipantType.NORMAL, SexAreaOrifice.ANUS, SexAreaPenetration.PENIS),
									normalSexExperienceValues, Main.game.getPlayer().isFeminine()?feminineNames:masculineNames));
		contentSB.append("
");

		return contentSB.toString();
	}

	public static void setSexExperience(SexType type, int index) {
		int count = Main.game.getPlayer().getSexCount(type);

		for(int i =0; i<normalSexExperienceValues.length; i++) {
			if(count == normalSexExperienceValues[i]) {
				Main.game.getPlayer().incrementAttribute(Attribute.MAJOR_CORRUPTION, -i);
				break;
			}
		}

		Main.game.getPlayer().incrementAttribute(Attribute.MAJOR_CORRUPTION, index);

		Main.game.getPlayer().setSexCount(type, CharacterModificationUtils.normalSexExperienceValues[index]);

		if(index!=0) {
			if(Main.game.getPlayer().getSexualOrientation()==SexualOrientation.GYNEPHILIC
					|| (Main.game.getPlayer().getSexualOrientation()==SexualOrientation.AMBIPHILIC && !Main.game.getPlayer().isFeminine())) {
				Main.game.getPlayer().setVirginityLoss(type, "", "your girlfriend");
			} else {
				Main.game.getPlayer().setVirginityLoss(type, "", "your boyfriend");
			}
		} else {
			Main.game.getPlayer().resetVirginityLoss(type);
		}

		if(type.getPerformingSexArea()==SexAreaPenetration.PENIS) {
			if(index==0) {
				Main.game.getPlayer().setPenisVirgin(true);
			} else {
				Main.game.getPlayer().setPenisVirgin(false);
			}
		}

		if(type.getTargetedSexArea()==SexAreaPenetration.PENIS) {
			if(type.getPerformingSexArea().isOrifice()) {
				switch((SexAreaOrifice)type.getPerformingSexArea()) {
					case ANUS:
						if(index==0) {
							Main.game.getPlayer().setAssVirgin(true);
						} else {
							Main.game.getPlayer().setAssVirgin(false);
						}
						break;
					case ASS:
						break;
					case BREAST:
						break;
					case BREAST_CROTCH:
						break;
					case MOUTH:
						if(index==0) {
							Main.game.getPlayer().setFaceVirgin(true);
						} else {
							Main.game.getPlayer().setFaceVirgin(false);
						}
						break;
					case NIPPLE:
						break;
					case NIPPLE_CROTCH:
						break;
					case THIGHS:
						break;
					case URETHRA_PENIS:
						break;
					case URETHRA_VAGINA:
						break;
					case VAGINA:
						if(index==0) {
							Main.game.getPlayer().setVaginaVirgin(true);
						} else {
							Main.game.getPlayer().setVaginaVirgin(false);
						}
						break;
				}
			}
		}
	}

	private static String getSexExperienceEntry(String id, String title, SexType associatedSexType, int[] values, String[] names) {
		int index = 0;
		for(int i=0; i<5; i++ ) {
			if(values[i] == Main.game.getPlayer().getSexCount(associatedSexType)) {
				index = i;
				break;
			}
		}

		return "
"
					+ "
"
						+ title+": "+names[index]+""
					+ "
"
					+ "
"
						+ "
"+values[0]+"
"
						+ "
"+values[1]+"
"
						+ "
"+values[2]+"
"
						+ "
"+values[3]+"
"
						+ "
"+values[4]+"
"
					+ "
"
						//TODO
//					+ "
"
//						+ "Virginity lost: "
//						+ (Main.game.getPlayer().getVirginityLoss(associatedSexType)==null?"[style.boldDisabled(N/A)]":Main.game.getPlayer().getVirginityLoss(associatedSexType))
//					+ "
"
				+ "
";
	}

	// Advanced:

	private static String applyWrapper(String title, String description, String id, String input, boolean halfWidth) {
		if(halfWidth) {
			return "
"
						+ getInformationDiv(id, new TooltipInformationEventListener().setInformation(title, description))
						+ "
"
							+ ""+title+""
						+"
"
						+ input
					+ "
";

		} else {
			return "
"
						+ getInformationDiv(id, new TooltipInformationEventListener().setInformation(title, description))
//						+"
"
//							+ "
"
//								+ "" +title+""
//							+"
"
//						+ "
"
//						+ "
"
//							+ input
//						+ "
"
						+ "
"
							+ ""+title+""
						+"
"
						+ input
					+ "
";
		}
	}

	private static String applyFullVariableWrapper(String title, String description, String id, String minorStep, String majorStep, String value, boolean decreaseDisabled, boolean increaseDisabled) {
			return "
"
						+ "
"
							+ getInformationDiv(id, new TooltipInformationEventListener().setInformation(title, description))
							+ ""+title+""
						+"
"
						+ "
"
							+ "
"
								+ (decreaseDisabled?"[style.boldDisabled(-"+majorStep+")]":"[style.boldBad(-"+majorStep+")]")
							+ "
"
							+ "
"
								+ (decreaseDisabled?"[style.boldDisabled(-"+minorStep+")]":"[style.boldBadMinor(-"+minorStep+")]")
							+ "
"
						+ "
"
						+ "
"
							+ value
						+ "
"
						+ "
"
							+ "
"
								+ (increaseDisabled?"[style.boldDisabled(+"+minorStep+")]":"[style.boldGoodMinor(+"+minorStep+")]")
							+ "
"
							+ "
"
								+ (increaseDisabled?"[style.boldDisabled(+"+majorStep+")]":"[style.boldGood(+"+majorStep+")]")
							+ "
"
						+ "
"
					+ "
";
	}

	private static String applyFullVariableWrapperSizes(String title, String description, String id, double value, boolean decreaseDisabled, boolean increaseDisabled) {
		return applyFullVariableWrapper(title, description, id, Units.size(1), Units.size(5),
				Units.size(value, Units.ValueType.PRECISE, Units.UnitType.SHORT),decreaseDisabled, increaseDisabled);
	}

	private static String applyVariableWrapperFluids(String title, String description, String id, String value, boolean decreaseDisabled, boolean increaseDisabled, int incrementSmall, int incrementAverage, int incrementLarge) {
		return "
"
					+ "
"
						+ getInformationDiv(id, new TooltipInformationEventListener().setInformation(title, description))
						+ ""+title+""
					+"
"
					+ "
"
						+ "
"
							+ "
"
								+ (decreaseDisabled?"[style.boldDisabled("+Units.fluid(-incrementSmall)+")]":"[style.boldBadMinor("+Units.fluid(-incrementSmall)+")]")
							+ "
"
							+ "
"
								+ (decreaseDisabled?"[style.boldDisabled("+Units.fluid(-incrementAverage)+")]":"[style.boldBad("+Units.fluid(-incrementAverage)+")]")
							+ "
"
							+ "
"
								+ (decreaseDisabled?"[style.boldDisabled("+Units.fluid(-incrementLarge)+")]":"[style.boldBad("+Units.fluid(-incrementLarge)+")]")
							+ "
"
						+ "
"
						+ "
"
							+ value
						+ "
"
						+ "
"
							+ "
"
								+ (increaseDisabled?"[style.boldDisabled(+"+Units.fluid(incrementSmall)+")]":"[style.boldGoodMinor(+"+Units.fluid(incrementSmall)+")]")
							+ "
"
							+ "
"
								+ (increaseDisabled?"[style.boldDisabled(+"+Units.fluid(incrementAverage)+")]":"[style.boldGood(+"+Units.fluid(incrementAverage)+")]")
							+ "
"
							+ "
"
								+ (increaseDisabled?"[style.boldDisabled(+"+Units.fluid(incrementLarge)+")]":"[style.boldGood(+"+Units.fluid(incrementLarge)+")]")
							+ "
"
						+ "
"
					+ "
"
				+ "
";
	}

	public static String getAgeAppearanceChoiceDiv() {
		return applyFullVariableWrapper(
				"Age Appearance",
				UtilText.parse(BodyChanging.getTarget(), "Change how old [npc.name] [npc.verb(appear)] to be. [npc.She] [npc.is] limited to looking as young as 18, or up to ten years older than [npc.her] real age."
						+ "
This is purely a cosmetic change, and doesn't affect any in-game choices."),
				"AGE_APPEARANCE",
				"1",
				"5",
				String.valueOf(BodyChanging.getTarget().getAppearsAsAgeValue()),
				BodyChanging.getTarget().getAppearsAsAgeValue()<=18,
				BodyChanging.getTarget().getAppearsAsAgeValue()>=(BodyChanging.getTarget().getAgeValue()+10))

				+ applyWrapper("Birthday",
						UtilText.parse(BodyChanging.getTarget(), "[npc.NamePos] birthday can not ever be changed, but by transforming [npc.her] body, [npc.she] may appear to be younger or older than [npc.she] really [npc.is]."),
						"BIRTHDAY",
						"
"
							+ BodyChanging.getTarget().getBirthdayString()
								+ UtilText.parse(BodyChanging.getTarget(),
									"
Making [npc.namePos] real age: "+BodyChanging.getTarget().getAgeValue()+"")
						+"
",
						true);
	}

	public static String getHeightChoiceDiv() {
		return applyFullVariableWrapperSizes("Height",
				UtilText.parse(BodyChanging.getTarget(), "Change how tall [npc.name] [npc.is]."
						+ "
This affects some minor descriptions and is also used for determining if a sex scene is categorised as 'size-difference' or not."),
				"HEIGHT",
				BodyChanging.getTarget().getHeightValue(),
				BodyChanging.getTarget().getHeightValue()<=BodyChanging.getTarget().getMinimumHeight(),
				BodyChanging.getTarget().getHeightValue()>=BodyChanging.getTarget().getMaximumHeight());
	}

	public static String getSelfTransformFemininityChoiceDiv() {
		return applyFullVariableWrapper(
				"Femininity",
				UtilText.parse(BodyChanging.getTarget(), "Change how feminine or masculine [npc.namePos] body is."
						+ "
This affects speech colour text, determines whether clothing is too feminine or masculine for [npc.herHim] to wear, and is also used for determining sexual attraction (based on others' orientation)."),
				"FEMININITY",
				"",
				"",
				BodyChanging.getTarget().getFemininityValue()
					+"
"+Util.capitaliseSentence(BodyChanging.getTarget().getFemininity().getName(false))+"",
				BodyChanging.getTarget().getFemininityValue()<=0,
				BodyChanging.getTarget().getFemininityValue()>=100);
	}

	public static String getSelfTransformTailChoiceDiv(List<Race> availableRaces, boolean removeNone) {
		contentSB.setLength(0);

		for(TailType tail : TailType.values()) {
			if((tail.getRace() !=null && availableRaces.contains(tail.getRace()))
					|| (!removeNone && tail==TailType.NONE)) {
				if(BodyChanging.getTarget().getTailType()==TailType.FOX_MORPH_MAGIC && tail!=TailType.FOX_MORPH_MAGIC) {
					continue;
				}
				if(BodyChanging.getTarget().getTailType()!=TailType.FOX_MORPH_MAGIC && tail==TailType.FOX_MORPH_MAGIC) {
					continue;
				}
				Colour c = Colour.TEXT_GREY;

				if(tail.getRace() != null) {
					c = tail.getRace().getColour();
				}

				if(BodyChanging.getTarget().getTailType() == tail) {
					contentSB.append(
							"
"
								+ ""+Util.capitaliseSentence(tail.getTransformName())+(tail.isPrehensile()?"*":"")+""
							+ "
");

				} else {
					contentSB.append(
							"
"
								+ ""+Util.capitaliseSentence(tail.getTransformName())+(tail.isPrehensile()?"*":"")+""
							+ "
");
				}
			}
		}

		return applyWrapper("Tail",
				UtilText.parse(BodyChanging.getTarget(), "Change [npc.namePos] tail type."
						+ "
Some types of tail are prehensile (marked by an asterisk), and can be used as penetrative objects in sex scenes."
						+ " (If the tail is furry, it is subject to the 'furry tail penetration' content option.)"),
				"TAIL_TYPE",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformTailCountDiv() {
		contentSB.setLength(0);

		for(int i=1; i <= Tail.MAXIMUM_COUNT; i++) {
			if(BodyChanging.getTarget().getTailCount() == i) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(Util.intToString(i))+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(Util.intToString(i))+""
						+ "
");
			}
		}

		return applyWrapper("Tail Count",
				UtilText.parse(BodyChanging.getTarget(), "Change how many [npc.tails] [npc.name] [npc.has]."
						+ "
The number of tails is taken into consideration when checking to see if there's a tail available for penetrative actions during sex."),
				"TAIL_COUNT",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformWingSizeDiv() {
		contentSB.setLength(0);

		for(WingSize wingSize : WingSize.values()) {
			if(BodyChanging.getTarget().getWingSize() == wingSize) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(wingSize.getName())
								+(wingSize.getValue()>=BodyChanging.getTarget().getLegConfiguration().getMinimumWingSizeForFlight().getValue()?"*":"")+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(wingSize.getName())
								+(wingSize.getValue()>=BodyChanging.getTarget().getLegConfiguration().getMinimumWingSizeForFlight().getValue()?"*":"")+""
						+ "
");
			}
		}

		return applyWrapper("Wing Size",
				UtilText.parse(BodyChanging.getTarget(), "Change the size of [npc.namePos] wings."
						+ "
Wing size affects [npc.namePos] ability to fly."
						+ " This varies based on leg configuration, with suitable sizes for [npc.namePos] '[npc.legConfiguration]' body being marked by an asterisk."),
				"WING_SIZE",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformWingChoiceDiv(List<Race> availableRaces, boolean removeNone) {
		contentSB.setLength(0);

		for(WingType wing : WingType.values()) {
			if((wing.getRace() !=null && availableRaces.contains(wing.getRace()))
					|| (!removeNone && wing==WingType.NONE)) {

				Colour c = Colour.TEXT_GREY;

				if(wing.getRace() != null) {
					c = wing.getRace().getColour();
				}

				if(BodyChanging.getTarget().getWingType() == wing) {
					contentSB.append(
							"
"
								+ ""+Util.capitaliseSentence(wing.getTransformName())+""
							+ "
");

				} else {
					contentSB.append(
							"
"
								+ ""+Util.capitaliseSentence(wing.getTransformName())+""
							+ "
");
				}
			}
		}

		return applyWrapper("Wings",
				UtilText.parse(BodyChanging.getTarget(), "Change [npc.namePos] wing type."
						+ "
Wing types which enable flight (when their size is also sufficiently large enough) are marked by an asterisk."),
				"WING_TYPE",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformHornChoiceDiv(List<Race> availableRaces) {
		contentSB.setLength(0);

		Set<AbstractHornType> types = new HashSet<>();
		for(Race race : availableRaces) {
			types.addAll(RacialBody.valueOfRace(race).getHornTypes(false));
		}

		for(AbstractHornType horn : types) {
			if((horn.getRace() !=null && availableRaces.contains(horn.getRace()))
					|| horn.equals(HornType.NONE)) {

				Colour c = Colour.TEXT_GREY;

				if(horn.getRace() != null) {
					c = horn.getRace().getColour();
				}

				if(BodyChanging.getTarget().getHornType().equals(horn)) {
					contentSB.append(
							"
"
								+ ""+Util.capitaliseSentence(horn.getTransformName())+""
							+ "
");

				} else {
					contentSB.append(
							"
"
								+ ""+Util.capitaliseSentence(horn.getTransformName())+""
							+ "
");
				}
			}
		}

		return applyWrapper("Horns",
				UtilText.parse(BodyChanging.getTarget(), "Change [npc.namePos] horn type."
						+ "
Horns of a sufficient length can be used as handles for some sex actions, and are also used for subspecies identification."),
				"HORN_TYPE",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformHornSizeDiv() {
		contentSB.setLength(0);

		for(HornLength hornLength : HornLength.values()) {
			if(HornLength.getHornLengthFromInt(BodyChanging.getTarget().getHornLength()) == hornLength) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(hornLength.getDescriptor())+(hornLength.isSuitableAsHandles()?"*":"")+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(hornLength.getDescriptor())+(hornLength.isSuitableAsHandles()?"*":"")+""
						+ "
");
			}
		}

		return applyWrapper("Horn Length",
				UtilText.parse(BodyChanging.getTarget(), "Change the length of [npc.namePos] horns."
						+ "
Horns of a sufficient length can be used as handles for some sex actions (marked by an asterisk), and are also used for subspecies identification."),
				"HORN_LENGTH",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformHornCountDiv() {
		contentSB.setLength(0);

		for(int i=1; i <= Horn.MAXIMUM_ROWS; i++) {
			if(BodyChanging.getTarget().getHornRows() == i) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(Util.intToString(i))+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(Util.intToString(i))+""
						+ "
");
			}
		}

		return applyWrapper("Horn Rows Count",
				UtilText.parse(BodyChanging.getTarget(), "Change how many pairs of [npc.horns] [npc.name] has."
						+ "
This is mostly a cosmetic change, but is also taken into account for subspecies identification (such as unicorns having one 'row' of a single horn)."),
				"HORN_COUNT",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformHornsPerRowCountDiv() {
		contentSB.setLength(0);

		for(int i=1; i <= Horn.MAXIMUM_HORNS_PER_ROW; i++) {
			if(BodyChanging.getTarget().getHornsPerRow() == i) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(Util.intToString(i))+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(Util.intToString(i))+""
						+ "
");
			}
		}

		return applyWrapper("Horns per Row Count",
				UtilText.parse(BodyChanging.getTarget(), "Change how many [npc.horns] are in each row."
						+ "
This is mostly a cosmetic change, but is also taken into account for subspecies identification (such as unicorns having one 'row' of a single horn)."),
				"HORN_COUNT_ROWS",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformAntennaChoiceDiv(List<Race> availableRaces) {
		contentSB.setLength(0);

		for(AntennaType antenna : AntennaType.values()) {
			if((antenna.getRace() !=null && availableRaces.contains(antenna.getRace()))
					|| antenna==AntennaType.NONE) {

				Colour c = Colour.TEXT_GREY;

				if(antenna.getRace() != null) {
					c = antenna.getRace().getColour();
				}

				if(BodyChanging.getTarget().getAntennaType() == antenna) {
					contentSB.append(
							"
"
								+ ""+Util.capitaliseSentence(antenna.getTransformName())+""
							+ "
");

				} else {
					contentSB.append(
							"
"
								+ ""+Util.capitaliseSentence(antenna.getTransformName())+""
							+ "
");
				}
			}
		}

		return applyWrapper("Antennae",
				UtilText.parse(BodyChanging.getTarget(), "Change [npc.namePos] antenna type."
						+ "
This is only used for subspecies identification."),
				"ANTENNAE_TYPE",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformHairChoiceDiv(List<Race> availableRaces) {
		contentSB.setLength(0);

		for(HairType hair : HairType.values()) {
			if((hair.getRace() !=null && availableRaces.contains(hair.getRace()))) {

				if(BodyChanging.getTarget().getHairType() == hair) {
					contentSB.append(
							"
"
								+ ""+Util.capitaliseSentence(hair.getTransformName())+""
							+ "
");

				} else {
					contentSB.append(
							"
"
								+ ""+Util.capitaliseSentence(hair.getTransformName())+""
							+ "
");
				}
			}
		}

		return applyWrapper("Hair",
				UtilText.parse(BodyChanging.getTarget(), "Change [npc.namePos] hair type."
						+ "
This is only used for subspecies identification."),
				"HAIR_TYPE",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformHairLengthDiv() {
		contentSB.setLength(0);

		for(HairLength hairLength : HairLength.values()) {
			if(BodyChanging.getTarget().getHairLength()==hairLength) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(hairLength.getDescriptor())+(hairLength.isSuitableForPulling()?"*":"")+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(hairLength.getDescriptor())+(hairLength.isSuitableForPulling()?"*":"")+""
						+ "
");
			}
		}

		return applyWrapper("Hair Length",
				UtilText.parse(BodyChanging.getTarget(), "Change the length of [npc.namePos] [npc.hair]."
						+ "
Hair of a sufficient length (marked by an asterisk) can be pulled in some sex actions."),
				"HAIR_LENGTH",
				contentSB.toString(),
				true);
	}

	public static String getSelfDivHairStyles(String title, String description) {
		contentSB.setLength(0);

		for (HairStyle hairStyle : HairStyle.values()) {
			if (BodyChanging.getTarget().getHairStyle() == hairStyle) {
				contentSB.append(
						"
"
							+ "" + Util.capitaliseSentence(hairStyle.getName()) + ""
						+ "
");
			} else {
				if(BodyChanging.getTarget().getHairRawLengthValue() >= hairStyle.getMinimumLengthRequired()) {
					contentSB.append(
							"
"
								+ "" + Util.capitaliseSentence(hairStyle.getName()) + ""
							+ "
");
				} else {
					contentSB.append(
							"
"
								+ "[style.colourDisabled(" + Util.capitaliseSentence(hairStyle.getName()) + ")]"
							+ "
");
				}
			}
		}

		return applyWrapper("Hair Style",
				description
					+ "
'"+Util.capitaliseSentence(HairStyle.TWIN_TAILS.getName())+"' and '"+Util.capitaliseSentence(HairStyle.TWIN_BRAIDS.getName())+"' can be used as handles in some sex actions.",
				"HAIR_STYLE",
				contentSB.toString(),
				false);
	}

	public static String getSelfTransformAssChoiceDiv(List<Race> availableRaces) {
		contentSB.setLength(0);

		for(AbstractAssType ass : AssType.getAllAssTypes()) {
			if((ass.getRace() !=null && availableRaces.contains(ass.getRace()))) {

				if(BodyChanging.getTarget().getAssType().equals(ass)) {
					contentSB.append(
							"
"
								+ ""+Util.capitaliseSentence(ass.getTransformName())+""
							+ "
");

				} else {
					contentSB.append(
							"
"
								+ ""+Util.capitaliseSentence(ass.getTransformName())+""
							+ "
");
				}
			}
		}

		return applyWrapper("Anus",
				UtilText.parse(BodyChanging.getTarget(), "Change the type of [npc.namePos] asshole."
						+ "
Anus type affects the asshole modifiers which characters spawn with, as well as descriptions and subspecies identification."),
				"ASSHOLE_TYPE",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformBreastChoiceDiv(List<Race> availableRaces) {
		contentSB.setLength(0);

		for(AbstractBreastType breast : BreastType.getAllBreastTypes()) {
			if((breast.getRace() !=null && breast.getRace()!=Race.NONE && availableRaces.contains(breast.getRace()))) {
				if(BodyChanging.getTarget().getBreastType() == breast) {
					contentSB.append(
							"
"
								+ ""+Util.capitaliseSentence(breast.getTransformName())+""
							+ "
");

				} else {
					contentSB.append(
							"
"
								+ ""+Util.capitaliseSentence(breast.getTransformName())+""
							+ "
");
				}
			}
		}

		return applyWrapper("Nipples",
				UtilText.parse(BodyChanging.getTarget(), "Change the type of [npc.namePos] nipples."
						+ "
Nipple type affects the nipple modifiers which characters spawn with, as well as the type of milk [npc.her] breasts produce. It is also used for character descriptions and subspecies identification."),
				"NIPPLE_TYPE",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformBreastCrotchChoiceDiv(List<Race> availableRaces) {
		contentSB.setLength(0);

		for(AbstractBreastType breast : BreastType.getAllBreastTypes()) {
			if((breast.getRace()==Race.NONE || availableRaces.contains(breast.getRace()))) {
				if(BodyChanging.getTarget().getBreastCrotchType() == breast) {
					contentSB.append(
							"
"
								+ ""+Util.capitaliseSentence(breast.getTransformName())+""
							+ "
");

				} else {
					contentSB.append(
							"
"
								+ ""+Util.capitaliseSentence(breast.getTransformName())+""
							+ "
");
				}
			}
		}

		return applyWrapper(Util.capitaliseSentence(getCrotchBoobName(false))+" Type",
				UtilText.parse(BodyChanging.getTarget(), "Change the type of the nipples on [npc.namePos] "+getCrotchBoobName(true)
						+ "
Crotch-nipple type affects the nipple modifiers which characters spawn with, as well as the type of milk [npc.her] "+getCrotchBoobName(true)+" produce."
								+ " It is also used for character descriptions and subspecies identification."),
				"CROTCH_NIPPLE_TYPE",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformArmChoiceDiv(List<Race> availableRaces) {
		contentSB.setLength(0);

		for(AbstractArmType arm : ArmType.getAllArmTypes()) {
			if(arm.getRace() !=null && availableRaces.contains(arm.getRace())) {
				if(BodyChanging.getTarget().getArmType().equals(arm)) {
					contentSB.append(
							"
"
								+ ""+Util.capitaliseSentence(arm.getTransformName())+""
							+ "
");

				} else {
					contentSB.append(
							"
"
								+ ""+Util.capitaliseSentence(arm.getTransformName())+""
							+ "
");
				}
			}
		}

		return applyWrapper("Arms",
				UtilText.parse(BodyChanging.getTarget(), "Change [npc.namePos] arm type."
						+ "
Arm type is only used for character descriptions and subspecies identification."),
				"ARM_TYPE",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformArmCountDiv() {
		contentSB.setLength(0);

		for(int i=1; i <= Arm.MAXIMUM_ROWS; i++) {
			if(BodyChanging.getTarget().getArmRows() == i) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(Util.intToString(i))+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(Util.intToString(i))+""
						+ "
");
			}
		}

		return applyWrapper("Arm Rows Count",
				UtilText.parse(BodyChanging.getTarget(), "Change how many pairs of [npc.arms] [npc.name] has."
						+ "
Arm count is used for character descriptions and subspecies identification, as well as determining if there are any free hands to use for sex actions."),
				"ARM_COUNT",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformLegChoiceDiv(List<Race> availableRaces) {
		contentSB.setLength(0);

		for(AbstractLegType leg : LegType.getAllLegTypes()) {
			if(leg.getRace() !=null && availableRaces.contains(leg.getRace())) {
				if(BodyChanging.getTarget().getLegType().equals(leg)) {
					contentSB.append(
							"
"
								+ ""+Util.capitaliseSentence(leg.getTransformName())+""
							+ "
");

				} else {
					contentSB.append(
							"
"
								+ ""+Util.capitaliseSentence(leg.getTransformName())+""
							+ "
");
				}
			}
		}

		return applyWrapper("Legs",
				UtilText.parse(BodyChanging.getTarget(), "Change [npc.namePos] leg type."
						+ "
Leg type is used for character descriptions and subspecies identification, and is sometimes limited based on current leg configuration."),
				"LEG_TYPE",
				contentSB.toString(),
				true);

	}

	public static String getSelfTransformFootStructureChoiceDiv() {
		contentSB.setLength(0);

		for(FootStructure footStructure : FootStructure.values()) {
			if(BodyChanging.getTarget().getLegType().getFootType().getPermittedFootStructures().contains(footStructure)) {
				if(BodyChanging.getTarget().getFootStructure() == footStructure) {
					contentSB.append(
							"
"
								+ ""+Util.capitaliseSentence(footStructure.getName())+""
							+ "
");

				} else {
					contentSB.append(
							"
"
								+ ""+Util.capitaliseSentence(footStructure.getName())+""
							+ "
");
				}
			}
		}

		return applyWrapper("Foot Structure",
				UtilText.parse(BodyChanging.getTarget(), "Change the structure of [npc.namePos] [npc.feet]."
						+ "
Foot type is used for both character and sex descriptions, and also may limit foot-related clothing which can be worn."),
				"FOOT_STRUCTURE",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformFaceChoiceDiv(List<Race> availableRaces) {
		contentSB.setLength(0);

		for(FaceType face : FaceType.values()) {
			if(face.getRace() !=null && availableRaces.contains(face.getRace())) {
				if(BodyChanging.getTarget().getFaceType() == face) {
					contentSB.append(
							"
"
								+ ""+Util.capitaliseSentence(face.getTransformName())+""
							+ "
");

				} else {
					contentSB.append(
							"
"
								+ ""+Util.capitaliseSentence(face.getTransformName())+""
							+ "
");
				}
			}
		}

		return applyWrapper("Face",
				UtilText.parse(BodyChanging.getTarget(), "Change [npc.namePos] face type."
						+ "
Face type influences mouth and tongue types, and is therefore used for both character and sex descriptions, as well as for determining subspecies identification."),
				"FACE_TYPE",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformBodyChoiceDiv(List<Race> availableRaces) {
		contentSB.setLength(0);

		for(SkinType skin : SkinType.values()) {
			if(availableRaces.contains(skin.getRace())) {
				if(BodyChanging.getTarget().getSkinType() == skin) {
					contentSB.append(
							"
"
								+ ""+Util.capitaliseSentence(skin.getTransformName())+""
							+ "
");

				} else {
					contentSB.append(
							"
"
								+ ""+Util.capitaliseSentence(skin.getTransformName())+""
							+ "
");
				}
			}
		}

		return applyWrapper("Body",
				UtilText.parse(BodyChanging.getTarget(), "Change [npc.namePos] body type."
						+ "
Body type influences what sort of material is covering [npc.namePos] body, and can range from skin or fur, to scales or feathers."),
				"BODY_TYPE",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformGenitalArrangementChoiceDiv() {
		contentSB.setLength(0);

		for(GenitalArrangement arrangement : GenitalArrangement.values()) {
			if(arrangement!=GenitalArrangement.CLOACA_BEHIND
					&& (BodyChanging.getTarget().getLegConfiguration().isGenitalConfigurationTransformable() || BodyChanging.getTarget().getGenitalArrangement() == arrangement)) {
				if(BodyChanging.getTarget().getGenitalArrangement() == arrangement) {
					contentSB.append(
							"
"
								+ ""+Util.capitaliseSentence(arrangement.getName())+""
							+ "
");

				} else if(!Main.getProperties().hasValue(PropertyValue.bipedalCloaca) && BodyChanging.getTarget().getLegConfiguration()==LegConfiguration.BIPEDAL && arrangement==GenitalArrangement.CLOACA) {
					contentSB.append(
							"
"
								+ ""+Util.capitaliseSentence(arrangement.getName())+""
							+ "
");

				} else {
					contentSB.append(
							"
"
								+ ""+Util.capitaliseSentence(arrangement.getName())+""
							+ "
");
				}
			}
		}

		return applyWrapper("Genital Arrangement",
				UtilText.parse(BodyChanging.getTarget(),
						"Change the [npc.namePos] genital configuration."
								+ "
This determines where [npc.namePos] genitals and asshole are located. It is limited to '"+GenitalArrangement.NORMAL.getName()+"' for most leg configurations."
						+ (!Main.getProperties().hasValue(PropertyValue.bipedalCloaca)
							?" [style.italicsDisabled(Bipedal cloacas are disabled in your content options.)]"
							:"")),
				"GENITAL_ARRANGEMENT",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformLegConfigurationChoiceDiv() {
		contentSB.setLength(0);

		for(LegConfiguration legConfig : LegConfiguration.values()) {
			if(BodyChanging.getTarget().getLegType().isLegConfigurationAvailable(legConfig)) {
				if(BodyChanging.getTarget().getLegConfiguration() == legConfig) {
					contentSB.append(
							"
"
								+ ""+Util.capitaliseSentence(legConfig.getName())+""
							+ "
");

				} else {
					contentSB.append(
							"
"
								+ ""+Util.capitaliseSentence(legConfig.getName())+""
							+ "
");
				}
			}
		}

		return applyWrapper("Leg Configuration",
				UtilText.parse(BodyChanging.getTarget(), "Change what [npc.namePos] lower body is like."
						+ "
Applying this change causes all body parts below the waist to be transformed."),
				"LEG_CONFIGURATION",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformEarChoiceDiv(List<Race> availableRaces) {
		contentSB.setLength(0);

		for(AbstractEarType ear : EarType.getAllEarTypes()) {
			if(availableRaces.contains(ear.getRace())) {
				if(BodyChanging.getTarget().getEarType().equals(ear)) {
					contentSB.append(
							"
"
								+ ""+Util.capitaliseSentence(ear.getTransformName())+(ear.isAbleToBeUsedAsHandlesInSex()?"*":"")+""
							+ "
");

				} else {
					contentSB.append(
							"
"
								+ ""+Util.capitaliseSentence(ear.getTransformName())+(ear.isAbleToBeUsedAsHandlesInSex()?"*":"")+""
							+ "
");
				}
			}
		}

		return applyWrapper("Ears",
				UtilText.parse(BodyChanging.getTarget(), "Change [npc.namePos] ear type."
						+ "
This is mostly used for cosmetics and subspecies identification, but some ear types are long enough to be pulled in some sex actions (marked by an asterisk where available)."),
				"EAR_TYPE",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformEyeChoiceDiv(List<Race> availableRaces) {
		contentSB.setLength(0);

		for(EyeType eye : EyeType.values()) {
			if(availableRaces.contains(eye.getRace())) {
				if(BodyChanging.getTarget().getEyeType() == eye) {
					contentSB.append(
							"
"
								+ ""+Util.capitaliseSentence(eye.getTransformName())+""
							+ "
");

				} else {
					contentSB.append(
							"
"
								+ ""+Util.capitaliseSentence(eye.getTransformName())+""
							+ "
");
				}
			}
		}

		return applyWrapper("Eyes",
				UtilText.parse(BodyChanging.getTarget(), "Change [npc.namePos] eye type."
						+ "
Eye type determines what iris and pupil shape characters spawn with, and is also used for subspecies identification."),
				"EYE_TYPE",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformIrisChoiceDiv() {
		contentSB.setLength(0);

		for(EyeShape eyeShape : EyeShape.values()) {
			if(BodyChanging.getTarget().getIrisShape() == eyeShape) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(eyeShape.getName())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(eyeShape.getName())+""
						+ "
");
			}
		}

		return applyWrapper("Iris Shape",
				UtilText.parse(BodyChanging.getTarget(), "Change the shape of [npc.namePos] irises."
						+ "
This is a purely cosmetic transformation."),
				"IRIS_SHAPE",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformPupilChoiceDiv() {
		contentSB.setLength(0);

		for(EyeShape eyeShape : EyeShape.values()) {
			if(BodyChanging.getTarget().getPupilShape() == eyeShape) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(eyeShape.getName())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(eyeShape.getName())+""
						+ "
");
			}
		}

		return applyWrapper("Pupil Shape",
				UtilText.parse(BodyChanging.getTarget(), "Change the shape of [npc.namePos] pupils."
						+ "
This is a purely cosmetic transformation."),
				"PUPIL_SHAPE",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformEyeCountDiv() {
		contentSB.setLength(0);

		for(int i=1; i <= Eye.MAXIMUM_PAIRS; i++) {
			if(BodyChanging.getTarget().getEyePairs() == i) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(Util.intToString(i))+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(Util.intToString(i))+""
						+ "
");
			}
		}

		return applyWrapper("Eye Pairs Count",
				UtilText.parse(BodyChanging.getTarget(), "Change how many pairs of [npc.eyes] [npc.name] [npc.has]."
						+ "
This is a purely cosmetic transformation."),
				"EYE_COUNT",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformLipSizeDiv() {
		contentSB.setLength(0);

		for(LipSize lipSize : LipSize.values()) {
			if(BodyChanging.getTarget().getLipSize() == lipSize) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(lipSize.getName())+(lipSize.isImpedesSpeech()?"*":"")+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(lipSize.getName())+(lipSize.isImpedesSpeech()?"*":"")+""
						+ "
");
			}
		}

		return applyWrapper("Lip Size",
				UtilText.parse(BodyChanging.getTarget(), "Change the size of [npc.namePos] lips."
						+ "
While mostly a cosmetic transformation, very large lip sizes (marked by an asterisk) will also cause [npc.name] to speak with a lisp."),
				"LIP_SIZE",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformThroatModifiersDiv() {
		contentSB.setLength(0);

		for(OrificeModifier orificeMod : OrificeModifier.values()) {
			if(BodyChanging.getTarget().hasFaceOrificeModifier(orificeMod)) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(orificeMod.getName())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(orificeMod.getName())+""
						+ "
");
			}
		}

		return applyWrapper("Lip & Throat Modifiers",
				UtilText.parse(BodyChanging.getTarget(), "Change the modifiers for [npc.namePos] lips and throat."
						+ "
These modifiers affect descriptions when performing oral sex."),
				"LIP_MODS",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformTongueModifiersDiv() {
		contentSB.setLength(0);

		for(TongueModifier tongueMod : TongueModifier.values()) {
			if(BodyChanging.getTarget().hasTongueModifier(tongueMod)) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(tongueMod.getName())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(tongueMod.getName())+""
						+ "
");
			}
		}

		return applyWrapper("Tongue Modifiers",
				UtilText.parse(BodyChanging.getTarget(), "Change the modifiers for [npc.namePos] tongue."
						+ "
Tongue modifiers affect descriptions when performing oral sex."),
				"TONGUE_MODS",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformTongueSizeDiv() {
		contentSB.setLength(0);

		for(TongueLength tongueLength : TongueLength.values()) {
			if(BodyChanging.getTarget().getTongueLength() == tongueLength) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(tongueLength.getDescriptor())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(tongueLength.getDescriptor())+""
						+ "
");
			}
		}

		return applyWrapper("Tongue Length",
				UtilText.parse(BodyChanging.getTarget(), "Change the length of [npc.namePos] tongue."
						+ "
This affects descriptions when performing oral sex."),
				"TONGUE_LENGTH",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformAssSizeDiv() {
		contentSB.setLength(0);

		for(AssSize as : AssSize.values()) {
			if(BodyChanging.getTarget().getAssSize() == as) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(as.getDescriptor())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(as.getDescriptor())+""
						+ "
");
			}
		}

		return applyWrapper("Ass Size",
				UtilText.parse(BodyChanging.getTarget(), "Change the size of [npc.namePos] ass."
						+ "
This is a purely cosmetic change."),
				"ASS_SIZE",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformHipSizeDiv() {
		contentSB.setLength(0);

		for(HipSize hs : HipSize.values()) {
			if(BodyChanging.getTarget().getHipSize() == hs) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(hs.getDescriptor())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(hs.getDescriptor())+""
						+ "
");
			}
		}

		return applyWrapper("Hip Size",
				UtilText.parse(BodyChanging.getTarget(), "Change the size of [npc.namePos] hips."
						+ "
This is a purely cosmetic change."),
				"HIP_SIZE",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformAnusCapacityDiv() {
		contentSB.setLength(0);

		for(Capacity value : Capacity.values()) {
			if(BodyChanging.getTarget().getAssCapacity() == value) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(value.getDescriptor())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(value.getDescriptor())+""
						+ "
");
			}
		}

		return applyWrapper("Anus Capacity",
				UtilText.parse(BodyChanging.getTarget(), "Change the capacity of [npc.namePos] asshole."
						+ "
This affects the size of penetrative objects that [npc.namePos] asshole can take. If capacity is too small or too large for a penetrative object, arousal gains are affected."),
				"ASSHOLE_CAPACITY",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformAnusWetnessDiv() {
		contentSB.setLength(0);

		for(Wetness value : Wetness.values()) {
			if(BodyChanging.getTarget().getAssWetness() == value) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(value.getDescriptor())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(value.getDescriptor())+""
						+ "
");
			}
		}

		return applyWrapper("Anus Wetness",
				UtilText.parse(BodyChanging.getTarget(), "Change the wetness of [npc.namePos] asshole."
						+ "
This affects pleasure in sex, as non-lubricated orifices negatively affect arousal gains."),
				"ASSHOLE_WETNESS",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformAnusElasticityDiv() {
		contentSB.setLength(0);

		for(OrificeElasticity value : OrificeElasticity.values()) {
			if(BodyChanging.getTarget().getAssElasticity() == value) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(value.getDescriptor())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(value.getDescriptor())+""
						+ "
");
			}
		}

		return applyWrapper("Anus Elasticity",
				UtilText.parse(BodyChanging.getTarget(), "Change the elasticity of [npc.namePos] asshole."
						+ "
Elasticity of an orifice determines how quickly it stretches out if a penetrating object is too big for it."),
				"ASSHOLE_ELASTICITY",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformAnusPlasticityDiv() {
		contentSB.setLength(0);

		for(OrificePlasticity value : OrificePlasticity.values()) {
			if(BodyChanging.getTarget().getAssPlasticity() == value) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(value.getDescriptor())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(value.getDescriptor())+""
						+ "
");
			}
		}

		return applyWrapper("Anus Plasticity",
				UtilText.parse(BodyChanging.getTarget(), "Change the plasticity of [npc.namePos] asshole."
						+ "
Plasticity of an orifice determines how quickly, and by how much, it regains its original tightness after being stretched out."),
				"ASSHOLE_PLASTICITY",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformAnusModifiersDiv() {
		contentSB.setLength(0);

		for(OrificeModifier orificeMod : OrificeModifier.values()) {
			if(BodyChanging.getTarget().hasAssOrificeModifier(orificeMod)) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(orificeMod.getName())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(orificeMod.getName())+""
						+ "
");
			}
		}

		return applyWrapper("Anus Modifiers",
				UtilText.parse(BodyChanging.getTarget(), "Change the modifiers for [npc.namePos] anus."
						+ "
Orifice modifiers affect descriptions and some actions in sex."),
				"ASSHOLE_MODS",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformBreastSizeDiv() {
		return applyFullVariableWrapper("Breast Size",
				BodyChanging.getTarget().isPlayer()
					?"Change the size of your breasts."
					:UtilText.parse(BodyChanging.getTarget(), "Change the size of [npc.namePos] breasts."),
				"BREAST_SIZE",
				"",
				"",
				BodyChanging.getTarget().getBreastRawSizeValue()
					+"
"+Util.capitaliseSentence(BodyChanging.getTarget().getBreastSize().getCupSizeName())+(BodyChanging.getTarget().getBreastSize()==CupSize.FLAT?"":"-cup")+"",
				BodyChanging.getTarget().getBreastRawSizeValue()<=0,
				BodyChanging.getTarget().getBreastRawSizeValue()>=CupSize.XXX_N.getMeasurement());
	}

	// This is to stop variable-length names from being generated every time a button is clicked (i.e. stop "crotch-tits", "crotch-breasts")
	private static String getCrotchBoobName(boolean plural) {
		if(BodyChanging.getTarget().getBreastCrotchShape()==BreastShape.UDDERS) {
			return plural?"udders":"udder";
		} else {
			return plural?"crotch-boobs":"crotch-boob";
		}
	}

	public static String getSelfTransformBreastCrotchSizeDiv() {
		return applyFullVariableWrapper(
				UtilText.parse(BodyChanging.getTarget(), Util.capitaliseSentence(getCrotchBoobName(false))+" Size"),
				UtilText.parse(BodyChanging.getTarget(), "Change the size of [npc.namePos] "+getCrotchBoobName(true)+"."),
				"CROTCH_BOOB_SIZE",
				" cup",
				" cup",
				Util.capitaliseSentence(BodyChanging.getTarget().getBreastCrotchSize().getCupSizeName())
					+"
("+BodyChanging.getTarget().getBreastCrotchRawSizeValue()+")",
				BodyChanging.getTarget().getBreastCrotchRawSizeValue()<=0,
				BodyChanging.getTarget().getBreastCrotchRawSizeValue()>=CupSize.XXX_N.getMeasurement());
	}

	public static String getSelfTransformBreastShapeDiv() {
		contentSB.setLength(0);

		for(BreastShape bs : BreastShape.values()) {
			if(!bs.isRestrictedToCrotchBoobs()) {
				if(BodyChanging.getTarget().getBreastShape() == bs) {
					contentSB.append(
							"
"
								+ ""+Util.capitaliseSentence(bs.getTransformName())+""
							+ "
");

				} else {
					contentSB.append(
							"
"
								+ ""+Util.capitaliseSentence(bs.getTransformName())+""
							+ "
");
				}
			}
		}

		return applyWrapper("Breast Shape",
				UtilText.parse(BodyChanging.getTarget(), "Change the shape of [npc.namePos] breasts."
						+ "
This is primarily a cosmetic change, but also affects some descriptions in sex."),
				"BREAST_SHAPE",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformBreastCrotchShapeDiv() {
		contentSB.setLength(0);

		for(BreastShape bs : BreastShape.values()) {
			if(BodyChanging.getTarget().getBreastCrotchShape() == bs) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(bs.getTransformName())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(bs.getTransformName())+""
						+ "
");
			}
		}

		return applyWrapper(
				UtilText.parse(BodyChanging.getTarget(), Util.capitaliseSentence(getCrotchBoobName(false))+" Shape"),
				UtilText.parse(BodyChanging.getTarget(), "Change the shape of [npc.namePos] "+getCrotchBoobName(true)+"."
						+ "
This is primarily a cosmetic change, but also affects some descriptions in sex."),
				"BREAST_CROTCH_SHAPE",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformBreastRowsDiv() {
		contentSB.setLength(0);

		for(int i=1; i <= Breast.MAXIMUM_BREAST_ROWS; i++) {
			if(BodyChanging.getTarget().getBreastRows() == i) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(Util.intToString(i))+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(Util.intToString(i))+""
						+ "
");
			}
		}

		return applyWrapper("Breast Rows",
				UtilText.parse(BodyChanging.getTarget(), "Change the number of breast rows [npc.name] [npc.has]."
						+ "
This is a mostly a cosmetic change, but is also taken into account when determining if there are any free nipples for use in sex."),
				"BREAST_ROWS",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformBreastCrotchRowsDiv() {
		contentSB.setLength(0);

		for(int i=1; i <= BreastCrotch.MAXIMUM_BREAST_ROWS; i++) {
			if(BodyChanging.getTarget().getBreastCrotchRows() == i) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(Util.intToString(i))+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(Util.intToString(i))+""
						+ "
");
			}
		}

		return applyWrapper(
				UtilText.parse(BodyChanging.getTarget(), Util.capitaliseSentence(getCrotchBoobName(false))+" Rows"),
				UtilText.parse(BodyChanging.getTarget(), "Change the number of "+getCrotchBoobName(false)+" rows [npc.name] [npc.has]."
						+ "
This is a mostly a cosmetic change, but is also taken into account when determining if there are any free crotch-nipples for use in sex."),
				"BREAST_CROTCH_ROWS",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformNippleCountDiv() {
		contentSB.setLength(0);

		for(int i=1; i <= Breast.MAXIMUM_NIPPLES_PER_BREAST; i++) {
			if(BodyChanging.getTarget().getNippleCountPerBreast() == i) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(Util.intToString(i))+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(Util.intToString(i))+""
						+ "
");
			}
		}

		return applyWrapper("Nipple Count",
				UtilText.parse(BodyChanging.getTarget(), "Change the number of nipples [npc.name] has on each breast."
						+ "
This is primarily a cosmetic change, but also affects descriptions in sex."),
				"BREAST_NIPPLE_COUNT",
				contentSB.toString(),true);
	}

	public static String getSelfTransformNippleCrotchCountDiv() {
		contentSB.setLength(0);

		for(int i=1; i <= BreastCrotch.MAXIMUM_NIPPLES_PER_BREAST; i++) {
			if(BodyChanging.getTarget().getNippleCrotchCountPerBreast() == i) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(Util.intToString(i))+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(Util.intToString(i))+""
						+ "
");
			}
		}

		return applyWrapper("Nipple Count",
				UtilText.parse(BodyChanging.getTarget(), "Change the number of nipples [npc.name] [npc.has] on each "+getCrotchBoobName(false)+"."
						+ "
This is primarily a cosmetic change, but also affects descriptions in sex."),
				"BREAST_CROTCH_NIPPLE_COUNT",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformNippleSizeDiv() {
		contentSB.setLength(0);

		for(NippleSize ns : NippleSize.values()) {
			if(BodyChanging.getTarget().getNippleSize() == ns) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(ns.getName())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(ns.getName())+""
						+ "
");
			}
		}

		return applyWrapper("Nipple Size",
				UtilText.parse(BodyChanging.getTarget(), "Change the size of [npc.namePos] nipples."
						+ "
This is primarily a cosmetic change, but also affects descriptions in sex."),
				"NIPPLE_SIZE",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformNippleCrotchSizeDiv() {
		contentSB.setLength(0);

		for(NippleSize ns : NippleSize.values()) {
			if(BodyChanging.getTarget().getNippleCrotchSize() == ns) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(ns.getName())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(ns.getName())+""
						+ "
");
			}
		}

		return applyWrapper("Nipple Size",
				UtilText.parse(BodyChanging.getTarget(), "Change the size of [npc.namePos] [npc.crotchNipples]."
						+ "
This is primarily a cosmetic change, but also affects descriptions in sex."),
				"NIPPLE_CROTCH_SIZE",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformAreolaeSizeDiv() {
		contentSB.setLength(0);

		for(AreolaeSize as : AreolaeSize.values()) {
			if(BodyChanging.getTarget().getAreolaeSize() == as) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(as.getName())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(as.getName())+""
						+ "
");
			}
		}

		return applyWrapper("Areolae Size",
				UtilText.parse(BodyChanging.getTarget(), "Change the size of [npc.namePos] areolae."
						+ "
This is primarily a cosmetic change, but also affects descriptions in sex."),
				"AREOLAE_SIZE",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformAreolaeCrotchSizeDiv() {
		contentSB.setLength(0);

		for(AreolaeSize as : AreolaeSize.values()) {
			if(BodyChanging.getTarget().getAreolaeCrotchSize() == as) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(as.getName())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(as.getName())+""
						+ "
");
			}
		}

		return applyWrapper("Areolae Size",
				UtilText.parse(BodyChanging.getTarget(), "Change the size of [npc.namePos] areolae around [npc.her] [npc.crotchNipples]."
						+ "
This is primarily a cosmetic change, but also affects descriptions in sex."),
				"CROTCH_AREOLAE_SIZE",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformNippleShapeDiv() {
		contentSB.setLength(0);

		for(NippleShape ns : NippleShape.values()) {
			if(BodyChanging.getTarget().getNippleShape() == ns) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(ns.getName())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(ns.getName())+""
						+ "
");
			}
		}

		return applyWrapper("Nipple Shape",
				UtilText.parse(BodyChanging.getTarget(), "Change the shape of [npc.namePos] nipples."
						+ "
This is primarily a cosmetic change, but also affects descriptions and availability of some actions in sex."),
				"NIPPLE_SHAPE",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformNippleCrotchShapeDiv() {
		contentSB.setLength(0);

		for(NippleShape ns : NippleShape.values()) {
			if(BodyChanging.getTarget().getNippleCrotchShape() == ns) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(ns.getName())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(ns.getName())+""
						+ "
");
			}
		}

		return applyWrapper("Crotch-nipple Shape",
				UtilText.parse(BodyChanging.getTarget(), "Change the shape of [npc.namePos] [npc.crotchNipples]."
						+ "
This is primarily a cosmetic change, but also affects descriptions and availability of some actions in sex."),
				"NIPPLE_CROTCH_SHAPE",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformNippleCapacityDiv() {
		contentSB.setLength(0);

		for(Capacity value : Capacity.values()) {
			if(BodyChanging.getTarget().getNippleCapacity() == value) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(value.getDescriptor())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(value.getDescriptor())+""
						+ "
");
			}
		}

		return applyWrapper("Nipple Capacity",
				UtilText.parse(BodyChanging.getTarget(), "Change the capacity of [npc.namePos] nipples."
						+ "
Anything other than a value of zero qualifies [npc.namePos] nipples as being able to be penetrated during sex. (Subject to your 'nipple penetration' preference in the content options.)"),
				"NIPPLE_CAPACITY",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformNippleCrotchCapacityDiv() {
		contentSB.setLength(0);

		for(Capacity value : Capacity.values()) {
			if(BodyChanging.getTarget().getNippleCrotchCapacity() == value) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(value.getDescriptor())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(value.getDescriptor())+""
						+ "
");
			}
		}

		return applyWrapper("Nipple Capacity",
				UtilText.parse(BodyChanging.getTarget(), "Change the capacity of [npc.namePos] [npc.crotchNipples]."
						+ "
Anything other than a value of zero qualifies [npc.namePos] nipples as being able to be penetrated during sex. (Subject to your 'nipple penetration' preference in the content options.)"),
				"NIPPLE_CROTCH_CAPACITY",
				contentSB.toString(),
				true);
	}

	public static int getLactationUpperLimit() {
		if(Main.game.isInNewWorld()) {
			return Lactation.SEVEN_MONSTROUS_AMOUNT_POURING.getMaximumValue();
		} else {
			return 150;
		}
	}

	public static String getSelfTransformLactationDiv() {
		return applyVariableWrapperFluids("Lactation",
				UtilText.parse(BodyChanging.getTarget(), "Change the maximum amount of milk [npc.name] can store in [npc.her] [npc.breasts]."
						+ "
Once drained, [npc.namePos] breasts fill with milk up to this value at a rate determined by [npc.her] milk regeneration value."),
				"MILK_PRODUCTION",
				Units.fluid(BodyChanging.getTarget().getBreastRawMilkStorageValue(), ValueType.PRECISE)
					+"
"+Util.capitaliseSentence(BodyChanging.getTarget().getBreastMilkStorage().getName())+"",
				BodyChanging.getTarget().getBreastRawMilkStorageValue()<=0,
				BodyChanging.getTarget().getBreastRawMilkStorageValue()>=getLactationUpperLimit(),
				FLUID_INCREMENT_SMALL,
				FLUID_INCREMENT_AVERAGE,
				FLUID_INCREMENT_LARGE);
	}

	public static int getLactationCrotchUpperLimit() {
		return Lactation.SEVEN_MONSTROUS_AMOUNT_POURING.getMaximumValue();
	}

	public static String getSelfTransformLactationCrotchDiv() {
		return applyVariableWrapperFluids("Lactation",
				UtilText.parse(BodyChanging.getTarget(), "Change the maximum amount of milk [npc.name] can store in [npc.her] [npc.crotchBoobs]."
						+ "
Once drained, [npc.namePos] [npc.crotchBoobs] fill with milk up to this value at a rate determined by [npc.her] crotch-milk regeneration value."),
				"MILK_CROTCH_PRODUCTION",
				Units.fluid(BodyChanging.getTarget().getBreastCrotchRawMilkStorageValue(), ValueType.PRECISE)
					+"
"+Util.capitaliseSentence(BodyChanging.getTarget().getBreastCrotchMilkStorage().getName())+"",
				BodyChanging.getTarget().getBreastCrotchRawMilkStorageValue()<=0,
				BodyChanging.getTarget().getBreastCrotchRawMilkStorageValue()>=getLactationCrotchUpperLimit(),
				FLUID_INCREMENT_SMALL,
				FLUID_INCREMENT_AVERAGE,
				FLUID_INCREMENT_LARGE);
	}

	public static String getSelfTransformLactationRegenerationDiv() {
		return applyVariableWrapperFluids("Milk Regeneration Per Breast",
				UtilText.parse(BodyChanging.getTarget(), "Alter the rate at which milk is produced in each of [npc.namePos] breasts. The more breasts [npc.sheHasFull], the more milk [npc.she] produces per second."
						+ "
Once drained, [npc.namePos] breasts fill with milk up to their maximum storage value at this rate."),
				"MILK_REGENERATION",
				Units.fluid(BodyChanging.getTarget().getBreastRawLactationRegenerationValue(), ValueType.PRECISE)+"/day"
					+"
("+Units.fluid(BodyChanging.getTarget().getLactationRegenerationPerSecond(false)*60, ValueType.PRECISE)+"/minute)"
					+"
"+Util.capitaliseSentence(BodyChanging.getTarget().getBreastLactationRegeneration().getName())+"",
				BodyChanging.getTarget().getBreastRawLactationRegenerationValue()<=0,
				BodyChanging.getTarget().getBreastRawLactationRegenerationValue()>=FluidRegeneration.FOUR_VERY_RAPID.getMaximumRegenerationValuePerDay(),
				FLUID_REGEN_INCREMENT_SMALL,
				FLUID_REGEN_INCREMENT_AVERAGE,
				FLUID_REGEN_INCREMENT_LARGE);
	}

	public static String getSelfTransformLactationCrotchRegenerationDiv() {
		return applyVariableWrapperFluids("Milk Regeneration Per Crotch-boob",
				UtilText.parse(BodyChanging.getTarget(), "Alter the rate at which milk is produced in each of [npc.namePos] [npc.crotchBoobs]. The more [npc.crotchBoobs] [npc.sheHasFull], the more milk [npc.she] produces per second."
						+ "
Once drained, [npc.namePos] [npc.crotchBoobs] fill with milk up to their maximum storage value at this rate."),
				"MILK_CROTCH_REGENERATION",
				Units.fluid(BodyChanging.getTarget().getBreastCrotchRawLactationRegenerationValue(), ValueType.PRECISE)+"/day"
						+"
("+Units.fluid(BodyChanging.getTarget().getCrotchLactationRegenerationPerSecond(false)*60, ValueType.PRECISE)+"/minute)"
						+"
"+Util.capitaliseSentence(BodyChanging.getTarget().getBreastCrotchLactationRegeneration().getName())+"",
				BodyChanging.getTarget().getBreastCrotchRawLactationRegenerationValue()<=0,
				BodyChanging.getTarget().getBreastCrotchRawLactationRegenerationValue()>=FluidRegeneration.FOUR_VERY_RAPID.getMaximumRegenerationValuePerDay(),
				FLUID_REGEN_INCREMENT_SMALL,
				FLUID_REGEN_INCREMENT_AVERAGE,
				FLUID_REGEN_INCREMENT_LARGE);
	}

	public static String getSelfTransformLactationFlavourDiv() {
		contentSB.setLength(0);

		for(FluidFlavour flavour : FluidFlavour.values()) {
			if(BodyChanging.getTarget().getMilkFlavour().equals(flavour)) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(flavour.getName())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(flavour.getName())+""
						+ "
");
			}
		}

		return applyWrapper("Milk Flavour",
				UtilText.parse(BodyChanging.getTarget(), "Change the flavour of [npc.namePos] milk."
							+ "
This will affect descriptions both in and out of sex."),
				"MILK_FLAVOUR",
				contentSB.toString(),
				false);
	}

	public static String getSelfTransformLactationCrotchFlavourDiv() {
		contentSB.setLength(0);

		for(FluidFlavour flavour : FluidFlavour.values()) {
			if(BodyChanging.getTarget().getMilkCrotchFlavour().equals(flavour)) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(flavour.getName())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(flavour.getName())+""
						+ "
");
			}
		}

		return applyWrapper("Milk Flavour",
				UtilText.parse(BodyChanging.getTarget(), "Change the flavour of [npc.namePos] [npc.crotchMilk]."
							+ "
This will affect descriptions both in and out of sex."),
				"MILK_CROTCH_FLAVOUR",
				contentSB.toString(),
				false);
	}

	public static String getSelfTransformNippleElasticityDiv() {
		contentSB.setLength(0);

		for(OrificeElasticity value : OrificeElasticity.values()) {
			if(BodyChanging.getTarget().getNippleElasticity() == value) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(value.getDescriptor())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(value.getDescriptor())+""
						+ "
");
			}
		}

		return applyWrapper("Nipple Elasticity",
				UtilText.parse(BodyChanging.getTarget(), "Change the elasticity of [npc.namePos] nipples."
						+ "
Elasticity of an orifice determines how quickly it stretches out if a penetrating object is too big for it."),
				"NIPPLE_ELASTICITY",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformNippleCrotchElasticityDiv() {
		contentSB.setLength(0);

		for(OrificeElasticity value : OrificeElasticity.values()) {
			if(BodyChanging.getTarget().getNippleCrotchElasticity() == value) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(value.getDescriptor())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(value.getDescriptor())+""
						+ "
");
			}
		}

		return applyWrapper("Nipple Elasticity",
				UtilText.parse(BodyChanging.getTarget(), "Change the elasticity of [npc.namePos] [npc.crotchNipples]."
							+ "
Elasticity of an orifice determines how quickly it stretches out if a penetrating object is too big for it."),
				"NIPPLE_CROTCH_ELASTICITY",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformNipplePlasticityDiv() {
		contentSB.setLength(0);

		for(OrificePlasticity value : OrificePlasticity.values()) {
			if(BodyChanging.getTarget().getNipplePlasticity() == value) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(value.getDescriptor())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(value.getDescriptor())+""
						+ "
");
			}
		}

		return applyWrapper("Nipple Plasticity",
				UtilText.parse(BodyChanging.getTarget(), "Change the plasticity of [npc.namePos] nipples."
						+ "
Plasticity of an orifice determines how quickly, and by how much, it regains its original tightness after being stretched out."),
				"NIPPLE_PLASTICITY",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformNippleCrotchPlasticityDiv() {
		contentSB.setLength(0);

		for(OrificePlasticity value : OrificePlasticity.values()) {
			if(BodyChanging.getTarget().getNippleCrotchPlasticity() == value) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(value.getDescriptor())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(value.getDescriptor())+""
						+ "
");
			}
		}

		return applyWrapper("Nipple Plasticity",
				UtilText.parse(BodyChanging.getTarget(), "Change the plasticity of [npc.namePos] [npc.crotchNipples]."
							+ "
Plasticity of an orifice determines how quickly, and by how much, it regains its original tightness after being stretched out."),
				"NIPPLE_CROTCH_PLASTICITY",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformNippleModifiersDiv() {
		contentSB.setLength(0);

		for(OrificeModifier orificeMod : OrificeModifier.values()) {
			if(BodyChanging.getTarget().hasNippleOrificeModifier(orificeMod)) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(orificeMod.getName())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(orificeMod.getName())+""
						+ "
");
			}
		}

		return applyWrapper("Nipple Modifiers",
				UtilText.parse(BodyChanging.getTarget(), "Change the modifiers for [npc.namePos] nipples."
							+ "
Orifice modifiers affect descriptions and some actions in sex."),
				"NIPPLE_MODS",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformNippleCrotchModifiersDiv() {
		contentSB.setLength(0);

		for(OrificeModifier orificeMod : OrificeModifier.values()) {
			if(BodyChanging.getTarget().hasNippleCrotchOrificeModifier(orificeMod)) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(orificeMod.getName())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(orificeMod.getName())+""
						+ "
");
			}
		}

		return applyWrapper("Nipple Modifiers",
				UtilText.parse(BodyChanging.getTarget(), "Change the modifiers for [npc.namePos] [npc.crotchNipples]."
							+ "
Orifice modifiers affect descriptions and some actions in sex."),
				"NIPPLE_CROTCH_MODS",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformVaginaChoiceDiv(List<Race> availableRaces) {
		contentSB.setLength(0);

		for(VaginaType vagina : VaginaType.values()) {
			if((vagina.getRace() !=null && availableRaces.contains(vagina.getRace()))
					|| vagina==VaginaType.NONE) {

				Colour c = Colour.TEXT_GREY;

				if(vagina.getRace() != null) {
					c = vagina.getRace().getColour();
				}

				if(BodyChanging.getTarget().getVaginaType() == vagina) {
					contentSB.append(
							"
"
								+ ""+Util.capitaliseSentence(vagina.getTransformName())+""
							+ "
");

				} else {
					contentSB.append(
							"
"
								+ ""
									+Util.capitaliseSentence(vagina.getTransformName())
								+""
							+ "
");
				}
			}
		}

		return applyWrapper("Vagina",
				UtilText.parse(BodyChanging.getTarget(), "Change [npc.namePos] vagina type."
					+ "
Vagina type affects the modifiers and other attributes of the vagina which characters spawn in with, as well as descriptions both in and out of sex.")
					+(BodyChanging.getTarget().isPregnant()
						?UtilText.parse(BodyChanging.getTarget(), " [style.italicsBad(Vagina type cannot be changed while [npc.nameIsFull] pregnant!)]")
						:BodyChanging.getTarget().hasStatusEffect(StatusEffect.PREGNANT_0)
							?UtilText.parse(BodyChanging.getTarget(), " [style.italicsBad(Vagina type cannot be changed while [npc.name] [npc.has] the possibility of being pregnant!)]")
							:""),
				"VAGINA_TYPE",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformGirlcumFlavourDiv() {
		contentSB.setLength(0);

		for(FluidFlavour flavour : FluidFlavour.values()) {
			if(BodyChanging.getTarget().getGirlcumFlavour().equals(flavour)) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(flavour.getName())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(flavour.getName())+""
						+ "
");
			}
		}

		return applyWrapper("Girlcum Flavour",
				UtilText.parse(BodyChanging.getTarget(), "Change the flavour of [npc.namePos] girlcum."
							+ "
This will affect descriptions both in and out of sex."),
				"GIRLCUM_FLAVOUR",
				contentSB.toString(),
				false);
	}

	public static String getSelfTransformVaginaCapacityDiv() {
		contentSB.setLength(0);

		for(Capacity value : Capacity.values()) {
			if(BodyChanging.getTarget().getVaginaCapacity() == value) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(value.getDescriptor())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(value.getDescriptor())+""
						+ "
");
			}
		}

		return applyWrapper("Vagina Capacity",
				UtilText.parse(BodyChanging.getTarget(), "Change the capacity of [npc.namePos] vagina."
							+ "
This affects the size of penetrative objects that [npc.namePos] pussy can take. If capacity is too small or too large for a penetrative object, arousal gains are affected."),
				"VAGINA_CAPACITY",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformVaginaWetnessDiv() {
		contentSB.setLength(0);

		for(Wetness value : Wetness.values()) {
			if(BodyChanging.getTarget().getVaginaWetness() == value) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(value.getDescriptor())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(value.getDescriptor())+""
						+ "
");
			}
		}

		return applyWrapper("Vagina Wetness",
				UtilText.parse(BodyChanging.getTarget(), "Change the wetness of [npc.namePos] vagina."
							+ "
This affects pleasure in sex, as non-lubricated orifices negatively affect arousal gains."),
				"VAGINA_WETNESS",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformVaginaElasticityDiv() {
		contentSB.setLength(0);

		for(OrificeElasticity value : OrificeElasticity.values()) {
			if(BodyChanging.getTarget().getVaginaElasticity() == value) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(value.getDescriptor())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(value.getDescriptor())+""
						+ "
");
			}
		}

		return applyWrapper("Vagina Elasticity",
				UtilText.parse(BodyChanging.getTarget(), "Change the elasticity of [npc.namePos] vagina."
							+ "
Elasticity of an orifice determines how quickly it stretches out if a penetrating object is too big for it."),
				"VAGINA_ELASTICITY",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformVaginaPlasticityDiv() {
		contentSB.setLength(0);

		for(OrificePlasticity value : OrificePlasticity.values()) {
			if(BodyChanging.getTarget().getVaginaPlasticity() == value) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(value.getDescriptor())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(value.getDescriptor())+""
						+ "
");
			}
		}

		return applyWrapper("Vagina Plasticity",
				UtilText.parse(BodyChanging.getTarget(), "Change the plasticity of [npc.namePos] vagina."
							+ "
Plasticity of an orifice determines how quickly, and by how much, it regains its original tightness after being stretched out."),
				"VAGINA_PLASTICITY",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformClitorisSizeDiv() {
		contentSB.setLength(0);

		for(ClitorisSize size : ClitorisSize.values()) {
			if(BodyChanging.getTarget().getVaginaClitorisSize() == size) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(size.getDescriptor())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(size.getDescriptor())+""
						+ "
");
			}
		}

		return applyWrapper("Clitoris Size",
				UtilText.parse(BodyChanging.getTarget(), "Change the size of [npc.namePos] clitoris."
						+ "
All non-zero values enable [npc.namePos] clitoris to be used as a penetrative object in sex."),
				"CLITORIS_SIZE",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformLabiaSizeDiv() {
		contentSB.setLength(0);

		for(LabiaSize size : LabiaSize.values()) {
			if(BodyChanging.getTarget().getVaginaLabiaSize() == size) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(size.getName())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(size.getName())+""
						+ "
");
			}
		}

		return applyWrapper("Labia Size",
				UtilText.parse(BodyChanging.getTarget(), "Change the size of [npc.namePos] labia."
						+ "
This is a purely cosmetic change, affecting descriptions in and out of sex."),
				"LABIA_SIZE",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformVaginaModifiersDiv() {
		contentSB.setLength(0);

		for(OrificeModifier orificeMod : OrificeModifier.values()) {
			if(BodyChanging.getTarget().hasVaginaOrificeModifier(orificeMod)) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(orificeMod.getName())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(orificeMod.getName())+""
						+ "
");
			}
		}

		return applyWrapper("Vagina Modifiers",
				UtilText.parse(BodyChanging.getTarget(), "Change the modifiers for [npc.namePos] vagina."
							+ "
Orifice modifiers affect descriptions and some actions in sex."),
				"VAGINA_MODS",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformVaginaUrethraCapacityDiv() {
		contentSB.setLength(0);

		for(Capacity value : Capacity.values()) {
			if(BodyChanging.getTarget().getVaginaUrethraCapacity() == value) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(value.getDescriptor())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(value.getDescriptor())+""
						+ "
");
			}
		}

		return applyWrapper("Urethra Capacity",
				UtilText.parse(BodyChanging.getTarget(), "Change the capacity of [npc.namePos] urethra."
							+ "
Anything other than a value of zero qualifies [npc.namePos] vaginal urethra as being able to be penetrated during sex. (Subject to your 'urethra penetration' preference in the content options.)"),
				"VAGINA_URETHRA_CAPACITY",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformVaginaUrethraElasticityDiv() {
		contentSB.setLength(0);

		for(OrificeElasticity value : OrificeElasticity.values()) {
			if(BodyChanging.getTarget().getVaginaUrethraElasticity() == value) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(value.getDescriptor())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(value.getDescriptor())+""
						+ "
");
			}
		}

		return applyWrapper("Urethra Elasticity",
				UtilText.parse(BodyChanging.getTarget(), "Change the elasticity of [npc.namePos] urethra."
							+ "
Elasticity of an orifice determines how quickly it stretches out if a penetrating object is too big for it."),
				"VAGINA_URETHRA_ELASTICITY",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformVaginaUrethraPlasticityDiv() {
		contentSB.setLength(0);

		for(OrificePlasticity value : OrificePlasticity.values()) {
			if(BodyChanging.getTarget().getVaginaUrethraPlasticity() == value) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(value.getDescriptor())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(value.getDescriptor())+""
						+ "
");
			}
		}

		return applyWrapper("Urethra Plasticity",
				UtilText.parse(BodyChanging.getTarget(), "Change the plasticity of [npc.namePos] urethra."
							+ "
Plasticity of an orifice determines how quickly, and by how much, it regains its original tightness after being stretched out."),
				"VAGINA_URETHRA_PLASTICITY",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformVaginaUrethraModifiersDiv() {
		contentSB.setLength(0);

		for(OrificeModifier orificeMod : OrificeModifier.values()) {
			if(BodyChanging.getTarget().hasVaginaUrethraOrificeModifier(orificeMod)) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(orificeMod.getName())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(orificeMod.getName())+""
						+ "
");
			}
		}

		return applyWrapper("Urethra Modifiers",
				UtilText.parse(BodyChanging.getTarget(), "Change the modifiers for [npc.namePos] urethra."
							+ "
Orifice modifiers affect descriptions and some actions in sex."),
				"VAGINA_URETHRA_MODS",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformPenisChoiceDiv(List<Race> availableRaces, boolean halfWidth) {
		contentSB.setLength(0);

		for(PenisType penis : PenisType.values()) {
			if(((penis.getRace() !=null && availableRaces.contains(penis.getRace()))
					|| penis==PenisType.NONE)
					&& penis!=PenisType.DILDO) {

				Colour c = Colour.TEXT_GREY;

				if(penis.getRace() != null) {
					c = penis.getRace().getColour();
				}

				if(BodyChanging.getTarget().getPenisType() == penis) {
					contentSB.append(
							"
"
								+ ""+Util.capitaliseSentence(penis.getTransformName())+""
							+ "
");

				} else {
					contentSB.append(
							"
"
								+ ""+Util.capitaliseSentence(penis.getTransformName())+""
							+ "
");
				}
			}
		}

		return applyWrapper("Penis",
				UtilText.parse(BodyChanging.getTarget(), "Change [npc.namePos] penis type."
						+ "
Penis type affects the modifiers and other attributes of the penis which characters spawn in with, as well as descriptions both in and out of sex."),
				"PENIS_TYPE",
				contentSB.toString(),
				halfWidth);
	}

	public static String getSelfTransformPenisSizeDiv() {
		return applyFullVariableWrapperSizes("Penis Size",
				(BodyChanging.getTarget().isPlayer()
						?"Change the size of your penis."
						:UtilText.parse(BodyChanging.getTarget(), "Change the size of [npc.namePos] penis.")),
				"PENIS_SIZE",
				BodyChanging.getTarget().getPenisRawSizeValue(),
				BodyChanging.getTarget().getPenisRawSizeValue()<=0,
				BodyChanging.getTarget().getPenisRawSizeValue()>=PenisSize.SEVEN_STALLION.getMaximumValue());
	}

	public static String getSelfTransformPenisGirthDiv() {
		contentSB.setLength(0);

		for(PenisGirth girth : PenisGirth.values()) {
			if(BodyChanging.getTarget().getPenisGirth() == girth) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(girth.getName())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(girth.getName())+""
						+ "
");
			}
		}

		return applyWrapper("Penis Girth",
				UtilText.parse(BodyChanging.getTarget(), "Change the girth of [npc.namePos] penis."
						+ "
Penis girth has an impact in determining whether a penis is too large for the orifice it is penetrating."),
				"PENIS_GIRTH",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformTesticleSizeDiv() {
		contentSB.setLength(0);

		for(TesticleSize size : TesticleSize.values()) {
			if(BodyChanging.getTarget().getTesticleSize() == size) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(size.getDescriptor())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(size.getDescriptor())+""
						+ "
");
			}
		}

		return applyWrapper("Testicle Size",
				UtilText.parse(BodyChanging.getTarget(), "Change the size of [npc.namePos] testicles."
						+ "
Testicle size is a cosmetic transformation, and affects descriptions both in and out of sex."),
				"TESTICLE_SIZE",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformTesticleCountDiv() {
		contentSB.setLength(0);

		for(int i=Testicle.MIN_TESTICLE_COUNT; i<=Testicle.MAX_TESTICLE_COUNT; i+=2) {
			if(BodyChanging.getTarget().getTesticleCount() == i) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(Util.intToString(i))+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(Util.intToString(i))+""
						+ "
");
			}
		}

		return applyWrapper("Testicle Count",
				UtilText.parse(BodyChanging.getTarget(), "Change how many testicles [npc.name] has."
							+ "
Testicle count is a cosmetic transformation, and affects descriptions both in and out of sex."),
				"TESTICLE_COUNT",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformInternalTesticleDiv() {
		contentSB.setLength(0);

		if(BodyChanging.getTarget().isInternalTesticles()) {
			contentSB.append(
					"
"
							+ "External"
					+ "
"
					+"
"
						+ "Internal"
					+ "
");
		} else {
			contentSB.append(
					"
"
							+ "External"
					+ "
"
					+"
"
						+ "Internal"
					+ "
");
		}

		return applyWrapper("Internal Testicles",
				UtilText.parse(BodyChanging.getTarget(), "Set whether [npc.namePos] testicles are internal or not."
						+ "
This affects descriptions and the availability of some actions in sex. (On futa characters, it is subject to your 'futanari testicles' content option.)"),
				"INTERNAL_TESTICLES",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformUrethraCapacityDiv() {
		contentSB.setLength(0);

		for(Capacity value : Capacity.values()) {
			if(BodyChanging.getTarget().getPenisCapacity() == value) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(value.getDescriptor())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(value.getDescriptor())+""
						+ "
");
			}
		}

		return applyWrapper("Urethra Capacity",
				UtilText.parse(BodyChanging.getTarget(), "Change the capacity of [npc.namePos] urethra."
							+ "
Anything other than a value of zero qualifies [npc.namePos] penile urethra as being able to be penetrated during sex. (Subject to your 'urethra penetration' preference in the content options.)"),
				"PENIS_URETHRA_CAPACITY",
				contentSB.toString(),
				true);
	}

	public static int getCumUpperLimit() {
		if(Main.game.isInNewWorld()) {
			return CumProduction.SEVEN_MONSTROUS.getMaximumValue();
		} else {
			return 30;
		}
	}

	public static String getSelfTransformCumProductionDiv() {
		return applyVariableWrapperFluids("Cum Storage",
				UtilText.parse(BodyChanging.getTarget(), "Change [npc.namePos] maximum cum storage."
						+ "
Once drained, [npc.namePos] balls fill with cum up to this value at a rate determined by [npc.her] cum regeneration value."),
				"CUM_PRODUCTION",
				Util.capitaliseSentence(BodyChanging.getTarget().getPenisCumStorage().getName())
					+"
("+Units.fluid(BodyChanging.getTarget().getPenisRawCumStorageValue(), ValueType.PRECISE)+")",
				BodyChanging.getTarget().getPenisRawCumStorageValue()<=0,
				BodyChanging.getTarget().getPenisRawCumStorageValue()>=getCumUpperLimit(),
				FLUID_INCREMENT_SMALL,
				FLUID_INCREMENT_AVERAGE,
				FLUID_INCREMENT_LARGE);
	}

	public static String getSelfTransformCumRegenerationDiv() {
		return applyVariableWrapperFluids("Cum Regeneration",
				UtilText.parse(BodyChanging.getTarget(), "Alter the rate at which [npc.name] [npc.verb(produce)] cum."
						+ "
Once drained, [npc.namePos] balls fill with cum up to their maximum storage value at this rate."),
				"CUM_REGENERATION",
				Units.fluid(BodyChanging.getTarget().getPenisRawCumProductionRegenerationValue(), ValueType.PRECISE)+"/day"
					+"
("+Units.fluid(BodyChanging.getTarget().getCumRegenerationPerSecond()*60, ValueType.PRECISE)+"/minute)"
					+"
"+Util.capitaliseSentence(BodyChanging.getTarget().getPenisCumProductionRegeneration().getName())+"",
				BodyChanging.getTarget().getPenisRawCumProductionRegenerationValue()<=0,
				BodyChanging.getTarget().getPenisRawCumProductionRegenerationValue()>=FluidRegeneration.FOUR_VERY_RAPID.getMaximumRegenerationValuePerDay(),
				FLUID_REGEN_INCREMENT_SMALL,
				FLUID_REGEN_INCREMENT_AVERAGE,
				FLUID_REGEN_INCREMENT_LARGE);
	}

	public static String getSelfTransformCumExplusionDiv() {
		return applyFullVariableWrapper("Cum Expulsion",
				UtilText.parse(BodyChanging.getTarget(), "Alter how much of [npc.namePos] stored cum is expelled at each orgasm."
						+ "
If the amount of stored cum is less than or equal to "+Units.fluid(Testicle.MINIMUM_VALUE_FOR_ALL_CUM_TO_BE_EXPELLED)+", 100% of cum left in [npc.namePos] will be expelled."),
				"CUM_EXPULSION",
				"1",
				"10",
				BodyChanging.getTarget().getPenisRawCumExpulsionValue()+"%"
					+"
"+Util.capitaliseSentence(BodyChanging.getTarget().getPenisCumExpulsion().getDescriptor())+"",
				BodyChanging.getTarget().getPenisRawCumExpulsionValue()<=5,
				BodyChanging.getTarget().getPenisRawCumExpulsionValue()>=100);
	}

	public static String getSelfTransformCumFlavourDiv() {
		contentSB.setLength(0);

		for(FluidFlavour flavour : FluidFlavour.values()) {
			if(BodyChanging.getTarget().getCumFlavour().equals(flavour)) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(flavour.getName())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(flavour.getName())+""
						+ "
");
			}
		}

		return applyWrapper("Cum Flavour",
				UtilText.parse(BodyChanging.getTarget(), "Change the flavour of [npc.namePos] cum."
							+ "
This will affect descriptions both in and out of sex."),
				"CUM_FLAVOUR",
				contentSB.toString(),
				false);
	}

	public static String getSelfTransformUrethraElasticityDiv() {
		contentSB.setLength(0);

		for(OrificeElasticity value : OrificeElasticity.values()) {
			if(BodyChanging.getTarget().getUrethraElasticity() == value) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(value.getDescriptor())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(value.getDescriptor())+""
						+ "
");
			}
		}

		return applyWrapper("Urethra Elasticity",
				UtilText.parse(BodyChanging.getTarget(), "Change the elasticity of [npc.namePos] urethra."
							+ "
Elasticity of an orifice determines how quickly it stretches out if a penetrating object is too big for it."),
				"PENIS_URETHRA_ELASTICITY",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformUrethraPlasticityDiv() {
		contentSB.setLength(0);

		for(OrificePlasticity value : OrificePlasticity.values()) {
			if(BodyChanging.getTarget().getUrethraPlasticity() == value) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(value.getDescriptor())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(value.getDescriptor())+""
						+ "
");
			}
		}

		return applyWrapper("Urethra Plasticity",
				UtilText.parse(BodyChanging.getTarget(), "Change the plasticity of [npc.namePos] urethra."
							+ "
Plasticity of an orifice determines how quickly, and by how much, it regains its original tightness after being stretched out."),
				"PENIS_URETHRA_PLASTICITY",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformPenisModifiersDiv() {
		contentSB.setLength(0);

		for(PenetrationModifier orificeMod : PenetrationModifier.values()) {
			if(BodyChanging.getTarget().hasPenisModifier(orificeMod)) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(orificeMod.getName())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(orificeMod.getName())+""
						+ "
");
			}
		}

		return applyWrapper("Penis Modifiers",
				UtilText.parse(BodyChanging.getTarget(), "Change the modifiers for [npc.namePos] penis."
							+ "
Penis modifiers affect descriptions and some actions in sex."),
				"PENIS_MODS",
				contentSB.toString(),
				true);
	}

	public static String getSelfTransformUrethraModifiersDiv() {
		contentSB.setLength(0);

		for(OrificeModifier orificeMod : OrificeModifier.values()) {
			if(BodyChanging.getTarget().hasUrethraOrificeModifier(orificeMod)) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(orificeMod.getName())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(orificeMod.getName())+""
						+ "
");
			}
		}

		return applyWrapper("Urethra Modifiers",
				UtilText.parse(BodyChanging.getTarget(), "Change the modifiers for [npc.namePos] urethra."
							+ "
Orifice modifiers affect descriptions and some actions in sex."),
				"PENIS_URETHRA_MODS",
				contentSB.toString(),
				true);
	}

	public static String getBodySizeChoiceDiv() {
		contentSB.setLength(0);

		for(BodySize bs : BodySize.values()) {
			if( BodyChanging.getTarget().getBodySize() == bs) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(bs.getName(false))+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(bs.getName(false))+""
						+ "
");
			}
		}

		return applyWrapper("Body Size",
				UtilText.parse(BodyChanging.getTarget(), "Change [npc.namePos] body size."
						+ "
This determines how much body fat [npc.namePos] [npc.has], and is a purely cosmetic transformation."),
				"BODY_SIZE",
				contentSB.toString(),
				true);
	}

	public static String getMuscleChoiceDiv() {
		contentSB.setLength(0);

		for(Muscle muscle : Muscle.values()) {
			if( BodyChanging.getTarget().getMuscle() == muscle) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(muscle.getName(false))+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(muscle.getName(false))+""
						+ "
");
			}
		}

		return applyWrapper("Muscle Definition",
				UtilText.parse(BodyChanging.getTarget(), "Change [npc.namePos] muscle definition."+(!Main.game.isInNewWorld()?" This does not affect the physique attribute of your character.":"")
							+ "
This determines how much muscle [npc.namePos] [npc.has], and is a purely cosmetic transformation."),
				"MUSCLE",
				contentSB.toString(),
				true);
	}

	public static String getLipSizeDiv() {
		contentSB.setLength(0);

		contentSB.append(
				"
"
					+"
"
						+ "
"
							+"Lip Size"
						+"
"
						+ "
"
							+ "Choose how large your lips are."
						+ "
"
						+ "
"
						+ "
");

		for(LipSize ls : LipSize.values()) {
			if(!ls.isImpedesSpeech()) {
				if(BodyChanging.getTarget().getLipSize() == ls) {
					contentSB.append(
							"
"
								+ ""+Util.capitaliseSentence(ls.getName())+""
							+ "
");

				} else {
					contentSB.append(
							"
"
								+ ""+Util.capitaliseSentence(ls.getName())+""
							+ "
");
				}
			}
		}

		contentSB.append("

");

		return contentSB.toString();
	}

	public static String getLipPuffynessDiv() {
		contentSB.setLength(0);

		contentSB.append(
				"
"
					+ "
"
						+ "
"
							+"Puffy Lips"
						+"
"
						+ "
"
							+ "Choose whether your lips are extra puffy or not."
						+ "
"
					+ "
"
					+ "
");

		if(BodyChanging.getTarget().hasFaceOrificeModifier(OrificeModifier.PUFFY)) {
			contentSB.append(
					"
"
							+ "Normal"
					+ "
"
					+ "
"
						+ "[style.boldGood(Puffy)]"
					+ "
");
		} else {
			contentSB.append(
					"
"
						+ "[style.boldGood(Normal)]"
					+ "
"
					+ "
"
						+ "Puffy"
					+ "
");
		}

		contentSB.append(
				"
"
			+ "
");

		return contentSB.toString();
	}

	public static CupSize[] getBreastSizesAvailable() {
		if(BodyChanging.getTarget().hasPenis()) {
			return new CupSize[] {CupSize.FLAT, CupSize.TRAINING_AAA, CupSize.TRAINING_AA, CupSize.TRAINING_A};
		} else {
			return new CupSize[] {CupSize.AA, CupSize.A, CupSize.B, CupSize.C, CupSize.D, CupSize.DD, CupSize.E};
		}
	}

	public static String getBreastSizeDiv() {
		contentSB.setLength(0);

		contentSB.append(
				"
"
					+"
"
						+ "
"
							+"Breast Size"
						+"
"
						+ "
"
							+ "Choose how large your breasts are, in cup size."
						+ "
"
						+ "
"
						+ "
");

		CupSize[] sizesAvailable = getBreastSizesAvailable();

		for(CupSize cs : sizesAvailable) {
			if(BodyChanging.getTarget().getBreastSize() == cs) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(cs.getCupSizeName())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(cs.getCupSizeName())+""
						+ "
");
			}
		}

		contentSB.append("

");

		return contentSB.toString();
	}

	public static String getBreastShapeDiv() {
		contentSB.setLength(0);

		contentSB.append(
				"
"
					+"
"
						+ "
"
							+"Breast Shape"
						+"
"
						+ "
"
							+ "Choose the shape of your breasts."
						+ "
"
						+ "
"
						+ "
");

		for(BreastShape bs : BreastShape.values()) {
			if(!bs.isRestrictedToCrotchBoobs()) {
				if(BodyChanging.getTarget().getBreastShape() == bs) {
					contentSB.append(
							"
"
								+ ""+Util.capitaliseSentence(bs.getTransformName())+""
							+ "
");

				} else {
					contentSB.append(
							"
"
								+ ""+Util.capitaliseSentence(bs.getTransformName())+""
							+ "
");
				}
			}
		}

		contentSB.append("

");

		return contentSB.toString();
	}

	public static NippleSize[] getNippleSizesAvailable() {
		if(BodyChanging.getTarget().hasPenis()) {
			return new NippleSize[] {NippleSize.ZERO_TINY, NippleSize.ONE_SMALL, NippleSize.TWO_BIG};
		} else {
			return new NippleSize[] {NippleSize.ZERO_TINY, NippleSize.ONE_SMALL, NippleSize.TWO_BIG, NippleSize.THREE_LARGE, NippleSize.FOUR_MASSIVE};
		}
	}

	public static String getNippleSizeDiv() {
		contentSB.setLength(0);

		contentSB.append(
				"
"
					+"
"
						+ "
"
							+"Nipple Size"
						+"
"
						+ "
"
							+ "Choose how large your nipples are."
						+ "
"
						+ "
"
						+ "
");

		NippleSize[] sizesAvailable = getNippleSizesAvailable();

		for(NippleSize ns : sizesAvailable) {
			if(BodyChanging.getTarget().getNippleSize() == ns) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(ns.getName())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(ns.getName())+""
						+ "
");
			}
		}

		contentSB.append("

");

		return contentSB.toString();
	}

	public static AreolaeSize[] getAreolaeSizesAvailable() {
		if(BodyChanging.getTarget().hasPenis()) {
			return new AreolaeSize[] {AreolaeSize.ZERO_TINY, AreolaeSize.ONE_SMALL, AreolaeSize.TWO_BIG};
		} else {
			return new AreolaeSize[] {AreolaeSize.ZERO_TINY, AreolaeSize.ONE_SMALL, AreolaeSize.TWO_BIG, AreolaeSize.THREE_LARGE, AreolaeSize.FOUR_MASSIVE};
		}
	}

	public static String getAreolaeSizeDiv() {
		contentSB.setLength(0);

		contentSB.append(
				"
"
					+"
"
						+ "
"
							+"Areolae Size"
						+"
"
						+ "
"
							+ "Choose how large your areolae are."
						+ "
"
						+ "
"
						+ "
");

		AreolaeSize[] sizesAvailable = getAreolaeSizesAvailable();

		for(AreolaeSize as : sizesAvailable) {
			if(BodyChanging.getTarget().getAreolaeSize() == as) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(as.getName())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(as.getName())+""
						+ "
");
			}
		}

		contentSB.append("

");

		return contentSB.toString();
	}

	public static String getNipplePuffynessDiv() {
		contentSB.setLength(0);

		contentSB.append(
				"
"
					+ "
"
						+ "
"
							+"Puffy Nipples"
						+"
"
						+ "
"
							+ "Choose whether your nipples are extra puffy or not."
						+ "
"
					+ "
"
					+ "
");

		if(BodyChanging.getTarget().hasNippleOrificeModifier(OrificeModifier.PUFFY)) {
			contentSB.append(
					"
"
							+ "Normal"
					+ "
"
					+ "
"
						+ "[style.boldGood(Puffy)]"
					+ "
");
		} else {
			contentSB.append(
					"
"
						+ "[style.boldGood(Normal)]"
					+ "
"
					+ "
"
						+ "Puffy"
					+ "
");
		}

		contentSB.append(
				"
"
			+ "
");

		return contentSB.toString();
	}

	public static AssSize[] getAssSizesAvailable() {
		if(BodyChanging.getTarget().hasPenis()) {
			return new AssSize[] {AssSize.ZERO_FLAT, AssSize.ONE_TINY, AssSize.TWO_SMALL, AssSize.THREE_NORMAL, AssSize.FOUR_LARGE};
		} else {
			return AssSize.values();
		}
	}

	public static String getAssSizeDiv() {
		contentSB.setLength(0);

		contentSB.append(
				"
"
					+"
"
						+ "
"
							+"Ass Size"
						+"
"
						+ "
"
							+ "Choose how large your ass is."
						+ "
"
						+ "
"
						+ "
");

		AssSize[] sizesAvailable = getAssSizesAvailable();

		for(AssSize as : sizesAvailable) {
			if(BodyChanging.getTarget().getAssSize() == as) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(as.getDescriptor())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(as.getDescriptor())+""
						+ "
");
			}
		}

		contentSB.append("

");

		return contentSB.toString();
	}

	public static HipSize[] getHipSizesAvailable() {
		if(BodyChanging.getTarget().hasPenis()) {
			return new HipSize[] {HipSize.ZERO_NO_HIPS, HipSize.ONE_VERY_NARROW, HipSize.TWO_NARROW, HipSize.THREE_GIRLY};
		} else {
			return HipSize.values();
		}
	}

	public static String getHipSizeDiv() {
		contentSB.setLength(0);

		contentSB.append(
				"
"
					+"
"
						+ "
"
							+"Hip Size"
						+"
"
						+ "
"
							+ "Choose how large your hips are."
						+ "
"
						+ "
"
						+ "
");

		HipSize[] sizesAvailable = getHipSizesAvailable();

		for(HipSize hs : sizesAvailable) {
			if(BodyChanging.getTarget().getHipSize() == hs) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(hs.getDescriptor())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(hs.getDescriptor())+""
						+ "
");
			}
		}

		contentSB.append("

");

		return contentSB.toString();
	}

	public static String getBleachedAnusDiv() {
		contentSB.setLength(0);

		contentSB.append(
				"
"
					+ "
"
						+ "
"
							+"Bleached Anus"
						+"
"
						+ "
"
							+ "Choose whether your anus has been bleached to have the same skin tone as the rest of your body."
						+ "
"
					+ "
"
					+ "
");

		if(BodyChanging.getTarget().isAssBleached()) {
			contentSB.append(
					"
"
							+ "Normal"
					+ "
"
					+ "
"
						+ "[style.boldGood(Bleached)]"
					+ "
");
		} else {
			contentSB.append(
					"
"
						+ "[style.boldGood(Normal)]"
					+ "
"
					+ "
"
						+ "Bleached"
					+ "
");
		}

		contentSB.append(
				"
"
			+ "
");

		return contentSB.toString();
	}

	public static int[] getPenisSizesAvailable() {
		return new int[] {3, 5, 8, 10, 12, 15, 18, 20};
	}

	public static String getPenisSizeDiv() {
		contentSB.setLength(0);

		contentSB.append(
				"
"
					+"
"
						+ "
"
							+"Penis Size"
						+"
"
						+ "
"
							+ "Choose how large your penis is."
						+ "
"
						+ "
"
						+ "
");

		int[] sizesAvailable = getPenisSizesAvailable();

		for(int size : sizesAvailable) {
			if(BodyChanging.getTarget().getPenisRawSizeValue() == size) {
				contentSB.append(
						"
"
							+ ""+Units.size(size)+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Units.size(size)+""
						+ "
");
			}
		}

		contentSB.append("

");

		return contentSB.toString();
	}

	public static TesticleSize[] getTesticleSizesAvailable() {
		return new TesticleSize[] {TesticleSize.ZERO_VESTIGIAL, TesticleSize.ONE_TINY, TesticleSize.TWO_AVERAGE, TesticleSize.THREE_LARGE};
	}

	public static String getTesticleSizeDiv() {
		contentSB.setLength(0);

		contentSB.append(
				"
"
					+"
"
						+ "
"
							+"Testicle Size"
						+"
"
						+ "
"
							+ "Choose how large your testicles are."
						+ "
"
						+ "
"
						+ "
");

		TesticleSize[] sizesAvailable = getTesticleSizesAvailable();

		for(TesticleSize size : sizesAvailable) {
			if(BodyChanging.getTarget().getTesticleSize() == size) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(size.getDescriptor())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(size.getDescriptor())+""
						+ "
");
			}
		}

		contentSB.append("

");

		return contentSB.toString();
	}

	public static String getVaginaCapacityDiv() {
		contentSB.setLength(0);

		contentSB.append(
				"
"
					+"
"
						+ "
"
							+"Vagina Capacity"
						+"
"
						+ "
"
							+ "Select the capacity of your vagina. A higher capacity means that you'll be able to take larger insertions easier, but if it's too loose, it won't be very pleasurable for partners with small cocks."
						+ "
"
						+ "
"
						+ "
");

		for(Capacity value : Capacity.values()) {
			if(BodyChanging.getTarget().getVaginaCapacity() == value) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(value.getDescriptor())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(value.getDescriptor())+""
						+ "
");
			}
		}

		contentSB.append("

");

		return contentSB.toString();
	}

	public static String getClitorisSizeDiv() {
		contentSB.setLength(0);

		contentSB.append(
				"
"
					+"
"
						+ "
"
							+"Clitoris Size"
						+"
"
						+ "
"
							+ "Choose how large your clitoris is."
						+ "
"
						+ "
"
						+ "
");

		ClitorisSize[] sizesAvailable = new ClitorisSize[] {ClitorisSize.ZERO_AVERAGE, ClitorisSize.ONE_BIG};

		for(ClitorisSize size : sizesAvailable) {
			if(BodyChanging.getTarget().getVaginaClitorisSize() == size) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(size.getDescriptor())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(size.getDescriptor())+""
						+ "
");
			}
		}

		contentSB.append("

");

		return contentSB.toString();
	}

	public static String getLabiaSizeDiv() {
		contentSB.setLength(0);

		contentSB.append(
				"
"
					+"
"
						+ "
"
							+"Labia Size"
						+"
"
						+ "
"
							+ "Choose how large your labia are."
						+ "
"
						+ "
"
						+ "
");

		for(LabiaSize size : LabiaSize.values()) {
			if(BodyChanging.getTarget().getVaginaLabiaSize() == size) {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(size.getName())+""
						+ "
");

			} else {
				contentSB.append(
						"
"
							+ ""+Util.capitaliseSentence(size.getName())+""
						+ "
");
			}
		}

		contentSB.append("

");

		return contentSB.toString();
	}

	// ---------------------- Kate's Shop: ---------------------- //

	public static String getKatesDivHairLengths(boolean withCost, String title, String description) {
		contentSB.setLength(0);

		boolean noCost = !withCost;

		contentSB.append(
				"
"
					+ "
"
						+ "
"
							+title
							+(noCost
									?""
									:" "+(Main.game.getPlayer().getMoney()>=SuccubisSecrets.BASE_HAIR_LENGTH_COST
											? UtilText.formatAsMoney(SuccubisSecrets.BASE_HAIR_LENGTH_COST, "b")
											: UtilText.formatAsMoney(SuccubisSecrets.BASE_HAIR_LENGTH_COST, "b", Colour.GENERIC_BAD)))
						+"
"
						+ "
"
							+ description
						+ "
"
					+ "
"
					+ "
");

		for (HairLength hairLength : HairLength.values()) {
			if (BodyChanging.getTarget().getHairLength() == hairLength) {
				contentSB.append(
						"
"
							+ "" + Util.capitaliseSentence(hairLength.getDescriptor()) + ""
						+ "
");
			} else {
				contentSB.append(
						"
"
								+ (Main.game.getPlayer().getMoney()>=SuccubisSecrets.BASE_HAIR_LENGTH_COST || noCost
									? "" + Util.capitaliseSentence(hairLength.getDescriptor()) + ""
									: "[style.colourDisabled(" + Util.capitaliseSentence(hairLength.getDescriptor()) + ")]")
						+ "
");
			}
		}

		contentSB.append(
				"
"
			+ "
");

		return contentSB.toString();
	}

	public static String getKatesDivHairStyles(boolean withCost, String title, String description) {
		contentSB.setLength(0);

		boolean noCost = !withCost;

		contentSB.append(
				"
"
					+ "
"
						+ "
"
							+title
							+(noCost
									?""
									:" "+(Main.game.getPlayer().getMoney()>=SuccubisSecrets.BASE_HAIR_STYLE_COST
										? UtilText.formatAsMoney(SuccubisSecrets.BASE_HAIR_STYLE_COST, "b")
										: UtilText.formatAsMoney(SuccubisSecrets.BASE_HAIR_STYLE_COST, "b", Colour.GENERIC_BAD)))
						+"
"
						+ "
"
							+ description
						+ "
"
					+ "
"
					+ "
");

		for (HairStyle hairStyle : HairStyle.values()) {
			if (BodyChanging.getTarget().getHairStyle() == hairStyle) {
				contentSB.append(
						"
"
							+ "" + Util.capitaliseSentence(hairStyle.getName()) + ""
						+ "
");
			} else {
				if(BodyChanging.getTarget().getHairRawLengthValue() >= hairStyle.getMinimumLengthRequired()) {
					contentSB.append(
							"
"
									+ (Main.game.getPlayer().getMoney()>=SuccubisSecrets.BASE_HAIR_STYLE_COST || noCost
										? "" + Util.capitaliseSentence(hairStyle.getName()) + ""
										: "[style.colourDisabled(" + Util.capitaliseSentence(hairStyle.getName()) + ")]")
							+ "
");
				} else {
					contentSB.append(
							"
"
								+ "[style.colourDisabled(" + Util.capitaliseSentence(hairStyle.getName()) + ")]"
							+ "
");
				}
			}
		}

		contentSB.append(
				"
"
			+ "
");

		return contentSB.toString();
	}

	public static String getKatesDivAssHair(boolean withCost, String title, String description) {
		return getKatesDivGenericBodyHair(withCost, title, description, BodyChanging.getTarget().getAssHair(), "ASS_HAIR_", false);
	}

	public static String getKatesDivUnderarmHair(boolean withCost, String title, String description) {
		return getKatesDivGenericBodyHair(withCost, title, description, BodyChanging.getTarget().getUnderarmHair(), "UNDERARM_HAIR_", false);
	}

	public static String getKatesDivFacialHair(boolean withCost, String title, String description) {
		return getKatesDivGenericBodyHair(withCost, title, description, BodyChanging.getTarget().getFacialHair(), "FACIAL_HAIR_", BodyChanging.getTarget().isFeminine() && !Main.game.isFemaleFacialHairEnabled());
	}

	public static String getKatesDivPubicHair(boolean withCost, String title, String description) {
		return getKatesDivGenericBodyHair(withCost, title, description, BodyChanging.getTarget().getPubicHair(), "PUBIC_HAIR_", false);
	}

	private static String getKatesDivGenericBodyHair(boolean withCost, String title, String description, BodyHair activeHair, String id, boolean blockAllButNoneOptions) {
		contentSB.setLength(0);

		boolean noCost = !withCost;

		contentSB.append(
				"
"
					+ "
"
						+ "
"
							+title
							+(noCost
								?""
								:" "+(Main.game.getPlayer().getMoney()>=SuccubisSecrets.BASE_BODY_HAIR_COST
									? UtilText.formatAsMoney(SuccubisSecrets.BASE_BODY_HAIR_COST, "b")
									: UtilText.formatAsMoney(SuccubisSecrets.BASE_BODY_HAIR_COST, "b", Colour.GENERIC_BAD)))
						+"
"
						+ "
"
							+ description
						+ "
"
					+ "
"
					+ "
");

		for (BodyHair bodyHair : BodyHair.values()) {
			if (activeHair == bodyHair) {
				contentSB.append(
						"
"
							+ "" + Util.capitaliseSentence(bodyHair.getName()) + ""
						+ "
");
			} else {
				if(blockAllButNoneOptions) {
					contentSB.append(
							"
"
								+ "[style.colourDisabled(" + Util.capitaliseSentence(bodyHair.getName()) + ")]"
							+ "
");
				} else {
					contentSB.append(
							"
"
									+ (Main.game.getPlayer().getMoney()>=SuccubisSecrets.BASE_BODY_HAIR_COST || noCost
										? "" + Util.capitaliseSentence(bodyHair.getName()) + ""
										: "[style.colourDisabled(" + Util.capitaliseSentence(bodyHair.getName()) + ")]")
							+ "
");
				}
			}
		}

		contentSB.append(
				"
"
			+ "
");

		return contentSB.toString();
	}

	public static String getKatesDivGenericBodyHairDisabled(String title, String description, String disabledDescription) {
		contentSB.setLength(0);

		contentSB.append(
				"
"
					+ "
"
						+ "
"
							+title
						+"
"
						+ "
"
							+ description
						+ "
"
					+ "
"
					+ "
"
					+ "
"
						+ disabledDescription
					+ "
"
				+ "
"
			+ "
");

		return contentSB.toString();
	}

	public static String getKatesDivAnalBleaching(String title, String description) {
		contentSB.setLength(0);

		boolean noCost = !Main.game.isInNewWorld();

		contentSB.append(
				"
"
					+ "
"
						+ "
"
							+ title
							+ (noCost
									?""
									:" "+(Main.game.getPlayer().getMoney()>=SuccubisSecrets.BASE_ANAL_BLEACHING_COST
										? UtilText.formatAsMoney(SuccubisSecrets.BASE_ANAL_BLEACHING_COST, "b")
										: UtilText.formatAsMoney(SuccubisSecrets.BASE_ANAL_BLEACHING_COST, "b", Colour.GENERIC_BAD)))
						+"
"
						+ "
"
							+ description
						+ "
"
					+ "
"
					+ "
");

		if(BodyChanging.getTarget().isAssBleached()) {
			contentSB.append(
					"
"
						+ "[style.colourDisabled(Normal)]"
					+ "
"
					+ "
"
						+ "[style.boldArcane(Bleached)]"
					+ "
");
		} else {
			contentSB.append(
					"
"
						+ "Normal"
					+ "
"
					+ "
"
						+ (Main.game.getPlayer().getMoney()>=SuccubisSecrets.BASE_ANAL_BLEACHING_COST
							?"Bleached"
							:"[style.colourDisabled(Bleached)]")
					+ "
");
		}

		contentSB.append(
				"
"
			+ "
");

		return contentSB.toString();
	}

	//TODO reset on open
	private static Map<BodyCoveringType, Covering> coveringsToBeApplied = new HashMap<>();

	public static Map<BodyCoveringType, Covering> getCoveringsToBeApplied() {
		return coveringsToBeApplied;
	}

	public static String getKatesDivCoveringsNew(boolean withCost, BodyCoveringType coveringType, String title, String description, boolean withSecondary, boolean withGlow) {
		return getKatesDivCoveringsNew(withCost, coveringType, title, description, withSecondary, withGlow, true);
	}

	public static String getKatesDivCoveringsNew(boolean withCost, BodyCoveringType coveringType, String title, String description, boolean withSecondary, boolean withGlow, boolean withDyeAndExtraPatterns) {
		boolean disabledButton = !coveringsToBeApplied.containsKey(coveringType) || coveringsToBeApplied.get(coveringType).equals(BodyChanging.getTarget().getCovering(coveringType));

		contentSB = new StringBuilder(
				"
"
					+ "
"
						+ "
"
							+ ""
								+ Util.capitaliseSentence(title)
							+ ""
						+ "
"
						+ "
"
							+ "Currently:
"+BodyChanging.getTarget().getCovering(coveringType).getFullDescription(BodyChanging.getTarget(), true)+""
						+ "
"

							+ (disabledButton
									?"
"
										+"Apply Changes"
											+ (withCost
												?"
("+UtilText.formatAsMoneyUncoloured(SuccubisSecrets.getBodyCoveringTypeCost(coveringType), "b")+")"
												:"")
										+""
									+ "
"
									:"
"
										+ "Apply Changes"
										+ (withCost
											?"
("
												+(Main.game.getPlayer().getMoney()>=SuccubisSecrets.getBodyCoveringTypeCost(coveringType)
													? UtilText.formatAsMoney(SuccubisSecrets.getBodyCoveringTypeCost(coveringType), "b")
													: UtilText.formatAsMoney(SuccubisSecrets.getBodyCoveringTypeCost(coveringType), "b", Colour.GENERIC_BAD))+")"
											:"")
										+ "
")

					+ "
"
					+ "
"
					+ "Pattern:
");

		Covering activeCovering = !coveringsToBeApplied.containsKey(coveringType)
										?BodyChanging.getTarget().getCovering(coveringType)
										:coveringsToBeApplied.get(coveringType);

		List<CoveringPattern> availablePatterns = withDyeAndExtraPatterns
														?coveringType.getAllPatterns()
														:coveringType.getNaturalPatterns();
		for (CoveringPattern pattern : availablePatterns) {
			if (activeCovering.getPattern() == pattern) {
				contentSB.append(
						"
"
							+ "" + Util.capitaliseSentence(pattern.getName()) + ""
						+ "
");
			} else {
				contentSB.append(
						"
"
								+ (Main.game.getPlayer().getMoney()>=SuccubisSecrets.getBodyCoveringTypeCost(coveringType) || !withCost
									? "" + Util.capitaliseSentence(pattern.getName()) + ""
									: "[style.colourDisabled(" + Util.capitaliseSentence(pattern.getName()) + ")]")
						+ "
");
			}
		}
		contentSB.append("
");
		contentSB.append("
");
		contentSB.append( "
"
					+ "Primary Colour:
");

			List<Colour> availablePrimaryColours = new ArrayList<>(withDyeAndExtraPatterns
															?coveringType.getAllPrimaryColours()
															:coveringType.getNaturalColoursPrimary());
			int rainbowIncrement=5;
			String rainbow = "";
			Collections.sort(availablePrimaryColours, (c1, c2)->c2.isMetallic()?(c1.isMetallic()?0:-1):(c1.isMetallic()?1:0));

			for (Colour c : availablePrimaryColours) {
				int i=0;
				if(c.getRainbowColours()!=null) {
					StringBuilder sb = new StringBuilder();
					sb.append("repeating-linear-gradient(135deg,");
					for(String s : c.getRainbowColours()) {
						sb.append(" "+s+" "+(i*rainbowIncrement)+"px, "+s+" "+((i+1)*rainbowIncrement)+"px,");
						i++;
					}
					sb.deleteCharAt(sb.length()-1);
					sb.append(");");
					rainbow = sb.toString();
				} else {
					rainbow = "";
				}
				contentSB.append("
"
									+ (c.isMetallic()
											?"<div class='phone-item-colour' style='background: repeating-linear-gradient(135deg, " + c.toWebHexString() + ", " + c.getShades()[4] + " 10px);"
											:(c.getRainbowColours()!=null
												?"")
									+"
"
								+ "
");
			}
			contentSB.append("
");
			if(activeCovering.getPrimaryColour() == Colour.COVERING_NONE || !withGlow) { // Disable glow:

			} else {
				if(activeCovering.isPrimaryGlowing()) {
					contentSB.append(
							"
"
								+ "[style.boldArcane(Arcane Glow)]"
							+ "
");
				} else {
					contentSB.append(
							"
"
								+ "Arcane Glow"
							+ "
");
				}
			}
			contentSB.append("
"
						+ ""
							+Util.capitaliseSentence(activeCovering.getPrimaryColour().getName())
						+""
					+ "
");

			contentSB.append("
"
					+ "
"
					+ "Secondary Colour:
");

			List<Colour> availableSecondaryColours = new ArrayList<>(withDyeAndExtraPatterns
														?coveringType.getAllSecondaryColours()
														:coveringType.getNaturalColoursSecondary());
			Collections.sort(availableSecondaryColours, (c1, c2)->c2.isMetallic()?(c1.isMetallic()?0:-1):(c1.isMetallic()?1:0));
			for (Colour c : availableSecondaryColours) {
				if(activeCovering.getPattern()==CoveringPattern.NONE
						|| activeCovering.getPattern()==CoveringPattern.EYE_IRISES
						|| activeCovering.getPattern()==CoveringPattern.EYE_PUPILS
						|| activeCovering.getPattern()==CoveringPattern.EYE_SCLERA
						|| !withSecondary) {
					contentSB.append("
"
						+ "

"
					+ "
");

				} else {
					int i=0;
					if(c.getRainbowColours()!=null) {
						StringBuilder sb = new StringBuilder();
						sb.append("repeating-linear-gradient(135deg,");
						for(String s : c.getRainbowColours()) {
							sb.append(" "+s+" "+(i*rainbowIncrement)+"px, "+s+" "+((i+1)*rainbowIncrement)+"px,");
							i++;
						}
						sb.deleteCharAt(sb.length()-1);
						sb.append(");");
						rainbow = sb.toString();
					} else {
						rainbow = "";
					}
					contentSB.append("
"
											+ (c.isMetallic()
													?"<div class='phone-item-colour' style='background: repeating-linear-gradient(135deg, " + c.toWebHexString() + ", " + c.getShades()[4] + " 10px);"
													:(c.getRainbowColours()!=null
														?"")
											+"
"
									+ "
");
				}
			}
			contentSB.append("
");
			if(activeCovering.getSecondaryColour() == Colour.COVERING_NONE || !withGlow || activeCovering.getPattern()==CoveringPattern.NONE || !withSecondary) { // Disable glow:

			} else {
				if(activeCovering.isSecondaryGlowing()) {
					contentSB.append(
							"
"
								+ "[style.boldArcane(Arcane Glow)]"
							+ "
");
				} else {
					contentSB.append(
							"
"
								+ "Arcane Glow"
							+ "
");
				}
			}
			if(activeCovering.getPattern()==CoveringPattern.NONE
					|| activeCovering.getPattern()==CoveringPattern.EYE_IRISES
					|| activeCovering.getPattern()==CoveringPattern.EYE_PUPILS
					|| activeCovering.getPattern()==CoveringPattern.EYE_SCLERA
					|| !withSecondary) {
				contentSB.append("
"
						+ "[style.boldDisabled(Disabled)]"
					+ "
");

			} else {
				contentSB.append("
"
						+ ""
							+Util.capitaliseSentence(activeCovering.getSecondaryColour().getName())
						+""
					+ "
");
			}
			contentSB.append("
");

			if(activeCovering.getType().getNaturalModifiers().size() + activeCovering.getType().getExtraModifiers().size()>1) {
				contentSB.append("
");
				for(CoveringModifier mod : activeCovering.getType().getNaturalModifiers()) {
					if (activeCovering.getModifier() == mod) {
						contentSB.append(
								"
"
									+ "" + Util.capitaliseSentence(mod.getName()) + ""
								+ "
");
					} else {
						contentSB.append(
								"
"
										+ (Main.game.getPlayer().getMoney()>=SuccubisSecrets.getBodyCoveringTypeCost(coveringType) || !withCost
											? "" + Util.capitaliseSentence(mod.getName()) + ""
											: "[style.colourDisabled(" + Util.capitaliseSentence(mod.getName()) + ")]")
								+ "
");
					}
				}
				for(CoveringModifier mod : activeCovering.getType().getExtraModifiers()) {
					if (activeCovering.getModifier() == mod) {
						contentSB.append(
								"
"
									+ "" + Util.capitaliseSentence(mod.getName()) + ""
								+ "
");
					} else {
						contentSB.append(
								"
"
										+ (Main.game.getPlayer().getMoney()>=SuccubisSecrets.getBodyCoveringTypeCost(coveringType) || !withCost
											? "" + Util.capitaliseSentence(mod.getName()) + ""
											: "[style.colourDisabled(" + Util.capitaliseSentence(mod.getName()) + ")]")
								+ "
");
					}
				}
				contentSB.append("
");
			}
		contentSB.append("
");

		contentSB.append("
");

		return contentSB.toString();
	}

	public static String getKatesDivPiercings(PiercingType type, String title, String description) {
		contentSB.setLength(0);

		boolean noCost = !Main.game.isInNewWorld();

		boolean isPierced = false, canPierce = true;

		switch(type) {
			case EAR:
				isPierced = BodyChanging.getTarget().isPiercedEar();
				break;
			case LIP:
				isPierced = BodyChanging.getTarget().isPiercedLip();
				break;
			case NAVEL:
				isPierced = BodyChanging.getTarget().isPiercedNavel();
				break;
			case NIPPLE:
				isPierced = BodyChanging.getTarget().isPiercedNipple();
				break;
			case NOSE:
				isPierced = BodyChanging.getTarget().isPiercedNose();
				break;
			case PENIS:
				canPierce = BodyChanging.getTarget().hasPenis();
				isPierced = BodyChanging.getTarget().isPiercedPenis();
				break;
			case TONGUE:
				isPierced = BodyChanging.getTarget().isPiercedTongue();
				break;
			case VAGINA:
				canPierce = BodyChanging.getTarget().hasVagina();
				isPierced = BodyChanging.getTarget().isPiercedVagina();
				break;
		}

		contentSB.append(
				"
"
					+ "
"
						+ "
"
							+(canPierce
								? title
									+(noCost
											?""
													:" "+(Main.game.getPlayer().getMoney()>=SuccubisSecrets.getPiercingCost(type)
														? UtilText.formatAsMoney(SuccubisSecrets.getPiercingCost(type), "b")
														: UtilText.formatAsMoney(SuccubisSecrets.getPiercingCost(type), "b", Colour.GENERIC_BAD)))
								:"[style.colourDisabled("+title + ")] " + UtilText.formatAsMoneyUncoloured(SuccubisSecrets.getPiercingCost(type), "b"))
						+"
"
						+ "
"
							+ description
						+ "
"
					+ "
"
					+ "
");

		if(isPierced) {
			contentSB.append(
					"
"
						+ "[style.colourDisabled(Unpierced)]"
					+ "
");

			contentSB.append(
					"
"
						+ "[style.boldArcane(Pierced)]"
					+ "
");
		} else {
			contentSB.append(
					"
"
						+ "Unpierced"
					+ "
");

			if(canPierce) {
				contentSB.append(
						"
"
							+ (Main.game.getPlayer().getMoney()>=SuccubisSecrets.getPiercingCost(type) || noCost
								?"Pierced"
								:"[style.colourDisabled(Pierced)]")
						+ "
");
			} else {
				contentSB.append(
						"
"
							+ "[style.colourDisabled(Pierced)]"
						+ "
");
			}
		}

		contentSB.append(
				"
"
			+ "
");

		return contentSB.toString();
	}

	public static String getKatesDivTattoos() {
		contentSB.setLength(0);

		contentSB.append("
"
				+ "
Main Areas
");

		for(InventorySlot invSlot : RenderingEngine.mainInventorySlots) {
			contentSB.append(getTattooDiv(invSlot));
		}

		contentSB.append("
");

		contentSB.append("
"
				+ "
Secondary Areas
");

		for(InventorySlot invSlot : RenderingEngine.secondaryInventorySlots) {
			contentSB.append(getTattooDiv(invSlot));
		}

		contentSB.append("
");

		return contentSB.toString();
	}

	private static String getTattooDiv(InventorySlot invSlot) {
		Tattoo tattooInSlot = BodyChanging.getTarget().getTattooInSlot(invSlot);
		boolean disabled = false;

		switch(invSlot) {
			case HORNS:
				if(BodyChanging.getTarget().getHornType().equals(HornType.NONE)) {
					disabled = true;
				}
				break;
			case PENIS:
				if(!BodyChanging.getTarget().hasPenisIgnoreDildo()) {
					disabled = true;
				}
				break;
			case TAIL:
				if(BodyChanging.getTarget().getTailType()==TailType.NONE) {
					disabled = true;
				}
				break;
			case VAGINA:
				if(!BodyChanging.getTarget().hasVagina()) {
					disabled = true;
				}
				break;
			case WINGS:
				if(BodyChanging.getTarget().getWingType()==WingType.NONE) {
					disabled = true;
				}
				break;
			default:
				break;
		}

		return "
"
				+ "
"+Util.capitaliseSentence(invSlot.getTattooSlotName())+"
"
				+(disabled
					?"

"
					:"
"
						+ (tattooInSlot==null
							?"

"
							:"
"+tattooInSlot.getSVGImage(BodyChanging.getTarget())+"
")
						+ "

"
					+ "
")

				+ "
"
					+ "
"
						+ "
"
							+(tattooInSlot==null
								?"Add"
								:(SuccubisSecrets.invSlotTattooToRemove==invSlot || !Main.getProperties().hasValue(PropertyValue.tattooRemovalConfirmations)?"[style.colourBad(Remove)]":"Remove"))
						+"
"
					+ "
"
					+ (Main.game.isInNewWorld()
						?"
"
								+ "
Enchant
"
							+ "
"
						:"")
				+ "
"
			+ "
";
	}

	public static InventorySlot tattooInventorySlot = null;
	public static Tattoo tattoo = null;

	public static void resetTattooVariables(InventorySlot slot) {
		tattooInventorySlot = slot;

		tattoo = new Tattoo(
				TattooType.TRIBAL,
				Colour.CLOTHING_GREY,
				null,
				null,
				false,
				new TattooWriting(
						"",
						Colour.BASE_GREY,
						false),
				new TattooCounter(
						TattooCounterType.NONE,
						TattooCountType.NUMBERS,
						Colour.BASE_GREY,
						false));
	}

	public static void resetTattooColours() {
		if(!tattoo.getType().getAvailablePrimaryColours().contains(tattoo.getPrimaryColour())) {
			tattoo.setPrimaryColour(tattoo.getType().getAvailablePrimaryColours().get(0));
		}

		if(!tattoo.getType().getAvailableSecondaryColours().contains(tattoo.getSecondaryColour())) {
			if(tattoo.getType().getAvailableSecondaryColours().isEmpty()) {
				tattoo.setSecondaryColour(null);
			} else {
				tattoo.setSecondaryColour(tattoo.getType().getAvailableSecondaryColours().get(0));
			}
		}

		if(!tattoo.getType().getAvailableTertiaryColours().contains(tattoo.getTertiaryColour())) {
			if(tattoo.getType().getAvailableTertiaryColours().isEmpty()) {
				tattoo.setTertiaryColour(null);
			} else {
				tattoo.setTertiaryColour(tattoo.getType().getAvailableTertiaryColours().get(0));
			}
		}

		tattoo.setGlowing(false);
	}

	public static String getKatesDivTattoosAdd() {
		contentSB.setLength(0);

		// Type:

		contentSB.append("
");
			contentSB.append("
");
				contentSB.append("
Select Type
");

				for(AbstractTattooType type : TattooType.getAllTattooTypes()) {
					if(type.getSlotAvailability().contains(tattooInventorySlot)) {
						contentSB.append("
"
											+ "
"+Util.capitaliseSentence(type.getName())+"
"
										+ "
");

					} else {
						contentSB.append("
"
								+ "
"+Util.capitaliseSentence(type.getName())+"
"
							+ "
");
					}
				}
				contentSB.append("
"
						+ "
");

				contentSB.append("
"
									+ "
"+tattoo.getSVGImage(BodyChanging.getTarget())+"
"
									+ "

"
								+ "
");

			contentSB.append("
");
		contentSB.append("
");

		// Colours:

		contentSB.append("
"
				+ "
Select Colours
");

			// Primary:
			contentSB.append("
");
				for (Colour c : tattoo.getType().getAvailablePrimaryColours()) {
					contentSB.append("
"
										+ "
X":"'>")+"
"
									+ "
");
				}
			contentSB.append("
");

			// Secondary:
			contentSB.append("
");
			if(tattoo.getType().getAvailableSecondaryColours().isEmpty()) {
				contentSB.append(
						"
[style.italicsDisabled(No secondary colours available...)]
");

			} else {
				for (Colour c : tattoo.getType().getAvailableSecondaryColours()) {
					contentSB.append("
"
										+ "
X":"'>")+"
"
									+ "
");
				}
			}
			contentSB.append("
");

			// Tertiary:
			contentSB.append("
");
			if(tattoo.getType().getAvailableTertiaryColours().isEmpty()) {
				contentSB.append(
						"
[style.italicsDisabled(No tertiary colours available...)]
");

			} else {
				for (Colour c : tattoo.getType().getAvailableTertiaryColours()) {
					contentSB.append("
"
										+ "
X":"'>")+"
"
									+ "
");
				}
			}
			contentSB.append("
");

			if(Main.game.isInNewWorld()) {
				contentSB.append("
");
					if(tattoo.getType().equals(TattooType.NONE)) {
						contentSB.append(
								"
"
									+ "Glow"
								+ "
");

					} else if(tattoo.isGlowing()) {
						contentSB.append(
								"
"
									+ "[style.boldArcane(Arcane Glow)]"
								+ "
");
					} else {
						contentSB.append(
								"
"
									+ "Arcane Glow"
								+ "
");
					}
				contentSB.append("
");
			}

		contentSB.append("
");

		// Writing:

		contentSB.append("
"
				+ "
Select Writing
");

			contentSB.append("
"
					+"<form style='padding:0; margin:0 0 4px 0; text-align:center;'>"
						+ "<input type='text' id='tattoo_name' value='" +UtilText.parseForHTMLDisplay(tattoo.getWriting().getText())+"' style='padding:0;margin:0;width:80%;'>"// "+(tattoo.getWriting().getStyle()==TattooWritingStyle.NONE?"disabled":"")+">"
					+ "</form>"
					+ "
Writing Style
");
			for(TattooWritingStyle style : TattooWritingStyle.values()) {
				contentSB.append("
"
									+ "
"+Util.capitaliseSentence(style.getName())+"
"
								+ "
");
			}
			contentSB.append("
"
					+ "Output: "+tattoo.getFormattedWritingOutput()
					+ "
");
			contentSB.append("
");

			contentSB.append("
");
				for (Colour c : TattooWriting.getAvailableColours()) {
					contentSB.append("
"
										+ "
X":"'>")+"
"
									+ "
");
				}
				if(Main.game.isInNewWorld()) {
					contentSB.append("
");
					if(tattoo.getWriting().isGlow()) {
						contentSB.append(
								"
"
									+ "[style.boldArcane(Arcane Glow)]"
								+ "
");
					} else {
						contentSB.append(
								"
"
									+ "Arcane Glow"
								+ "
");
					}
				}
			contentSB.append("
");
		contentSB.append("
");

		// Counter:
		if(Main.game.isInNewWorld()) {
			contentSB.append("
"
					+ "
Select Counter
");

				contentSB.append("
");
					contentSB.append("
");
						contentSB.append("
Counter Type
");
						for(TattooCounterType counterType : TattooCounterType.values()) {
							contentSB.append("
"
												+ "
"
													+Util.capitaliseSentence(counterType.getName())+"
"
											+ "
");
						}
					contentSB.append("
");
				contentSB.append("
");

				contentSB.append("
");
					for (Colour c : TattooCounter.getAvailableColours()) {
						contentSB.append("
"
											+ "
X":"'>")+"
"
										+ "
");
					}
					contentSB.append("
");
					if(tattoo.getCounter().isGlow()) {
						contentSB.append(
								"
"
									+ "[style.boldArcane(Arcane Glow)]"
								+ "
");
					} else {
						contentSB.append(
								"
"
									+ "Arcane Glow"
								+ "
");
					}

					contentSB.append("
");
						contentSB.append("
Counter Style
");
						for(TattooCountType countType : TattooCountType.values()) {
							contentSB.append("
"
												+ "
"
													+Util.capitaliseSentence(countType.getName())+"
"
											+ "
");
						}
					contentSB.append("
");

					contentSB.append("
"
							+ "Output: "+tattoo.getFormattedCounterOutput(BodyChanging.getTarget())
							+ "
");
				contentSB.append("
");

			contentSB.append("
");
		}

		contentSB.append("

");

		return contentSB.toString();
	}

}

repo_name: klorpa/Pedo

license: unknown