code:
package com.lilithsthrone.game.character.effects;

import java.util.ArrayList;
import java.util.List;

/**
 * @since 0.1.99
 * @version 0.3.4
 * @author Innoxia
 */
public class TreeEntry<T, S> {

	private int row;
	private List<TreeEntry<T, S>> links;
	private T category;
	private S entry;

	public TreeEntry(T category, int row, S perk) {
		this.category = category;
		this.row = row;
		this.links = new ArrayList<>();
		this.entry = perk;
	}

	@SuppressWarnings("unchecked")
	@Override
	public boolean equals(Object o) {
		if(o instanceof TreeEntry){
			try {
				if(((TreeEntry<T, S>)o).getRow() == (this.getRow())) {
					if(((TreeEntry<T, S>)o).getCategory()==(this.getCategory())) {
						if(((TreeEntry<T, S>)o).getEntry().equals(this.getEntry())) {
//							System.out.println("ok");
//						&& ((TreeEntry<T, S>)o).getLinks().size() == (this.getLinks().size())
							return true;
						}
					}
				}
			} catch(Exception ex) {
//				System.out.println("broken");
				return false;
			}
			return false;
		}
//		System.out.println(o.getClass().getSimpleName());
		return false;
	}

	@Override
	public int hashCode() {
		int result = 17;
		result = 31 * result + this.getRow();
		result = 31 * result + this.getCategory().hashCode();
		result = 31 * result + this.getEntry().hashCode();
//		result = 31 * result + this.getLinks().size();
		return result;
	}

	public T getCategory() {
		return category;
	}

	public int getRow() {
		return row;
	}

	public List<TreeEntry<T, S>> getParentLinks() {
		List<TreeEntry<T, S>> parentLinks = new ArrayList<>(links);
		parentLinks.removeIf((e) -> e.getRow()>=this.getRow());
		return parentLinks;
	}

	public List<TreeEntry<T, S>> getChildLinks() {
		List<TreeEntry<T, S>> childLinks = new ArrayList<>(links);
		childLinks.removeIf((e) -> e.getRow()<=this.getRow());
		return childLinks;
	}

	public List<TreeEntry<T, S>> getSiblingLinks() {
		List<TreeEntry<T, S>> siblingLinks = new ArrayList<>(links);
		siblingLinks.removeIf((e) -> e.getRow()!=this.getRow());
		return siblingLinks;
	}

	public List<TreeEntry<T, S>> getLinks() {
		return links;
	}

	public void setLinks(List<TreeEntry<T, S>> links) {
		this.links = new ArrayList<>(links);
	}

	public void addLink(TreeEntry<T, S> entry) {
		if(!getLinks().contains(entry)) {
			links.add(entry);
		}

		if(!entry.getLinks().contains(this)) {
			entry.addLink(this);
		}
	}

	public S getEntry() {
		return entry;
	}

}

repo_name: klorpa/Pedo

license: unknown


code:
package com.lilithsthrone.game.character.fetishes;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map.Entry;

import com.lilithsthrone.game.character.GameCharacter;
import com.lilithsthrone.game.character.attributes.Attribute;
import com.lilithsthrone.game.character.attributes.CorruptionLevel;
import com.lilithsthrone.game.character.body.types.VaginaType;
import com.lilithsthrone.game.character.effects.Perk;
import com.lilithsthrone.game.character.persona.SexualOrientation;
import com.lilithsthrone.game.character.race.Subspecies;
import com.lilithsthrone.game.dialogue.utils.UtilText;
import com.lilithsthrone.utils.Colour;
import com.lilithsthrone.utils.SvgUtil;
import com.lilithsthrone.utils.Util;
import com.lilithsthrone.utils.Util.Value;

/**
 * @since 0.1.?
 * @version 0.1.99
 * @author Innoxia
 */
public enum Fetish {

	// FETISHES:

	// Sex types:

	FETISH_ANAL_GIVING(60,
			"anal",
			"performing anal",
			"fetish_anal_giving",
			FetishExperience.BASE_EXPERIENCE_GAIN,
			Colour.GENERIC_ARCANE,
			null,
			Util.newArrayListOfValues(
					"Unlocks anal tease",
					"Weak to buttslut tease"),
			null) {

		@Override
		public String getDescription(GameCharacter owner) {
			if (owner.isPlayer()) {
				return "You absolutely love giving anal sex. Using your partner's ass, claiming their asshole as your property, it all drives you crazy with lust!";
			} else {
				return UtilText.parse(owner, "[npc.Name] has a fetish for giving anal sex.");
			}
		}

		@Override
		public String getFetishDesireDescription(GameCharacter target, FetishDesire desire) {
			return getGenericFetishDesireDescription(target, desire, "performing anal sex actions");
		}

		@Override
		public CorruptionLevel getAssociatedCorruptionLevel() {
			return CorruptionLevel.TWO_HORNY;
		}
	},

	FETISH_ANAL_RECEIVING(60,
			"buttslut",
			"receiving anal",
			"fetish_anal_receiving",
			FetishExperience.BASE_EXPERIENCE_GAIN,
			Colour.GENERIC_ARCANE,
			null,
			Util.newArrayListOfValues(
					"Unlocks buttslut tease",
					"Weak to anal tease"),
			null) {

		@Override
		public String getDescription(GameCharacter owner) {
			if (owner.isPlayer()) {
				return "You absolutely love receiving anal sex. The thought of getting your ass pounded like a good little buttslut drives you crazy with lust!";
			} else {
				return UtilText.parse(owner, "[npc.Name] has a fetish for receiving anal sex.");
			}
		}

		@Override
		public String getFetishDesireDescription(GameCharacter target, FetishDesire desire) {
			return getGenericFetishDesireDescription(target, desire, "receiving any anal attention");
		}

		@Override
		public CorruptionLevel getAssociatedCorruptionLevel() {
			return CorruptionLevel.TWO_HORNY;
		}
	},

	FETISH_VAGINAL_GIVING(60,
			"vaginal",
			"performing vaginal",
			"fetish_vaginal_giving",
			FetishExperience.BASE_EXPERIENCE_GAIN,
			Colour.GENERIC_ARCANE,
			null,
			Util.newArrayListOfValues(
					"Unlocks pussy worship tease",
					"Weak to pussy slut tease"),
			null) {

		@Override
		public String getDescription(GameCharacter owner) {
			if (owner.isPlayer()) {
				return "Although typically one of the most vanilla of all sexual acts, your love for performing vaginal sex has turned into a complete obsession.";
			} else {
				return UtilText.parse(owner, "[npc.Name] has an extreme obsession with performing vaginal sex.");
			}
		}

		@Override
		public String getFetishDesireDescription(GameCharacter target, FetishDesire desire) {
			return getGenericFetishDesireDescription(target, desire, "performing vaginal sex actions");
		}

		@Override
		public CorruptionLevel getAssociatedCorruptionLevel() {
			return CorruptionLevel.ONE_VANILLA;
		}
	},

	FETISH_VAGINAL_RECEIVING(60,
			"pussy slut",
			"receiving vaginal",
			"fetish_vaginal_receiving",
			FetishExperience.BASE_EXPERIENCE_GAIN,
			Colour.GENERIC_ARCANE,
			null,
			Util.newArrayListOfValues(
					"Unlocks pussy slut tease",
					"Weak to pussy worship tease"),
			null) {

		@Override
		public String getDescription(GameCharacter owner) {
			if (owner.isPlayer()) {
				return "Although typically one of the most vanilla of all sexual acts, your love for receiving all forms of vaginal sex has turned into a complete obsession.";
			} else {
				return UtilText.parse(owner, "[npc.Name] has an extreme obsession with receiving vaginal sex.");
			}
		}

		@Override
		public String getFetishDesireDescription(GameCharacter target, FetishDesire desire) {
			return getGenericFetishDesireDescription(target, desire, "receiving any vaginal attention");
		}

		@Override
		public CorruptionLevel getAssociatedCorruptionLevel() {
			return CorruptionLevel.ONE_VANILLA;
		}
	},

	FETISH_ORAL_RECEIVING(60,
			"oral",
			"receiving oral",
			"fetish_oral_receiving",
			FetishExperience.BASE_EXPERIENCE_GAIN,
			Colour.GENERIC_ARCANE,
			null,
			Util.newArrayListOfValues(
					"Unlocks oral tease",
					"Weak to oral performer tease"),
			null) {

		@Override
		public String getDescription(GameCharacter owner) {
			if (owner.isPlayer()) {
				return "You absolutely love receiving oral sex. Guiding your partner's head down between your legs is your favourite activity, and you're always eager for someone's lips and tongue to bring you to orgasm.";
			} else {
				return UtilText.parse(owner, "[npc.Name] has a fetish for receiving oral sex.");
			}
		}

		@Override
		public String getFetishDesireDescription(GameCharacter target, FetishDesire desire) {
			return getGenericFetishDesireDescription(target, desire, "receiving oral sex");
		}

		@Override
		public CorruptionLevel getAssociatedCorruptionLevel() {
			return CorruptionLevel.ONE_VANILLA;
		}
	},

	FETISH_ORAL_GIVING(60,
			"oral performer",
			"giving oral",
			"fetish_oral_giving",
			FetishExperience.BASE_EXPERIENCE_GAIN,
			Colour.GENERIC_ARCANE,
			null,
			Util.newArrayListOfValues(
					"Unlocks oral performer tease",
					"Weak to oral tease"),
			null) {

		@Override
		public String getDescription(GameCharacter owner) {
			if (owner.isPlayer()) {
				return "You absolutely love giving oral sex. Going down between your partner's legs is your favourite activity, and you're always ready and eager to use your mouth to bring them to orgasm!";
			} else {
				return UtilText.parse(owner, "[npc.Name] has a fetish for giving oral sex.");
			}
		}

		@Override
		public String getFetishDesireDescription(GameCharacter target, FetishDesire desire) {
			return getGenericFetishDesireDescription(target, desire, "performing oral sex");
		}

		@Override
		public CorruptionLevel getAssociatedCorruptionLevel() {
			return CorruptionLevel.ONE_VANILLA;
		}
	},

	FETISH_BREASTS_OTHERS(60,
			"breasts lover",
			"others' breasts",
			"fetish_breasts_others",
			FetishExperience.BASE_EXPERIENCE_GAIN,
			Colour.GENERIC_ARCANE,
			null,
			Util.newArrayListOfValues(
					"Unlocks breasts lover tease",
					"Weak to breasts tease"),
			null) {

		@Override
		public String getDescription(GameCharacter owner) {
			if (owner.isPlayer()) {
				return "You have an obsession with breasts. Either big or small, if someone's got a pair (or more) of tits, you're going to be finding a way to use them.";
			} else {
				return UtilText.parse(owner, "[npc.Name] has a fetish for other's breasts.");
			}
		}

		@Override
		public String getFetishDesireDescription(GameCharacter target, FetishDesire desire) {
			return getGenericFetishDesireDescription(target, desire, "playing with others' breasts");
		}

		@Override
		public CorruptionLevel getAssociatedCorruptionLevel() {
			return CorruptionLevel.ONE_VANILLA;
		}
	},

	FETISH_BREASTS_SELF(60,
			"breasts",
			"self breast play",
			"fetish_breasts_self",
			FetishExperience.BASE_EXPERIENCE_GAIN,
			Colour.GENERIC_ARCANE,
			null,
			Util.newArrayListOfValues(
					"Unlocks breasts tease",
					"Weak to breasts lover tease"),
			null) {

		@Override
		public String getDescription(GameCharacter owner) {
			if (owner.isPlayer()) {
				return "You have an obsession with your breasts. You love nothing more than pleasuring your partners with them, or showing them off to your many admirers.";
			} else {
				return UtilText.parse(owner, "[npc.Name] has a fetish for using [npc.her] breasts.");
			}
		}

		@Override
		public String getFetishDesireDescription(GameCharacter target, FetishDesire desire) {
			return getGenericFetishDesireDescription(target, desire, "having your [npc.breasts] touched and fondled");
		}

		@Override
		public CorruptionLevel getAssociatedCorruptionLevel() {
			return CorruptionLevel.ONE_VANILLA;
		}
	},

	FETISH_LACTATION_OTHERS(60,
			"milk lover",
			"being breast-fed",
			"fetish_lactation_others",
			FetishExperience.BASE_EXPERIENCE_GAIN,
			Colour.GENERIC_ARCANE,
			null,
			Util.newArrayListOfValues(
					"Unlocks milk-lover tease",
					"Weak to lactation tease"),
			null) {

		@Override
		public String getDescription(GameCharacter owner) {
			if (owner.isPlayer()) {
				return "You have an obsession with being breast-fed. You love nothing more than to be suckling on someone's engorged, milky teats.";
			} else {
				return UtilText.parse(owner, "[npc.Name] has a fetish for being breast-fed.");
			}
		}

		@Override
		public String getFetishDesireDescription(GameCharacter target, FetishDesire desire) {
			return getGenericFetishDesireDescription(target, desire, "being breast-fed");
		}

		@Override
		public CorruptionLevel getAssociatedCorruptionLevel() {
			return CorruptionLevel.ONE_VANILLA;
		}
	},

	FETISH_LACTATION_SELF(60,
			"lactation",
			"lactating",
			"fetish_lactation_self",
			FetishExperience.BASE_EXPERIENCE_GAIN,
			Colour.GENERIC_ARCANE,
			null,
			Util.newArrayListOfValues(
					"Unlocks lactation tease",
					"Weak to milk-lover tease"),
			null) {

		@Override
		public String getDescription(GameCharacter owner) {
			if (owner.isPlayer()) {
				return "You have an obsession with lactating. You love nothing more than the full, satisfying feeling of having your breasts milked.";
			} else {
				return UtilText.parse(owner, "[npc.Name] has a fetish for lactating.");
			}
		}

		@Override
		public String getFetishDesireDescription(GameCharacter target, FetishDesire desire) {
			return getGenericFetishDesireDescription(target, desire, "having your [npc.breasts] milked");
		}

		@Override
		public CorruptionLevel getAssociatedCorruptionLevel() {
			return CorruptionLevel.ONE_VANILLA;
		}
	},

	FETISH_LEG_LOVER(60,
			"leg lover",
			"partner's legs",
			"fetish_leg_lover",
			FetishExperience.BASE_EXPERIENCE_GAIN,
			Colour.GENERIC_ARCANE,
			null,
			Util.newArrayListOfValues(
					"Unlocks leg lover tease",
					"Weak to strutter tease"),
			null) {

		@Override
		public String getDescription(GameCharacter owner) {
			if (owner.isPlayer()) {
				return "You absolutely love legs. Using a partner's legs or thighs in sex is the ultimate turn-on for you.";
			} else {
				return UtilText.parse(owner, "[npc.Name] has a fetish for using other people's legs and thighs.");
			}
		}

		@Override
		public String getFetishDesireDescription(GameCharacter target, FetishDesire desire) {
			return getGenericFetishDesireDescription(target, desire, "others' legs");
		}

		@Override
		public CorruptionLevel getAssociatedCorruptionLevel() {
			return CorruptionLevel.ONE_VANILLA;
		}
	},

	FETISH_STRUTTER(60,
			"strutter",
			"having legs used",
			"fetish_strutter",
			FetishExperience.BASE_EXPERIENCE_GAIN,
			Colour.GENERIC_ARCANE,
			null,
			Util.newArrayListOfValues(
					"Unlocks strutter tease",
					"Weak to leg lover tease"),
			null) {

		@Override
		public String getDescription(GameCharacter owner) {
			if (owner.isPlayer()) {
				return "You absolutely love showing off your legs. Using your legs or thighs in sex is the ultimate turn-on for you.";
			} else {
				return UtilText.parse(owner, "[npc.Name] has a fetish for using [npc.her] legs or thighs in sex.");
			}
		}

		@Override
		public String getFetishDesireDescription(GameCharacter target, FetishDesire desire) {
			return getGenericFetishDesireDescription(target, desire, "using your thighs and legs in sex");
		}

		@Override
		public CorruptionLevel getAssociatedCorruptionLevel() {
			return CorruptionLevel.ONE_VANILLA;
		}
	},

	FETISH_FOOT_GIVING(60,
			"dominant foot",
			"using feet",
			"fetish_foot_giving",
			FetishExperience.BASE_EXPERIENCE_GAIN,
			Colour.GENERIC_ARCANE,
			null,
			Util.newArrayListOfValues(
					"Unlocks dominant foot tease",
					"Weak to submissive foot tease"),
			null) {

		@Override
		public String getDescription(GameCharacter owner) {
			if (owner.isPlayer()) {
				return "You absolutely love using your feet in sex. Getting your partner to worship and perform sexual acts on your feet and toes turns you on like nothing else.";
			} else {
				return UtilText.parse(owner, "[npc.Name] has a fetish for using [npc.her] feet in sex.");
			}
		}

		@Override
		public String getFetishDesireDescription(GameCharacter target, FetishDesire desire) {
			return getGenericFetishDesireDescription(target, desire, "using your feet in sex");
		}

		@Override
		public CorruptionLevel getAssociatedCorruptionLevel() {
			return CorruptionLevel.ONE_VANILLA;
		}
	},

	FETISH_FOOT_RECEIVING(60,
			"submissive foot",
			"using partner's feet",
			"fetish_foot_receiving",
			FetishExperience.BASE_EXPERIENCE_GAIN,
			Colour.GENERIC_ARCANE,
			null,
			Util.newArrayListOfValues(
					"Unlocks submissive foot tease",
					"Weak to dominant foot tease"),
			null) {

		@Override
		public String getDescription(GameCharacter owner) {
			if (owner.isPlayer()) {
				return "You absolutely love feet. Getting your partner to use their feet and toes in sex is the ultimate turn-on for you.";
			} else {
				return UtilText.parse(owner, "[npc.Name] has a fetish for using other people's feet.");
			}
		}

		@Override
		public String getFetishDesireDescription(GameCharacter target, FetishDesire desire) {
			return getGenericFetishDesireDescription(target, desire, "others' feet");
		}

		@Override
		public CorruptionLevel getAssociatedCorruptionLevel() {
			return CorruptionLevel.ONE_VANILLA;
		}
	},

	FETISH_PENIS_RECEIVING(60,
			"cock addict",
			"others cocks",
			"fetish_cock_addict",
			FetishExperience.BASE_EXPERIENCE_GAIN,
			Colour.GENERIC_ARCANE,
			null,
			Util.newArrayListOfValues("Unlocks cock addict tease",
					"Weak to cock stud tease"),
			null) {

		@Override
		public String getDescription(GameCharacter owner) {
			if (owner.isPlayer())
				return "You are hopelessly addicted to cock. Large, small, fat, thin, you really don't care about the looks, just so long as it's pumping in and out of one of your holes...";
			else
				return UtilText.parse(owner, "[npc.Name] is hopelessly addicted to cock.");
		}

		@Override
		public String getFetishDesireDescription(GameCharacter target, FetishDesire desire) {
			return getGenericFetishDesireDescription(target, desire, "others' cocks");
		}

		@Override
		public CorruptionLevel getAssociatedCorruptionLevel() {
			return CorruptionLevel.TWO_HORNY;
		}
	},

	FETISH_PENIS_GIVING(60,
			"cock stud",
			"using their cock",
			"fetish_dick_dealer",
			FetishExperience.BASE_EXPERIENCE_GAIN,
			Colour.GENERIC_ARCANE,
			null,
			Util.newArrayListOfValues("Unlocks cock stud tease (Requires penis)",
					"Weak to cock addict tease"),
			null) {

		@Override
		public String getDescription(GameCharacter owner) {
			if (owner.isPlayer())
				return "You are obsessed with penetrative sex. Thrusting your cock into any available orifice is all you can think about...";
			else
				return UtilText.parse(owner, "[npc.Name] has a fetish for using [npc.her] cock.");
		}

		@Override
		public String getFetishDesireDescription(GameCharacter target, FetishDesire desire) {
			return getGenericFetishDesireDescription(target, desire, "using your cock");
		}

		@Override
		public CorruptionLevel getAssociatedCorruptionLevel() {
			return CorruptionLevel.TWO_HORNY;
		}
	},

	FETISH_CUM_STUD(60,
			"cum stud",
			"cumming",
			"fetish_cum",
			FetishExperience.BASE_RARE_EXPERIENCE_GAIN,
			Colour.GENERIC_ARCANE,
			null,
			Util.newArrayListOfValues("Unlocks cum stud tease (Requires penis)",
					"Weak to cum addict tease"),
			null) {

		@Override
		public String getDescription(GameCharacter owner) {
				return UtilText.parse(owner,
						"[npc.NameHasFull] a particular obsession with cumming. Pumping any and all orifices full of cum is what [npc.she] [npc.verb(love)] the most, but spurting it all over someone's body is also an acceptable pastime.");
		}

		@Override
		public String getFetishDesireDescription(GameCharacter target, FetishDesire desire) {
			return getGenericFetishDesireDescription(target, desire, "any form of self-focused cum play");
		}

		@Override
		public CorruptionLevel getAssociatedCorruptionLevel() {
			return CorruptionLevel.THREE_DIRTY;
		}
	},

	FETISH_CUM_ADDICT(60,
			"cum addict",
			"cum-play",
			"fetish_cum_addict",
			FetishExperience.BASE_RARE_EXPERIENCE_GAIN,
			Colour.CLOTHING_WHITE,
			null,
			Util.newArrayListOfValues("Unlocks cum addict tease",
					"Weak to cum stud tease"),
			null) {

		@Override
		public String getDescription(GameCharacter owner) {
			if (owner.isPlayer())
				return "You are hopelessly addicted to cum. You really don't care who's providing it; all you want is for your mouth to be full of delicious, salty seed..."
						+ " Letting it slide around over your tongue, savouring every moment... Mmm... Cum really is the best...";
			else
				return UtilText.parse(owner, "[npc.Name] has a fetish for cum.");
		}

		@Override
		public String getFetishDesireDescription(GameCharacter target, FetishDesire desire) {
			return getGenericFetishDesireDescription(target, desire, "others' cum");
		}

		@Override
		public CorruptionLevel getAssociatedCorruptionLevel() {
			return CorruptionLevel.THREE_DIRTY;
		}
	},

	FETISH_DEFLOWERING(60,
			"deflowering",
			"deflowering",
			"fetish_deflowering",
			FetishExperience.BASE_EXPERIENCE_GAIN,
			Colour.GENERIC_ARCANE,
			null,
			Util.newArrayListOfValues("Gain xp from taking virginities"),
			null) {

		@Override
		public String getDescription(GameCharacter owner) {
			if (owner.isPlayer()) {
				return "You love nothing more than claiming an innocent maiden's virginity. Although breaking in a soon-to-be slut's pussy is your favourite, you still enjoy being the first to fuck a person's ass, nipples, or throat.";

			} else {
				return UtilText.parse(owner, "[npc.Name] has a fetish for deflowering. [npc.She] loves being the one to break a girl's hymen, but also enjoys being the first to fuck a person's ass, nipples, or throat.");
			}
		}

		@Override
		public String getFetishDesireDescription(GameCharacter target, FetishDesire desire) {
			return getGenericFetishDesireDescription(target, desire, "taking virginities");
		}

		@Override
		public CorruptionLevel getAssociatedCorruptionLevel() {
			return CorruptionLevel.FOUR_LUSTFUL;
		}
	},

	FETISH_PURE_VIRGIN(60,
			"virginity",
			"retaining virginity",
			"fetish_virginity",
			FetishExperience.BASE_VERY_RARE_EXPERIENCE_GAIN,
			Colour.GENERIC_ARCANE,
			null,
			Util.newArrayListOfValues(
					"Gain 'pure virgin'",
					"Suffer 'broken virgin'"),
			null) {

		@Override
		public String getDescription(GameCharacter owner) {
			if (owner == null)
				return "";

			if (owner.isPlayer()) {
				if (owner.getVaginaType() != VaginaType.NONE)
					return "You prize your vaginal virginity above everything else in the world. If you were ever to lose it, you don't know how you'd cope...";
				else
					return "Although you currently don't have a vagina, you know that if you were ever to have one, you'd prize its virginity above everything else in the world. If you were ever to lose it, you don't know how you'd cope...";
			} else
				return UtilText.parse(owner, "[npc.Name] prizes [npc.her] virginity above all else.");
		}

		@Override
		public String getFetishDesireDescription(GameCharacter target, FetishDesire desire) {
			return getGenericFetishDesireDescription(target, desire, "holding onto your virginity");
		}

		@Override
		public boolean isAvailable(GameCharacter character) {
			return character.getVaginaType() != VaginaType.NONE && character.isVaginaVirgin();
		}

		@Override
		public List<String> getPerkRequirements(GameCharacter character) {
			perkRequirementsList.clear();

			if(character.getVaginaType()==VaginaType.NONE) {
				perkRequirementsList.add("Requires vagina");
			} else {
				perkRequirementsList.add("Requires vagina");
			}

			if(!character.isVaginaVirgin()) {
				perkRequirementsList.add("Requires virginity");
			} else {
				perkRequirementsList.add("Requires virginity");
			}

			return perkRequirementsList;
		}

		@Override
		public CorruptionLevel getAssociatedCorruptionLevel() {
			return CorruptionLevel.ZERO_PURE;
		}
	},

	FETISH_MASTURBATION(60,
			"masturbation",
			"masturbating",
			"fetish_masturbation",
			FetishExperience.BASE_EXPERIENCE_GAIN,
			Colour.GENERIC_ARCANE,
			null,
			Util.newArrayListOfValues(
					"No special abilities"),
			null) {

		@Override
		public String getDescription(GameCharacter owner) {
			if (owner.isPlayer()) {
				return "To you, masturbating is just as good as, if not better than, having sex.";
			} else {
				return UtilText.parse(owner, "[npc.Name] loves masturbating.");
			}
		}

		@Override
		public String getFetishDesireDescription(GameCharacter target, FetishDesire desire) {
			return getGenericFetishDesireDescription(target, desire, "masturbating");
		}

		@Override
		public CorruptionLevel getAssociatedCorruptionLevel() {
			return CorruptionLevel.ONE_VANILLA;
		}
	},

	// FETISH_SPANKING("spanking", "You love the idea of spanking or being
	// spanked during sex."),

	// Body parts:

	// FETISH_LACTATION("lactation", "The idea of someone being milked drives
	// you crazy with lust."),
	// FETISH_FOOT("foot", "It doesn't matter if they're yours or your
	// partner's, you're going to be trying to find a way to use feet during
	// sex."),

	// Effects:
	FETISH_PREGNANCY(60,
			"pregnancy",
			"being pregnant",
			"fetish_pregnancy",
			FetishExperience.BASE_RARE_EXPERIENCE_GAIN,
			Colour.GENERIC_ARCANE,
			Util.newHashMapOfValues(new Value<Attribute, Integer>(Attribute.FERTILITY, 5)),
			Util.newArrayListOfValues("Unlocks fertility tease (Requires vagina)",
					"Weak to virility tease"),
			null) {

		@Override
		public String getDescription(GameCharacter owner) {
			if (owner.isPlayer()) {
				return "You often find yourself fantasising about being impregnated, and the idea of being bred like an animal drives you crazy with lust.";
			} else {
				return UtilText.parse(owner, "[npc.Name] has a fetish for being impregnated.");
			}
		}

		@Override
		public String getFetishDesireDescription(GameCharacter target, FetishDesire desire) {
			return getGenericFetishDesireDescription(target, desire, "being pregnant");
		}

		@Override
		public CorruptionLevel getAssociatedCorruptionLevel() {
			return CorruptionLevel.TWO_HORNY;
		}
	},

	FETISH_IMPREGNATION(60,
			"impregnation",
			"impregnating",
			"fetish_impregnation",
			FetishExperience.BASE_RARE_EXPERIENCE_GAIN,
			Colour.GENERIC_ARCANE,
			Util.newHashMapOfValues(new Value<Attribute, Integer>(Attribute.VIRILITY, 5)),
			Util.newArrayListOfValues("Unlocks virility tease (Requires penis)",
					"Weak to fertility tease"),
			null) {

		@Override
		public String getDescription(GameCharacter owner) {
			if (owner.isPlayer()) {
				return "You often find yourself fantasising about filling fertile wombs with your seed, and the idea of breeding your sexual partner like an animal drives you crazy with lust.";
			} else {
				return UtilText.parse(owner, "[npc.Name] has a fetish for impregnating [npc.her] partner during sex.");
			}
		}

		@Override
		public String getFetishDesireDescription(GameCharacter target, FetishDesire desire) {
			return getGenericFetishDesireDescription(target, desire, "impregnating others");
		}

		@Override
		public CorruptionLevel getAssociatedCorruptionLevel() {
			return CorruptionLevel.TWO_HORNY;
		}
	},

	FETISH_TRANSFORMATION_GIVING(60,
			"transformer",
			"transforming others",
			"fetish_transformation_giving",
			FetishExperience.BASE_RARE_EXPERIENCE_GAIN,
			Colour.GENERIC_ARCANE,
			null,
			Util.newArrayListOfValues(
					"Halves cost of all potion making"),
			null) {

		@Override
		public String getDescription(GameCharacter owner) {
			if (owner.isPlayer()) {
				return "You love the idea of transforming others. Watching their bodies change, either voluntarily or otherwise, is a massive turn-on for you.";
			} else {
				return UtilText.parse(owner, "[npc.Name] loves transforming others. Watching their bodies change, either voluntarily or otherwise, is a massive turn-on for [npc.herHim].");
			}
		}

		@Override
		public String getFetishDesireDescription(GameCharacter target, FetishDesire desire) {
			return getGenericFetishDesireDescription(target, desire, "transforming others");
		}

		@Override
		public CorruptionLevel getAssociatedCorruptionLevel() {
			return CorruptionLevel.FOUR_LUSTFUL;
		}
	},

	FETISH_TRANSFORMATION_RECEIVING(60,
			"test subject",
			"being transformed",
			"fetish_transformation_receiving",
			FetishExperience.BASE_RARE_EXPERIENCE_GAIN,
			Colour.GENERIC_ARCANE,
			null,
			Util.newArrayListOfValues(
					"[style.boldGood(Increases potency)] of receiving forced transformations",
					"[style.boldBad(Disables)] ability to spit out TF potions"),
			null) {

		@Override
		public String getDescription(GameCharacter owner) {
			if (owner.isPlayer()) {
				return "You love the idea of being transformed. Having your body parts changed, either voluntarily or otherwise, is a massive turn-on for you.";
			} else {
				return UtilText.parse(owner, "[npc.Name] loves being transformed. Having [npc.her] body parts changed, either voluntarily or otherwise, is a massive turn-on for [npc.herHim].");
			}
		}

		@Override
		public String getFetishDesireDescription(GameCharacter target, FetishDesire desire) {
			return getGenericFetishDesireDescription(target, desire, "the idea of being transformed");
		}

		@Override
		public CorruptionLevel getAssociatedCorruptionLevel() {
			return CorruptionLevel.TWO_HORNY;
		}
	},

	FETISH_KINK_GIVING(60,
			"kink advocate",
			"giving others fetishes",
//			"fetish_transformation_giving",
			"fetish_kink_giving",
			FetishExperience.BASE_EXPERIENCE_GAIN,
			Colour.GENERIC_ARCANE,
			null,
			Util.newArrayListOfValues(
					// Unclear what extra effects this fetish should provide, other than triggering forced fetishes
					"Enjoy making others try new things!"),
			null) {

		@Override
		public String getDescription(GameCharacter owner) {
			if (owner.isPlayer()) {
				return "The idea of giving people new fetishes, either voluntarily or otherwise, is a massive turn-on for you.";
			} else {
				return UtilText.parse(owner, "[npc.Name] loves giving others new fetishes. Watching them enjoy perverse new things, either voluntarily or otherwise, is a massive turn-on for [npc.herHim].");
			}
		}

		@Override
		public String getFetishDesireDescription(GameCharacter target, FetishDesire desire) {
			return getGenericFetishDesireDescription(target, desire, "giving others new fetishes");
		}

		@Override
		public CorruptionLevel getAssociatedCorruptionLevel() {
			return CorruptionLevel.FOUR_LUSTFUL;
		}
	},

	FETISH_KINK_RECEIVING(60,
			"kink curious",
			"gaining fetishes",
//			"fetish_transformation_receiving",
			"fetish_kink_receiving",
			FetishExperience.BASE_EXPERIENCE_GAIN,
			Colour.GENERIC_ARCANE,
			null,
			Util.newArrayListOfValues(
					// Unclear what extra effects this fetish should provide, other than not taking corruption from receiving forced fetishes
					"Removes corruption gain when a fetish is forced on you."),
			null) {

		@Override
		public String getDescription(GameCharacter owner) {
			if (owner.isPlayer()) {
				return "You love the idea of developing new fetishes. Gaining perverse joy from new things, either voluntarily or otherwise, is a massive turn-on for you.";
			} else {
				return UtilText.parse(owner, "[npc.Name] loves developing new fetishes. Gaining perverse joy from new things, either voluntarily or otherwise, is a massive turn-on for [npc.herHim].");
			}
		}

		@Override
		public String getFetishDesireDescription(GameCharacter target, FetishDesire desire) {
			return getGenericFetishDesireDescription(target, desire, "the idea of being given new fetishes");
		}

		@Override
		public CorruptionLevel getAssociatedCorruptionLevel() {
			return CorruptionLevel.THREE_DIRTY;
		}
	},

	// Behaviour (organised roughly in active/passive pairs):

	FETISH_DENIAL(60,
			"orgasm denier",
			"denying orgasms",
			"fetish_denial",
			FetishExperience.BASE_RARE_EXPERIENCE_GAIN,
			Colour.GENERIC_ARCANE,
			null,
			null,
			null) {

		@Override
		public String getDescription(GameCharacter owner) {
			if (owner.isPlayer())
				return "Either by teasing them with your body, or preventing them from orgasming, you love denying your partners during sex.";
			else
				return UtilText.parse(owner, "[npc.Name] has a fetish for orgasm denial.");
		}

		@Override
		public String getFetishDesireDescription(GameCharacter target, FetishDesire desire) {
			return getGenericFetishDesireDescription(target, desire, "denying orgasms");
		}

		@Override
		public CorruptionLevel getAssociatedCorruptionLevel() {
			return CorruptionLevel.THREE_DIRTY;
		}
	},

	FETISH_DENIAL_SELF(60,
			"self-denial",
			"being denied",
			"fetish_denial_self",
			FetishExperience.BASE_RARE_EXPERIENCE_GAIN,
			Colour.GENERIC_ARCANE,
			Util.newHashMapOfValues(
					new Value<Attribute, Integer>(Attribute.RESISTANCE_PHYSICAL, 1),
					new Value<Attribute, Integer>(Attribute.RESISTANCE_LUST, 2)),
			null,
			null) {

		@Override
		public String getDescription(GameCharacter owner) {
			if (owner.isPlayer())
				return "You love edging and having your orgasms denied.";
			else
				return UtilText.parse(owner, "[npc.Name] has a fetish for having [npc.her] orgasms denied.");
		}

		@Override
		public String getFetishDesireDescription(GameCharacter target, FetishDesire desire) {
			return getGenericFetishDesireDescription(target, desire, "having your orgasms denied");
		}

		@Override
		public CorruptionLevel getAssociatedCorruptionLevel() {
			return CorruptionLevel.THREE_DIRTY;
		}
	},

	FETISH_DOMINANT(60,
			"dominant",
			"acting dominantly",
			"fetish_dominant",
			FetishExperience.BASE_EXPERIENCE_GAIN,
			Colour.GENERIC_ARCANE,
			Util.newHashMapOfValues(new Value<Attribute, Integer>(Attribute.MANA_MAXIMUM, 5)),
			Util.newArrayListOfValues(
					"Unlocks dominant tease",
					"Weak to submissive tease"),
			null) {
		@Override
		public String getDescription(GameCharacter owner) {
			if (owner.isPlayer())
				return "You love being the dominant partner during sex, and you know just how to show your partners who's in charge.";
			else
				return UtilText.parse(owner, "[npc.Name] has a fetish for being the dominant partner in sex.");
		}

		@Override
		public String getFetishDesireDescription(GameCharacter target, FetishDesire desire) {
			return getGenericFetishDesireDescription(target, desire, "being the dominant partner");
		}

		@Override
		public CorruptionLevel getAssociatedCorruptionLevel() {
			return CorruptionLevel.THREE_DIRTY;
		}
	},

	FETISH_SUBMISSIVE(60,
			"submissive",
			"acting submissively",
			"fetish_submissive",
			FetishExperience.BASE_EXPERIENCE_GAIN,
			Colour.GENERIC_ARCANE,
			Util.newHashMapOfValues(new Value<Attribute, Integer>(Attribute.MAJOR_PHYSIQUE, 2)),
			Util.newArrayListOfValues(
					"Unlocks submissive tease",
					"Weak to dominant tease"),
			null) {
		@Override
		public String getDescription(GameCharacter owner) {
			if (owner.isPlayer())
				return "You love being the submissive partner during sex. You'll do anything to show your submission, and will happily let your partner do whatever they want with you.";
			else
				return UtilText.parse(owner, "[npc.Name] has a fetish for being the submissive partner in sex.");
		}

		@Override
		public String getFetishDesireDescription(GameCharacter target, FetishDesire desire) {
			return getGenericFetishDesireDescription(target, desire, "being the submissive partner");
		}

		@Override
		public CorruptionLevel getAssociatedCorruptionLevel() {
			return CorruptionLevel.THREE_DIRTY;
		}
	},

	FETISH_INCEST(60,
			"incest",
			"incestuous sex",
			"fetish_incest",
			FetishExperience.BASE_EXPERIENCE_GAIN,
			Colour.GENERIC_ARCANE,
			null,
			Util.newArrayListOfValues(
					"Unlocks incest tease"),
			null) {
		@Override
		public String getDescription(GameCharacter owner) {
			if (owner.isPlayer()) {
				if(owner.getSexualOrientation()==SexualOrientation.ANDROPHILIC) {
					return "You always did have the hots for your male cousin...";
				} else {
					return "You always did have the hots for your aunt Lily, as well as your female cousin...";
				}

			} else {
				return UtilText.parse(owner, "[npc.Name] has a fetish for incestuous sex.");
			}
		}

		@Override
		public String getFetishDesireDescription(GameCharacter target, FetishDesire desire) {
			return getGenericFetishDesireDescription(target, desire, "having sex with your relatives");
		}

		@Override
		public CorruptionLevel getAssociatedCorruptionLevel() {
			return CorruptionLevel.FIVE_CORRUPT;
		}
	},

	FETISH_MASOCHIST(60,
			"masochist",
			"pain and humiliation",
			"fetish_masochist",
			FetishExperience.BASE_EXPERIENCE_GAIN,
			Colour.GENERIC_ARCANE,
			Util.newHashMapOfValues(new Value<Attribute, Integer>(Attribute.RESISTANCE_PHYSICAL, 2)),
			Util.newArrayListOfValues(
					"All incoming "+Attribute.HEALTH_MAXIMUM.getName()+" damage is reduced by 25%",
					"Reduced damage is converted to lust damage",
					"Gain [style.boldArcane(1 essence)] when you are critically hit"),
			null) {

		@Override
		public String getDescription(GameCharacter owner) {
			if (owner.isPlayer())
				return "You love pain, and being treated like dirt sends you absolutely wild with lust.";
			else
				return UtilText.parse(owner, "[npc.Name] has a fetish for being treated like a worthless slut.");
		}

		@Override
		public String getFetishDesireDescription(GameCharacter target, FetishDesire desire) {
			return getGenericFetishDesireDescription(target, desire, "pain and humiliation");
		}

		@Override
		public CorruptionLevel getAssociatedCorruptionLevel() {
			return CorruptionLevel.THREE_DIRTY;
		}
	},

	FETISH_SADIST(60,
			"sadist",
			"inflicting pain",
			"fetish_sadist",
			FetishExperience.BASE_EXPERIENCE_GAIN,
			Colour.GENERIC_ARCANE,
			Util.newHashMapOfValues(new Value<Attribute, Integer>(Attribute.DAMAGE_PHYSICAL, 5)),
			Util.newArrayListOfValues(
					"All dealt [style.colourHealth("+Attribute.HEALTH_MAXIMUM.getName()+" damage)] is increased by 5%",
					"You take 10% of dealt [style.colourHealth("+Attribute.HEALTH_MAXIMUM.getName()+" damage)] as lust damage",
					"Gain [style.boldArcane(1 essence)] when you critically hit"),
			null) {

		@Override
		public String getDescription(GameCharacter owner) {
			if (owner.isPlayer())
				return "You love dishing out pain and humiliation, and causing others to suffer sends you absolutely wild with lust.";
			else
				return UtilText.parse(owner, "[npc.Name] has a fetish for dealing out pain and humiliation.");
		}

		@Override
		public String getFetishDesireDescription(GameCharacter target, FetishDesire desire) {
			return getGenericFetishDesireDescription(target, desire, "inflicting pain and humiliation on others");
		}

		@Override
		public CorruptionLevel getAssociatedCorruptionLevel() {
			return CorruptionLevel.THREE_DIRTY;
		}
	},

	FETISH_NON_CON_DOM(60,
			"non-consent",
			"raping",
			"fetish_noncon_dom",
			FetishExperience.BASE_EXPERIENCE_GAIN,
			Colour.GENERIC_ARCANE,
			null,
			Util.newArrayListOfValues(
					"Increases arousal gain when partner is resisting sex"),
			null) {

		@Override
		public String getDescription(GameCharacter owner) {
			if (owner.isPlayer()) {
				return "You love nothing more than when someone's being forced, against their will, to have sex. The more they're struggling, the more you get turned on...";

			} else {
				return UtilText.parse(owner, "[npc.Name] has a fetish for non-consensual encounters. The more [npc.her] victim struggles, the more [npc.she] gets turned on...");
			}
		}

		@Override
		public String getFetishDesireDescription(GameCharacter target, FetishDesire desire) {
			return getGenericFetishDesireDescription(target, desire, "having sex with an unwilling partner");
		}

		@Override
		public CorruptionLevel getAssociatedCorruptionLevel() {
			return CorruptionLevel.FIVE_CORRUPT;
		}
	},

	FETISH_NON_CON_SUB(60,
			"unwilling fuck-toy",
			"being raped",
			"fetish_noncon_sub",
			FetishExperience.BASE_EXPERIENCE_GAIN,
			Colour.GENERIC_ARCANE,
			null,
			Util.newArrayListOfValues(
					"Increases arousal gain when you are resisting sex"),
			null) {

		@Override
		public String getDescription(GameCharacter owner) {
			if (owner.isPlayer()) {
				return "You love nothing more than when you're forced, against your will, to have sex with someone. Struggling and pleading to be released turns you on like nothing else...";

			} else {
				return UtilText.parse(owner, "[npc.Name] has a fetish for being the victim in non-consensual encounters. Struggling and pleading to be released turns [npc.herHim] on like nothing else...");
			}
		}

		@Override
		public String getFetishDesireDescription(GameCharacter target, FetishDesire desire) {
			return getGenericFetishDesireDescription(target, desire, "being forced to have sex against your will");
		}

		@Override
		public CorruptionLevel getAssociatedCorruptionLevel() {
			return CorruptionLevel.FOUR_LUSTFUL;
		}
	},

	FETISH_EXHIBITIONIST(60,
			"exhibitionist",
			"exposing themself",
			"fetish_exhibitionist",
			FetishExperience.BASE_EXPERIENCE_GAIN,
			Colour.GENERIC_ARCANE,
			null,
			Util.newArrayListOfValues(
					"Replaces exposed status effects"
										+" with beneficial versions"),
			null) {

		@Override
		public String getDescription(GameCharacter owner) {
			if (owner.isPlayer())
				return "You love showing off your body, and the act of parading your naked form in public places turns you on like nothing else.";
			else
				return UtilText.parse(owner, "[npc.Name] has a fetish for exhibiting [npc.her] body.");
		}

		@Override
		public String getFetishDesireDescription(GameCharacter target, FetishDesire desire) {
			return getGenericFetishDesireDescription(target, desire, "exposing yourself to others");
		}

		@Override
		public CorruptionLevel getAssociatedCorruptionLevel() {
			return CorruptionLevel.THREE_DIRTY;
		}
	},

	FETISH_VOYEURIST(60,
			"voyeurist",
			"watching others",
			"fetish_voyeurist",
			FetishExperience.BASE_EXPERIENCE_GAIN,
			Colour.GENERIC_ARCANE,
			null,
			Util.newArrayListOfValues(
					"Arousal boost while watching sex scenes"),
			null) {

		@Override
		public String getDescription(GameCharacter owner) {
			if (owner.isPlayer()) {
				return "You love watching other people... Especially while they're doing something naughty...";
			} else {
				return UtilText.parse(owner, "[npc.Name] has a fetish for watching other people...");
			}
		}

		@Override
		public String getFetishDesireDescription(GameCharacter target, FetishDesire desire) {
			return getGenericFetishDesireDescription(target, desire, "watching others");
		}

		@Override
		public CorruptionLevel getAssociatedCorruptionLevel() {
			return CorruptionLevel.THREE_DIRTY;
		}
	},

	FETISH_BIMBO(60,
			"bimbo",
			"being a bimbo",
			null,
			FetishExperience.BASE_EXPERIENCE_GAIN,
			Colour.GENERIC_ARCANE,
			Util.newHashMapOfValues(new Value<Attribute, Integer>(Attribute.DAMAGE_LUST, 10)),
			Util.newArrayListOfValues("Talk like a bimbo"),
			null) {

		@Override
		public String getName(GameCharacter owner) {
			if(owner==null ||owner.isFeminine()) {
				return "bimbo";
			} else {
				return "bro";
			}
		}

		@Override
		public String getShortDescriptor(GameCharacter target) {
			if (target==null ||target.isFeminine()) {
				return "being a bimbo";
			} else {
				return "being a bro";
			}
		}

		@Override
		public String getDescription(GameCharacter owner) {
			if (owner==null || owner.isFeminine()) {
				return UtilText.parse(owner,
						"[npc.NameIs] obsessed with the idea of acting like a complete bimbo."
						+ " It's gotten to the point where no matter how intelligent [npc.she] might actually be, [npc.she] can't imagine [npc.herself] as anything other than a ditzy airhead.");
			} else {
				return UtilText.parse(owner,
						"[npc.NameIs] obsessed with the idea of acting like a dopey surfer bro."
						+ " It's gotten to the point where no matter how intelligent [npc.she] might actually be, [npc.she] can't imagine [npc.herself] as anything other than an airheaded meathead.");
			}
		}

		@Override
		public String getFetishDesireDescription(GameCharacter target, FetishDesire desire) {
			if(target==null || target.isFeminine()) {
				return getGenericFetishDesireDescription(target, desire, "acting like a bimbo");
			} else {
				return getGenericFetishDesireDescription(target, desire, "acting like a bro");
			}
		}

		@Override
		public List<String> getExtraEffects(GameCharacter owner) {
			if(owner==null || owner.isFeminine()) {
				return Util.newArrayListOfValues("Talk like a bimbo");
			} else {
				return Util.newArrayListOfValues("Talk like a bro");
			}
		}

		@Override
		public CorruptionLevel getAssociatedCorruptionLevel() {
			return CorruptionLevel.ONE_VANILLA;
		}

		@Override
		public String getSVGString(GameCharacter owner) {
//			String bimboString = "";
//			if(Main.game!=null && Main.game.isStarted()) {
//				try {
//					InputStream is = Fetish.class.getClassLoader().getResourceAsStream("/com/lilithsthrone/res/fetishes/fetish_bimbo.svg");
//					if(is==null) {
//						System.err.println("Error! Fetish icon file does not exist (Trying to read from 'com/lilithsthrone/res/fetishes/fetish_bimbo')!");
//					}
//					bimboString = Util.inputStreamToString(is);
//					bimboString = SvgUtil.colourReplacement("FETISH_BIMBO", Colour.BASE_PINK, bimboString);
//					is.close();
//				} catch (IOException e) {
//					e.printStackTrace();
//				}
//			}
//			return bimboString;
			if(owner==null || owner.isFeminine()) {
				return bimboString;
			} else {
				return broString;
			}
		}
	},

	FETISH_CROSS_DRESSER(60,
			"cross dressing",
			"cross dressing",
			"fetish_cross_dresser",
			FetishExperience.BASE_EXPERIENCE_GAIN,
			Colour.GENERIC_ARCANE,
			Util.newHashMapOfValues(new Value<Attribute, Integer>(Attribute.MANA_MAXIMUM, 10)),
			Util.newArrayListOfValues("Immune to clothing femininity status effects"),
			null) {

		@Override
		public String getDescription(GameCharacter owner) {
			if (owner.isPlayer()) {
				return "You love wearing all manner of different clothing, and you don't really care if it's too masculine or feminine for your body.";
			} else {
				return UtilText.parse(owner, "[npc.Name] loves wearing all manner of different clothing, and [npc.she] doesn't really care if it's too masculine or feminine for [npc.her] body.");
			}
		}

		@Override
		public String getFetishDesireDescription(GameCharacter target, FetishDesire desire) {
			return getGenericFetishDesireDescription(target, desire, "wearing clothing more suited to the opposite gender");
		}

		@Override
		public CorruptionLevel getAssociatedCorruptionLevel() {
			return CorruptionLevel.ONE_VANILLA;
		}
	},

	// Derived fetishes:

	FETISH_SWITCH(60,
			"switch",
			"being a switch",
			"fetish_switch",
			FetishExperience.BASE_EXPERIENCE_GAIN,
			Colour.GENERIC_ARCANE,
			Util.newHashMapOfValues(
					new Value<Attribute, Integer>(Attribute.MAJOR_PHYSIQUE, 5)),
			null,
			Util.newArrayListOfValues(
					Fetish.FETISH_DOMINANT,
					Fetish.FETISH_SUBMISSIVE)) {

		@Override
		public String getDescription(GameCharacter owner) {
			if (owner.isPlayer())
				return "You're perfectly happy with switching between dom and sub as the situation calls for it.";
			else
				return UtilText.parse(owner, "[npc.Name] is happy to play as either the dom or sub during sex.");
		}

		@Override
		public String getFetishDesireDescription(GameCharacter target, FetishDesire desire) {
			return getGenericFetishDesireDescription(target, desire, "switching between dom and sub");
		}

		@Override
		public CorruptionLevel getAssociatedCorruptionLevel() {
			return CorruptionLevel.THREE_DIRTY;
		}
	},

	FETISH_BREEDER(60,
			"breeder",
			"breeding",
			"fetish_breeder",
			FetishExperience.BASE_RARE_EXPERIENCE_GAIN,
			Colour.GENERIC_ARCANE,
			Util.newHashMapOfValues(
					new Value<Attribute, Integer>(Attribute.FERTILITY, 25),
					new Value<Attribute, Integer>(Attribute.VIRILITY, 25)),
			null,
			Util.newArrayListOfValues(
					Fetish.FETISH_PREGNANCY,
					Fetish.FETISH_IMPREGNATION)) {

		@Override
		public String getDescription(GameCharacter owner) {
			if (owner.isPlayer())
				return "You have a dream. A dream of a world in which everyone is pregnant, including yourself!";
			else
				return UtilText.parse(owner, "[npc.Name] wants nothing more than to share [npc.her] love of pregnancies with everyone [npc.she] meets.");
		}

		@Override
		public String getFetishDesireDescription(GameCharacter target, FetishDesire desire) {
			return getGenericFetishDesireDescription(target, desire, "anything to do with reproduction");
		}

		@Override
		public CorruptionLevel getAssociatedCorruptionLevel() {
			return CorruptionLevel.THREE_DIRTY;
		}
	},

	FETISH_SADOMASOCHIST(60,
			"sadomasochist",
			"sadomasochism",
			"fetish_sadomasochist",
			FetishExperience.BASE_EXPERIENCE_GAIN,
			Colour.GENERIC_ARCANE,
			Util.newHashMapOfValues(
					new Value<Attribute, Integer>(Attribute.RESISTANCE_PHYSICAL, 3),
					new Value<Attribute, Integer>(Attribute.DAMAGE_PHYSICAL, 10)),
			null,
			Util.newArrayListOfValues(
					Fetish.FETISH_SADIST,
					Fetish.FETISH_MASOCHIST)) {

		@Override
		public String getDescription(GameCharacter owner) {
			if (owner.isPlayer())
				return "You don't care whether you're on the giving or receiving end; if there's pain and humiliation involved, you're up for anything.";
			else
				return UtilText.parse(owner, "[npc.Name] loves pain and humiliation in all of its forms.");
		}

		@Override
		public String getFetishDesireDescription(GameCharacter target, FetishDesire desire) {
			return getGenericFetishDesireDescription(target, desire, "all forms of pain and humiliation");
		}

		@Override
		public CorruptionLevel getAssociatedCorruptionLevel() {
			return CorruptionLevel.THREE_DIRTY;
		}
	},

	FETISH_LUSTY_MAIDEN(60,
			"lusty maiden",
			"lusty maiden",
			"fetish_lusty_maiden",
			FetishExperience.BASE_RARE_EXPERIENCE_GAIN,
			Colour.GENERIC_ARCANE,
			null,
			Util.newArrayListOfValues(
					"Empowers 'pure virgin'",
					"Amplifies 'broken virgin'"),
			Util.newArrayListOfValues(
					Fetish.FETISH_PURE_VIRGIN,
					Fetish.FETISH_ANAL_RECEIVING,
					Fetish.FETISH_ORAL_GIVING,
					Fetish.FETISH_BREASTS_SELF)) {

		@Override
		public String getDescription(GameCharacter owner) {
			if (owner.isPlayer()) {
				return "You are the ultimate tease, seducing and pleasuring others with your ass, mouth, breasts, and even the promise of your pussy,"
							+ " but you'll never actually allow anyone to penetrate your feminine sex and take your precious virginity.";
			} else {
				return UtilText.parse(owner, "[npc.Name] loves to pleasure others with [npc.her] ass, mouth, breasts, and even the promise of [npc.her] pussy,"
							+ " but [npc.she]'ll never actually allow anyone to penetrate [npc.her] feminine sex and take [npc.her] precious virginity.");
			}
		}

		@Override
		public String getFetishDesireDescription(GameCharacter target, FetishDesire desire) {
			return getGenericFetishDesireDescription(target, desire, "avoiding vaginal sex");
		}

		@Override
		public CorruptionLevel getAssociatedCorruptionLevel() {
			return CorruptionLevel.TWO_HORNY;
		}
	};

	private int renderingPriority;
	protected String name;
	protected String shortDescriptor;
	private int experienceGainFromSexAction;
	private HashMap<Attribute, Integer> attributeModifiers;

	private String SVGString;

	private List<String> extraEffects;

	private List<String> modifiersList;

	private List<Fetish> fetishesForAutomaticUnlock;

	private static String bimboString = "";
	private static String broString = "";
	static {
		try {
			InputStream is = Subspecies.class.getClassLoader().getResourceAsStream("com/lilithsthrone/res/fetishes/fetish_bimbo.svg");
			if(is==null) {
				System.err.println("Error! Fetish icon file does not exist (Trying to read from 'com/lilithsthrone/res/fetishes/fetish_bimbo')!");
			}
			bimboString = Util.inputStreamToString(is);
			bimboString = SvgUtil.colourReplacement("FETISH_BIMBO", Colour.BASE_PINK, bimboString);
			is.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		try {
			InputStream is = Subspecies.class.getClassLoader().getResourceAsStream("com/lilithsthrone/res/fetishes/fetish_bro.svg");
			broString = Util.inputStreamToString(is);
			broString = SvgUtil.colourReplacement("FETISH_BRO", Colour.BASE_BLUE, broString);
			is.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private Fetish(
			int renderingPriority,
			String name,
			String shortDescriptor,
			String pathName,
			FetishExperience experienceGainFromSexAction,
			Colour colourShade,
			HashMap<Attribute, Integer> attributeModifiers,
			List<String> extraEffects,
			List<Fetish> fetishesForAutomaticUnlock) {

		this.renderingPriority = renderingPriority;
		this.name = name;
		this.shortDescriptor = shortDescriptor;
		this.experienceGainFromSexAction = experienceGainFromSexAction.getExperience();

		this.attributeModifiers = attributeModifiers;

		this.extraEffects = extraEffects;

		if(fetishesForAutomaticUnlock==null) {
			this.fetishesForAutomaticUnlock = new ArrayList<>();
		} else {
			this.fetishesForAutomaticUnlock = fetishesForAutomaticUnlock;
		}

		if(pathName!=null) {
			try {
				InputStream is = this.getClass().getResourceAsStream("/com/lilithsthrone/res/fetishes/" + pathName + ".svg");
				if(is==null) {
					System.err.println("Error! Fetish icon file does not exist (Trying to read from '"+pathName+"')!");
				}
				SVGString = Util.inputStreamToString(is);
				SVGString = SvgUtil.colourReplacement(this.toString(), colourShade, SVGString);
				is.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		} else {
			SVGString = "";
		}

		modifiersList = new ArrayList<>();

		if(attributeModifiers != null) {
			for (Entry<Attribute, Integer> e : attributeModifiers.entrySet()) {
				modifiersList.add(""+(e.getValue() > 0 ? "+" : "") + e.getValue() + " "+ Util.capitaliseSentence(e.getKey().getAbbreviatedName())+ "");
			}
		}
	}

	public List<Fetish> getFetishesForAutomaticUnlock() {
		return fetishesForAutomaticUnlock;
	}

	public boolean isAvailable(GameCharacter character) {
		return true;
	}

	private static List<String> perkRequirementsList = new ArrayList<>();
	public List<String> getPerkRequirements(GameCharacter character) {
		perkRequirementsList.clear();

		return perkRequirementsList;
	}

	public String getName(GameCharacter owner) {
		return name;
	}

	public String getShortDescriptor(GameCharacter target) {
		return shortDescriptor;
	}

	public abstract String getDescription(GameCharacter target);

	public abstract String getFetishDesireDescription(GameCharacter target, FetishDesire desire);

	private static String getGenericFetishDesireDescription(GameCharacter target, FetishDesire desire, String descriptor) {
		switch(desire) {
			case ZERO_HATE:
				return UtilText.parse(target, "You absolutely hate "+descriptor+".");
			case ONE_DISLIKE:
				return UtilText.parse(target, "You don't like "+descriptor+".");
			case TWO_NEUTRAL:
				return UtilText.parse(target, "You are indifferent to "+descriptor+".");
			case THREE_LIKE:
				return UtilText.parse(target, "You like "+descriptor+".");
			case FOUR_LOVE:
				return UtilText.parse(target, "You love "+descriptor+".");
		}
		return "";
	}

	public int getExperienceGainFromSexAction() {
		return experienceGainFromSexAction;
	}

	public int getCost() {
		return 5;
	}

	public List<String> getModifiersAsStringList(GameCharacter owner) {
		List<String> modList = new ArrayList<>(modifiersList);
		if(getExtraEffects(owner) != null) {
			modList.addAll(getExtraEffects(owner));
		}
		return modList;
	}

	public HashMap<Attribute, Integer> getAttributeModifiers() {
		return attributeModifiers;
	}

	public String applyPerkGained(GameCharacter character) {
		return "";
	}

	public String applyPerkLost(GameCharacter character){
		return "";
	}

	public Fetish getPreviousLevelPerk() {
		return null;
	}

	public Perk getNextLevelPerk() {
		return null;
	}

	public CorruptionLevel getAssociatedCorruptionLevel() {
		return CorruptionLevel.ZERO_PURE;
	}

	public int getRenderingPriority() {
		return renderingPriority;
	}

	public List<String> getExtraEffects(GameCharacter owner) {
		return extraEffects;
	}

	public String getSVGString(GameCharacter owner) {
		return SVGString;
	}

	public static int getExperienceGainFromTakingVaginalVirginity(GameCharacter owner) {
		return owner.getLevel()*2;
	}

	public static int getExperienceGainFromTakingOtherVirginity(GameCharacter owner) {
		return owner.getLevel();
	}
}


code:
package com.lilithsthrone.game.character.fetishes;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

import com.lilithsthrone.game.character.attributes.Attribute;
import com.lilithsthrone.utils.Colour;
import com.lilithsthrone.utils.SvgUtil;
import com.lilithsthrone.utils.Util;

/**
 * @since 0.1.99
 * @version 0.2.9
 * @author Innoxia
 */
public enum FetishDesire {

	ZERO_HATE(0, "hate", "hate", "hates", "fondness1", Colour.BASE_CRIMSON),

	ONE_DISLIKE(1, "dislike", "dislike", "dislikes", "fondness2", Colour.BASE_RED),

	TWO_NEUTRAL(2, "indifferent", "are indifferent to", "is indifferent to", "fondness3", Colour.BASE_BLUE_STEEL),

	THREE_LIKE(3, "like", "like", "likes", "fondness4", Colour.BASE_PINK_LIGHT),

	FOUR_LOVE(4, "love", "love", "loves", "fondness5", Colour.BASE_PINK);

	private int value;
	private String name;
	private String nameAsPlayerVerb;
	private String nameAsVerb;
	private String SVGImage;
	private String SVGImageDesaturated;
	private Colour colour;
	private List<String> modifiersList;

	private FetishDesire(int value, String name, String nameAsPlayerVerb, String nameAsVerb, String pathName, Colour colour) {
		this.value = value;
		this.name = name;
		this.nameAsPlayerVerb = nameAsPlayerVerb;
		this.nameAsVerb = nameAsVerb;
		this.colour = colour;

		modifiersList = new ArrayList<>();
		modifiersList.add("Modifies sex actions' [style.boldLust("+ Util.capitaliseSentence(Attribute.LUST.getAbbreviatedName())+ " increments)]");

		try {
			InputStream is = this.getClass().getResourceAsStream("/com/lilithsthrone/res/fetishes/" + pathName + ".svg");
			if(is==null) {
				System.err.println("Error! FetishDesire icon file does not exist (Trying to read from '"+pathName+"')!");
			}
			String base = Util.inputStreamToString(is);

			SVGImage = base;

			SVGImage = SvgUtil.colourReplacement(this.toString(), colour, SVGImage);

			SVGImageDesaturated = base;

			SVGImageDesaturated = SvgUtil.colourReplacement(this.toString(), Colour.BASE_GREY, SVGImageDesaturated);

			is.close();

		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public boolean isNegative() {
		return this==ZERO_HATE || this==ONE_DISLIKE;
	}

	public boolean isPositive() {
		return this==THREE_LIKE || this==FOUR_LOVE;
	}

	public static int getCostToChange() {
		return 0;
	}

	public static FetishDesire getDesireFromValue(int value) {
		for(FetishDesire desire : FetishDesire.values()) {
			if(desire.getValue() == value) {
				return desire;
			}
		}

		if(value<=ZERO_HATE.getValue()) {
			return ZERO_HATE;
		} else {
			return FOUR_LOVE;
		}
	}

	public FetishDesire getPreviousDesire() {
		switch(this) {
			case ZERO_HATE:
				return ZERO_HATE;
			case ONE_DISLIKE:
				return ZERO_HATE;
			case TWO_NEUTRAL:
				return ONE_DISLIKE;
			case THREE_LIKE:
				return TWO_NEUTRAL;
			case FOUR_LOVE:
				return THREE_LIKE;
		}
		return TWO_NEUTRAL;
	}

	public FetishDesire getNextDesire() {
		switch(this) {
			case ZERO_HATE:
				return ONE_DISLIKE;
			case ONE_DISLIKE:
				return TWO_NEUTRAL;
			case TWO_NEUTRAL:
				return THREE_LIKE;
			case THREE_LIKE:
				return FOUR_LOVE;
			case FOUR_LOVE:
				return FOUR_LOVE;
		}
		return TWO_NEUTRAL;
	}

	public int getValue() {
		return value;
	}

	public String getName() {
		return name;
	}

	public String getNameAsPlayerVerb() {
		return nameAsPlayerVerb;
	}

	public String getNameAsVerb() {
		return nameAsVerb;
	}

	public String getSVGImage() {
		return SVGImage;
	}

	public String getSVGImageDesaturated() {
		return SVGImageDesaturated;
	}

	public Colour getColour() {
		return colour;
	}

	public List<String> getModifiersAsStringList() {
		return modifiersList;
	}

}


code:
package com.lilithsthrone.game.character.fetishes;

/**
 * @since 0.2.12
 * @version 0.2.12
 * @author Innoxia
 */
public enum FetishExperience {

	BASE_EXPERIENCE_GAIN(2),
	BASE_RARE_EXPERIENCE_GAIN(10),
	BASE_VERY_RARE_EXPERIENCE_GAIN(25);

	int experience;

	private FetishExperience(int experience) {
		this.experience = experience;
	}

	public int getExperience() {
		return experience;
	}
}


code:
package com.lilithsthrone.game.character.fetishes;

import java.io.IOException;
import java.io.InputStream;

import com.lilithsthrone.utils.Colour;
import com.lilithsthrone.utils.SvgUtil;
import com.lilithsthrone.utils.Util;

/**
 * @since 0.1.99
 * @version 0.1.99
 * @author Innoxia
 */
public enum FetishLevel {

	ZERO_NO_EXPERIENCE("inexperienced", "I", "", "overlay1", 0, 0, 10, Colour.DESIRE_STAGE_ZERO),

	ONE_AMATEUR("amateur", "II", "", "overlay2", 0.5f, 10, 50, Colour.DESIRE_STAGE_ONE),

	TWO_EXPERIENCED("experienced", "III", "", "overlay3", 1f, 50, 100, Colour.DESIRE_STAGE_TWO),

	THREE_EXPERT("expert", "IV", "", "overlay4", 2f, 100, 200, Colour.DESIRE_STAGE_THREE),

	FOUR_MASTERFUL("masterful", "V", "", "overlay5", 2.5f, 200, 200, Colour.DESIRE_STAGE_FOUR);

	private String name;
	private String numeral;
	private String description;
	private String SVGImageOverlay;
	private float bonusArousalIncrease;
	private int minimumExperience;
	private int maximumExperience;
	private Colour colour;

	private FetishLevel(String name, String numeral, String description, String pathName, float bonusArousalIncrease, int minimumExperience, int maximumExperience, Colour colour) {
		this.name = name;
		this.numeral = numeral;
		this.description = description;
		this.bonusArousalIncrease = bonusArousalIncrease;
		this.minimumExperience = minimumExperience;
		this.maximumExperience = maximumExperience;
		this.colour = colour;

		try {
			InputStream is = this.getClass().getResourceAsStream("/com/lilithsthrone/res/fetishes/" + pathName + ".svg");
			if(is==null) {
				System.err.println("Error! FetishLevel icon file does not exist (Trying to read from '"+pathName+"')!");
			}
			SVGImageOverlay = Util.inputStreamToString(is);

			SVGImageOverlay = SvgUtil.colourReplacement(this.toString(), Colour.BASE_PINK, SVGImageOverlay);

			is.close();

		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public String getName() {
		return name;
	}

	public String getNumeral() {
		return numeral;
	}

	public String getDescription() {
		return description;
	}

	public String getSVGImageOverlay() {
		return SVGImageOverlay;
	}

	public float getBonusArousalIncrease() {
		return bonusArousalIncrease;
	}

	public float getBonusArousalIncreasePartner() {
		return Math.round((bonusArousalIncrease/2f)*100)/100f;
	}

	public int getMinimumExperience() {
		return minimumExperience;
	}

	public int getMaximumExperience() {
		return maximumExperience;
	}

	public Colour getColour() {
		return colour;
	}

	public static FetishLevel getFetishLevelFromValue(int value){
		for(FetishLevel fl : FetishLevel.values()) {
			if(value>=fl.getMinimumExperience() && value<fl.getMaximumExperience())
				return fl;
		}
		return FOUR_MASTERFUL;
	}

}


code:
package com.lilithsthrone.game.character.gender;

import com.lilithsthrone.utils.Colour;

/**
 * @since 0.1.69
 * @version 0.1.69
 * @author Innoxia
 */
public enum AndrogynousIdentification {
	FEMININE("feminine", Colour.FEMININE),
	CLOTHING_FEMININE("clothing feminine", Colour.ANDROGYNOUS),
	CLOTHING_MASCULINE("clothing masculine", Colour.ANDROGYNOUS),
	MASCULINE("masculine", Colour.MASCULINE);

	private String name;
	private Colour colour;

	private AndrogynousIdentification(String name, Colour colour) {
		this.name = name;
		this.colour = colour;
	}

	public String getName() {
		return name;
	}

	public Colour getColour() {
		return colour;
	}
}


code:
package com.lilithsthrone.game.character.gender;

import java.util.HashMap;
import java.util.Map;

import com.lilithsthrone.game.character.body.valueEnums.Femininity;
import com.lilithsthrone.game.settings.ContentPreferenceValue;
import com.lilithsthrone.main.Main;
import com.lilithsthrone.utils.Colour;
import com.lilithsthrone.utils.Util;

/**
 * @since 0.1.0
 * @version 0.1.86
 * @author Innoxia
 */
public enum Gender {

	// Masculine:
	M_P_V_B_HERMAPHRODITE(GenderNames.Y_PENIS_Y_VAGINA_Y_BREASTS, PronounType.MASCULINE, Colour.MASCULINE, ContentPreferenceValue.ZERO_NONE),
	M_P_V_HERMAPHRODITE(GenderNames.Y_PENIS_Y_VAGINA_N_BREASTS, PronounType.MASCULINE, Colour.MASCULINE, ContentPreferenceValue.ZERO_NONE),
	M_P_B_BUSTYBOY(GenderNames.Y_PENIS_N_VAGINA_Y_BREASTS, PronounType.MASCULINE, Colour.MASCULINE, ContentPreferenceValue.ZERO_NONE),
	M_P_MALE(GenderNames.Y_PENIS_N_VAGINA_N_BREASTS, PronounType.MASCULINE, Colour.MASCULINE, ContentPreferenceValue.THREE_AVERAGE),
	M_V_B_BUTCH(GenderNames.N_PENIS_Y_VAGINA_Y_BREASTS, PronounType.MASCULINE, Colour.MASCULINE, ContentPreferenceValue.ZERO_NONE),
	M_V_CUNTBOY(GenderNames.N_PENIS_Y_VAGINA_N_BREASTS, PronounType.MASCULINE, Colour.MASCULINE, ContentPreferenceValue.ZERO_NONE),
	M_B_MANNEQUIN(GenderNames.N_PENIS_N_VAGINA_Y_BREASTS, PronounType.MASCULINE, Colour.MASCULINE, ContentPreferenceValue.ZERO_NONE),
	M_MANNEQUIN(GenderNames.N_PENIS_N_VAGINA_N_BREASTS, PronounType.MASCULINE, Colour.MASCULINE, ContentPreferenceValue.ZERO_NONE),

	// Feminine:
	F_P_V_B_FUTANARI(GenderNames.Y_PENIS_Y_VAGINA_Y_BREASTS, PronounType.FEMININE, Colour.FEMININE, ContentPreferenceValue.ONE_MINIMAL),
	F_P_V_FUTANARI(GenderNames.Y_PENIS_Y_VAGINA_N_BREASTS, PronounType.FEMININE, Colour.FEMININE, ContentPreferenceValue.ZERO_NONE),
	F_P_B_SHEMALE(GenderNames.Y_PENIS_N_VAGINA_Y_BREASTS, PronounType.FEMININE, Colour.FEMININE, ContentPreferenceValue.ZERO_NONE),
	F_P_TRAP(GenderNames.Y_PENIS_N_VAGINA_N_BREASTS, PronounType.FEMININE, Colour.FEMININE, ContentPreferenceValue.ONE_MINIMAL),
	F_V_B_FEMALE(GenderNames.N_PENIS_Y_VAGINA_Y_BREASTS, PronounType.FEMININE, Colour.FEMININE, ContentPreferenceValue.THREE_AVERAGE),
	F_V_FEMALE(GenderNames.N_PENIS_Y_VAGINA_N_BREASTS, PronounType.FEMININE, Colour.FEMININE, ContentPreferenceValue.ZERO_NONE),
	F_B_DOLL(GenderNames.N_PENIS_N_VAGINA_Y_BREASTS, PronounType.FEMININE, Colour.FEMININE, ContentPreferenceValue.ZERO_NONE),
	F_DOLL(GenderNames.N_PENIS_N_VAGINA_N_BREASTS, PronounType.FEMININE, Colour.FEMININE, ContentPreferenceValue.ZERO_NONE),

	// Neutral (treated as feminine for now):
	N_P_V_B_HERMAPHRODITE(GenderNames.Y_PENIS_Y_VAGINA_Y_BREASTS, PronounType.NEUTRAL, Colour.ANDROGYNOUS, ContentPreferenceValue.ZERO_NONE),
	N_P_V_HERMAPHRODITE(GenderNames.Y_PENIS_Y_VAGINA_N_BREASTS, PronounType.NEUTRAL, Colour.ANDROGYNOUS, ContentPreferenceValue.ZERO_NONE),
	N_P_B_SHEMALE(GenderNames.Y_PENIS_N_VAGINA_Y_BREASTS, PronounType.NEUTRAL, Colour.ANDROGYNOUS, ContentPreferenceValue.ZERO_NONE),
	N_P_TRAP(GenderNames.Y_PENIS_N_VAGINA_N_BREASTS, PronounType.NEUTRAL, Colour.ANDROGYNOUS, ContentPreferenceValue.ZERO_NONE),
	N_V_B_TOMBOY(GenderNames.N_PENIS_Y_VAGINA_Y_BREASTS, PronounType.NEUTRAL, Colour.ANDROGYNOUS, ContentPreferenceValue.ZERO_NONE),
	N_V_TOMBOY(GenderNames.N_PENIS_Y_VAGINA_N_BREASTS, PronounType.NEUTRAL, Colour.ANDROGYNOUS, ContentPreferenceValue.ZERO_NONE),
	N_B_DOLL(GenderNames.N_PENIS_N_VAGINA_Y_BREASTS, PronounType.NEUTRAL, Colour.ANDROGYNOUS, ContentPreferenceValue.ZERO_NONE),
	N_NEUTER(GenderNames.N_PENIS_N_VAGINA_N_BREASTS, PronounType.NEUTRAL, Colour.ANDROGYNOUS, ContentPreferenceValue.ZERO_NONE);

	private GenderNames name;
	private Colour colour;
	private PronounType type;
	private ContentPreferenceValue genderPreferenceDefault;

	private Gender(GenderNames name, PronounType type, Colour colour, ContentPreferenceValue genderPreferenceDefault) {
		this.name = name;
		this.type = type;
		this.colour = colour;
		this.genderPreferenceDefault = genderPreferenceDefault;
	}

	public GenderNames getGenderName() {
		return name;
	}

	public PronounType getType() {
		return type;
	}

	public boolean isFeminine() {
		return type == PronounType.FEMININE || type == PronounType.NEUTRAL;
	}

	public Colour getColour() {
		return colour;
	}

	/**
	 * boy, girl
	 */
	public String getNounYoung() {
		if(isFeminine()) {
			return Main.getProperties().genderPronounFemale.get(GenderPronoun.YOUNG_NOUN);
		} else {
			return Main.getProperties().genderPronounMale.get(GenderPronoun.YOUNG_NOUN);
		}
	}

	/**
	 * man, woman
	 */
	public String getNoun() {
		if(isFeminine()) {
			return Main.getProperties().genderPronounFemale.get(GenderPronoun.NOUN);
		} else {
			return Main.getProperties().genderPronounMale.get(GenderPronoun.NOUN);
		}
	}

	public String getName() {
		switch(type) {
			case FEMININE:
				return Main.getProperties().genderNameFemale.get(name);
			case MASCULINE:
				return Main.getProperties().genderNameMale.get(name);
			case NEUTRAL:
				return Main.getProperties().genderNameNeutral.get(name);
		}
		return Main.getProperties().genderNameFemale.get(name);
	}

	/**
	 * Possessive: his, her
	 */
	public String getPossessiveBeforeNoun() {
		if(isFeminine()) {
			return Main.getProperties().genderPronounFemale.get(GenderPronoun.POSSESSIVE_BEFORE_NOUN);
		} else {
			return Main.getProperties().genderPronounMale.get(GenderPronoun.POSSESSIVE_BEFORE_NOUN);
		}
	}

	/**
	 * Possessive: his, hers
	 */
	public String getPossessiveAlone() {
		if(isFeminine()) {
			return Main.getProperties().genderPronounFemale.get(GenderPronoun.POSSESSIVE_ALONE);
		} else {
			return Main.getProperties().genderPronounMale.get(GenderPronoun.POSSESSIVE_ALONE);
		}
	}

	/**
	 * Second person: he, she
	 */
	public String getSecondPerson() {
		if(isFeminine()) {
			return Main.getProperties().genderPronounFemale.get(GenderPronoun.SECOND_PERSON);
		} else {
			return Main.getProperties().genderPronounMale.get(GenderPronoun.SECOND_PERSON);
		}
	}

	/**
	 * Pronoun: him, her
	 */
	public String getThirdPerson() {
		if(isFeminine()) {
			return Main.getProperties().genderPronounFemale.get(GenderPronoun.THIRD_PERSON);
		} else {
			return Main.getProperties().genderPronounMale.get(GenderPronoun.THIRD_PERSON);
		}
	}

	public ContentPreferenceValue getGenderPreferenceDefault() {
		return genderPreferenceDefault;
	}

	public static Gender getGenderFromUserPreferences(boolean requiresVagina, boolean requiresPenis) {
		Map<Gender, Integer> genderMap = new HashMap<>();

		for(Gender g : Gender.values()) {
			if((!requiresVagina || g.getGenderName().isHasVagina())
					&& (!requiresPenis || g.getGenderName().isHasPenis())
					&& Main.getProperties().genderPreferencesMap.get(g)>0) {
				genderMap.put(g, Main.getProperties().genderPreferencesMap.get(g));
			}
		}

		if(genderMap.isEmpty()) {
			if(Math.random()>0.5f || requiresVagina) {
				if(requiresVagina && requiresPenis) {
					return Gender.F_P_V_B_FUTANARI;

				} else if(requiresPenis) {
					return Gender.F_P_B_SHEMALE;
				}
				return Gender.F_V_B_FEMALE;

			} else {
				return Gender.M_P_MALE;
			}
		}

		return Util.getRandomObjectFromWeightedMap(genderMap);
	}

	public static Gender getGenderFromUserPreferences(Femininity femininity) {
		Map<Gender, Integer> genderMap = new HashMap<>();

		for(Gender g : Gender.values()) {
			if(g.isFeminine() == femininity.isFeminine()
					&& Main.getProperties().genderPreferencesMap.get(g)>0) {
				genderMap.put(g, Main.getProperties().genderPreferencesMap.get(g));
			}
		}

		if(genderMap.isEmpty()) {
			if(femininity.isFeminine()) {
				return Gender.F_V_B_FEMALE;
			} else {
				return Gender.M_P_MALE;
			}
		}

		return Util.getRandomObjectFromWeightedMap(genderMap);
	}

}


code:
package com.lilithsthrone.game.character.gender;

/**
 * @since 0.1.86
 * @version 0.1.97
 * @author Innoxia
 */
public enum GenderNames {

	Y_PENIS_Y_VAGINA_Y_BREASTS(true, true, true, "futanari", "hermaphrodite", "hermaphrodite"),
	Y_PENIS_Y_VAGINA_N_BREASTS(true, true, false, "futanari", "hermaphrodite", "hermaphrodite"),
	Y_PENIS_N_VAGINA_Y_BREASTS(true, false, true, "shemale", "shemale", "busty-boy"),
	Y_PENIS_N_VAGINA_N_BREASTS(true, false, false, "trap", "trap", "male"),
	N_PENIS_Y_VAGINA_Y_BREASTS(false, true, true, "female", "tomboy",  "butch"),
	N_PENIS_Y_VAGINA_N_BREASTS(false, true, false, "female", "tomboy", "cuntboy"),
	N_PENIS_N_VAGINA_Y_BREASTS(false, false, true, "doll", "neuter", "mannequin"),
	N_PENIS_N_VAGINA_N_BREASTS(false, false, false, "doll", "neuter", "mannequin");

	private boolean hasPenis, hasVagina, hasBreasts;
	private String feminine, masculine, neutral;

	private GenderNames(boolean hasPenis, boolean hasVagina, boolean hasBreasts, String feminine, String neutral, String masculine){
		this.hasPenis = hasPenis;
		this.hasVagina = hasVagina;
		this.hasBreasts = hasBreasts;
		this.feminine = feminine;
		this.neutral = neutral;
		this.masculine = masculine;
	}

	public boolean isHasPenis() {
		return hasPenis;
	}

	public boolean isHasVagina() {
		return hasVagina;
	}

	public boolean isHasBreasts() {
		return hasBreasts;
	}

	public String getFeminine() {
		return feminine;
	}

	public String getMasculine() {
		return masculine;
	}

	public String getNeutral() {
		return neutral;
	}
}


code:
package com.lilithsthrone.game.character.gender;

/**
 * @since 0.1.67
 * @version 0.1.86
 * @author Innoxia
 */
public enum GenderPronoun {

	NOUN("Noun", "woman", "man", "person"),
	YOUNG_NOUN("Young noun", "girl", "boy", "person"),

	SECOND_PERSON("Second person", "she", "he", "they"),
	THIRD_PERSON("Third person", "her", "him", "them"),
	POSSESSIVE_BEFORE_NOUN("Possessive before noun", "her", "his", "their"),
	POSSESSIVE_ALONE("Possessive alone", "hers", "his", "theirs");

	private String name, feminine, masculine, neutral;

	private GenderPronoun(String name, String feminine, String masculine, String neutral){
		this.name = name;
		this.feminine = feminine;
		this.masculine = masculine;
	}

	public String getName() {
		return name;
	}

	public String getFeminine() {
		return feminine;
	}

	public String getMasculine() {
		return masculine;
	}

	public String getNeutral() {
		return neutral;
	}
}


code:
package com.lilithsthrone.game.character.gender;

import com.lilithsthrone.utils.Colour;

/**
 * @since 0.1.86
 * @version 0.1.86
 * @author Innoxia
 */
public enum PronounType {
	FEMININE("feminine", Colour.FEMININE),
	NEUTRAL("androgynous", Colour.ANDROGYNOUS),
	MASCULINE("masculine", Colour.MASCULINE);

	private String name;
	private Colour colour;

	private PronounType(String name, Colour colour) {
		this.name = name;
		this.colour = colour;
	}

	public String getName() {
		return name;
	}

	public Colour getColour() {
		return colour;
	}
}


code:
package com.lilithsthrone.game.character.markings;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import com.lilithsthrone.game.character.GameCharacter;
import com.lilithsthrone.game.inventory.AbstractCoreType;
import com.lilithsthrone.game.inventory.InventorySlot;
import com.lilithsthrone.game.inventory.enchanting.AbstractItemEffectType;
import com.lilithsthrone.game.inventory.enchanting.ItemEffectType;
import com.lilithsthrone.utils.Colour;
import com.lilithsthrone.utils.ColourListPresets;
import com.lilithsthrone.utils.SvgUtil;
import com.lilithsthrone.utils.Util;

/**
 * @since 0.2.6
 * @version 0.2.6
 * @author Innoxia
 */
public class AbstractTattooType extends AbstractCoreType {

	private static List<InventorySlot> standardInventorySlots = new ArrayList<>(InventorySlot.getClothingSlots());

	private boolean isMod;

	private int value;

	@SuppressWarnings("unused")
	private int enchantmentLimit; // Removed as part of 0.3.3.7's update to add enchantment capacity mechanics.

	private List<InventorySlot> slotAvailability;

	private String name;
	private String description;

	private List<Colour> availablePrimaryColours;
	private List<Colour> availableSecondaryColours;
	private List<Colour> availableTertiaryColours;

	private String pathName;
	private Map<Colour, Map<Colour, Map<Colour, String>>> SVGStringMap;

	public AbstractTattooType(
			String pathName,
			String name,
			String description,
			List<Colour> availablePrimaryColours,
			List<Colour> availableSecondaryColours,
			List<Colour> availableTertiaryColours,
			List<InventorySlot> slotAvailability) {

		this.isMod = false;

		value = 500;
		enchantmentLimit = 5;

		this.pathName = pathName;
		this.name = name;
		this.description = description;

		this.availablePrimaryColours = new ArrayList<>();
		if (availablePrimaryColours == null) {
			this.availablePrimaryColours.add(Colour.CLOTHING_BLACK);
		} else {
			this.availablePrimaryColours.addAll(availablePrimaryColours);
		}

		this.availableSecondaryColours = new ArrayList<>();
		if (availableSecondaryColours != null) {
			this.availableSecondaryColours.addAll(availableSecondaryColours);
		}

		this.availableTertiaryColours = new ArrayList<>();
		if (availableTertiaryColours != null) {
			this.availableTertiaryColours.addAll(availableTertiaryColours);
		}

		SVGStringMap = new HashMap<>();

		if(slotAvailability==null) {
			this.slotAvailability = standardInventorySlots;
		} else {
			this.slotAvailability = slotAvailability;
		}
	}

	public AbstractTattooType(File tattooXMLFile) {

		if (tattooXMLFile.exists()) {
			try {
				DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
				DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
				Document doc = dBuilder.parse(tattooXMLFile);

				// Cast magic:
				doc.getDocumentElement().normalize();

				Element clothingElement = (Element) doc.getElementsByTagName("tattoo").item(0);

				Element coreAttributes;
				if(clothingElement.getElementsByTagName("coreAtributes").getLength()>0) {
					coreAttributes = (Element) clothingElement.getElementsByTagName("coreAtributes").item(0); // Support for old versions
				} else {
					coreAttributes = (Element) clothingElement.getElementsByTagName("coreAttributes").item(0); // Fix typo
				}

				List<InventorySlot> slotAvailability = new ArrayList<>();
				NodeList slotAvailabilityNodeList = ((Element)coreAttributes.getElementsByTagName("slotAvailability").item(0)).getElementsByTagName("slot");
				try {
					for(int i = 0; i < slotAvailabilityNodeList.getLength(); i++){
						Element e = ((Element)slotAvailabilityNodeList.item(i));
						slotAvailability.add(InventorySlot.valueOf(e.getTextContent()));
					}
					if (slotAvailability.isEmpty()) {
						slotAvailability = standardInventorySlots;
					}
				} catch(Exception ex) {
					System.err.println("AbstractTattooType loading failed. Cause: 'slotAvailability' element unable to be parsed. (" + tattooXMLFile.getName() + ")\n" + ex);
				}
				this.slotAvailability = slotAvailability;

				this.isMod = true;

				this.value = Integer.valueOf(coreAttributes.getElementsByTagName("value").item(0).getTextContent());
				this.pathName = tattooXMLFile.getParentFile().getAbsolutePath() + "/" + coreAttributes.getElementsByTagName("imageName").item(0).getTextContent();
				this.name = coreAttributes.getElementsByTagName("name").item(0).getTextContent();
				this.description = coreAttributes.getElementsByTagName("description").item(0).getTextContent();

				try {
					enchantmentLimit = Integer.valueOf(coreAttributes.getElementsByTagName("enchantmentLimit").item(0).getTextContent());
				} catch(Exception ex) {
					System.err.println("AbstractTattooType loading failed. Cause: 'enchantmentLimit' element unable to be parsed. (" + tattooXMLFile.getName() + ")\n" + ex);
				}

				List<Colour> importedPrimaryColours = new ArrayList<>();
				try {
					importedPrimaryColours = readColoursFromElement(coreAttributes, "primaryColours");
				} catch(Exception ex) {
					System.err.println("AbstractTattooType loading failed. Cause: 'primaryColours' element unable to be parsed. (" + tattooXMLFile.getName() + ")\n" + ex);
				}

				List<Colour> importedSecondaryColours = new ArrayList<>();
				try {
					importedSecondaryColours = readColoursFromElement(coreAttributes, "secondaryColours");
				} catch(Exception ex) {
					System.err.println("AbstractTattooType loading failed. Cause: 'secondaryColours' element unable to be parsed. (" + tattooXMLFile.getName() + ")\n" + ex);
				}

				List<Colour> importedTertiaryColours = new ArrayList<>();
				try {
					importedTertiaryColours = readColoursFromElement(coreAttributes, "tertiaryColours");
				} catch(Exception ex) {
					System.err.println("AbstractTattooType loading failed. Cause: 'tertiaryColours' element unable to be parsed. (" + tattooXMLFile.getName() + ")\n" + ex);
				}

				this.availablePrimaryColours = new ArrayList<>(importedPrimaryColours);

				this.availableSecondaryColours = new ArrayList<>(importedSecondaryColours);

				this.availableTertiaryColours = new ArrayList<>(importedTertiaryColours);

				SVGStringMap = new HashMap<>();

			} catch(Exception ex) {
				System.err.println("TattooType was unable to be loaded from file! (" + tattooXMLFile.getName() + ")\n" + ex);
			}
		}
	}

	private List<Colour> readColoursFromElement(Element coreAttributes, String elementTagName) {
		Element coloursElement = ((Element)coreAttributes.getElementsByTagName(elementTagName).item(0));
		if(coloursElement.getAttribute("values").isEmpty()) {
			NodeList coloursNodeList = coloursElement.getElementsByTagName("colour");
			List<Colour> result = new ArrayList<>(coloursNodeList.getLength());
			for(int i = 0; i < coloursNodeList.getLength(); i++){
				result.add(Colour.valueOf(((Element)coloursNodeList.item(i)).getTextContent()));
			}
			return result;
		}
		return ColourListPresets.getColourListFromId(coloursElement.getAttribute("values"));
	}

	@Override
	public boolean equals(Object o) {
		if(super.equals(o)) {
			return (o instanceof AbstractTattooType)
					&& ((AbstractTattooType)o).isMod()==isMod
					&& ((AbstractTattooType)o).getSlotAvailability().equals(this.getSlotAvailability())
					&& ((AbstractTattooType)o).getName().equals(this.getName())
					&& ((AbstractTattooType)o).getDescription().equals(this.getDescription())
					&& ((AbstractTattooType)o).getAvailablePrimaryColours().equals(this.getAvailablePrimaryColours())
					&& ((AbstractTattooType)o).getAvailableSecondaryColours().equals(this.getAvailableSecondaryColours())
					&& ((AbstractTattooType)o).getAvailableTertiaryColours().equals(this.getAvailableTertiaryColours())
					&& ((AbstractTattooType)o).getPathName().equals(this.getPathName());
		} else {
			return false;
		}
	}

	@Override
	public int hashCode() {
		int result = super.hashCode();
		result = 31 * result + (this.isMod() ? 1 : 0);

		result = 31 * result + getSlotAvailability().hashCode();
		result = 31 * result + getName().hashCode();
		result = 31 * result + getDescription().hashCode();

		result = 31 * result + getAvailablePrimaryColours().hashCode();
		result = 31 * result + getAvailableSecondaryColours().hashCode();
		result = 31 * result + getAvailableTertiaryColours().hashCode();

		result = 31 * result + getPathName().hashCode();

		return result;
	}

	public boolean isMod() {
		return isMod;
	}

	public int getValue() {
		return value;
	}

	public String getName() {
		return name;
	}

	public String getDescription() {
		return description;
	}

	public List<Colour> getAvailablePrimaryColours() {
		return availablePrimaryColours;
	}

	public List<Colour> getAvailableSecondaryColours() {
		return availableSecondaryColours;
	}

	public List<Colour> getAvailableTertiaryColours() {
		return availableTertiaryColours;
	}

	public List<InventorySlot> getSlotAvailability() {
		return slotAvailability;
	}

	public String getId() {
		return TattooType.getIdFromTattooType(this);
	}

	public int getEnchantmentLimit() {
		return 100;
	}

	public AbstractItemEffectType getEnchantmentEffect() {
		return ItemEffectType.TATTOO;
	}

	private void addSVGStringMapping(Colour colour, Colour colourSecondary, Colour colourTertiary, String s) {
		if(SVGStringMap.get(colour)==null) {
			SVGStringMap.put(colour, new HashMap<>());
			SVGStringMap.get(colour).put(colourSecondary, new HashMap<>());

		} else if(SVGStringMap.get(colour).get(colourSecondary)==null) {
			SVGStringMap.get(colour).put(colourSecondary, new HashMap<>());
		}

		SVGStringMap.get(colour).get(colourSecondary).put(colourTertiary, s);
	}

	private String getSVGStringFromMap(Colour colour, Colour colourSecondary, Colour colourTertiary) {
		if(SVGStringMap.get(colour)==null) {
			return null;
		} else {
			if(SVGStringMap.get(colour).get(colourSecondary)==null) {
				return null;
			} else {
				return SVGStringMap.get(colour).get(colourSecondary).get(colourTertiary);
			}
		}
	}

	public String getSVGImage(GameCharacter character, Colour colour, Colour colourSecondary, Colour colourTertiary) {
		if (!availablePrimaryColours.contains(colour) || pathName==null || pathName.isEmpty()) {
			return "";
		}

		String stringFromMap = getSVGStringFromMap(colour, colourSecondary, colourTertiary);
		if (stringFromMap!=null) {
			return stringFromMap;

		} else {
			if (availablePrimaryColours.contains(colour)) {
				try {
					InputStream is;
					String s;
					if(isMod) {
						List<String> lines = Files.readAllLines(Paths.get(pathName));
						StringBuilder sb = new StringBuilder();
						for(String line : lines) {
							sb.append(line);
						}
						s = sb.toString();

					} else {
						is = this.getClass().getResourceAsStream("/com/lilithsthrone/res/tattoos/" + pathName + ".svg");
						s = Util.inputStreamToString(is);
						is.close();
					}

					s = SvgUtil.colourReplacement(this.getId(), colour, colourSecondary, colourTertiary, s);

					addSVGStringMapping(colour, colourSecondary, colourTertiary, s);

					return s;
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}

		return "";
	}

	public String getPathName() {
		return pathName;
	}
}


code:
package com.lilithsthrone.game.character.markings;

import org.w3c.dom.Document;
import org.w3c.dom.Element;

import com.lilithsthrone.game.character.CharacterUtils;
import com.lilithsthrone.utils.XMLSaving;

/**
 * @since 0.2.6
 * @version 0.2.6
 * @author Innoxia
 */
public class Scar implements XMLSaving {

	private ScarType type;
	private boolean plural;

	public Scar(ScarType type, boolean plural) {
		this.type = type;
		this.plural = plural;
	}

	@Override
	public boolean equals(Object o) {
		if(super.equals(o)) {
			return (o instanceof Scar)
					&& ((Scar)o).getType()==type
					&& ((Scar)o).isPlural()==plural;
		} else {
			return false;
		}
	}

	@Override
	public int hashCode() {
		int result = 17;
		result = 31 * result + type.hashCode();
		result = 31 * result + (plural ? 1 : 0);
		return result;
	}

	public Element saveAsXML(Element parentElement, Document doc) {
		Element element = doc.createElement("scar");
		parentElement.appendChild(element);

		CharacterUtils.addAttribute(doc, element, "type", this.getType().toString());
		CharacterUtils.addAttribute(doc, element, "plural", String.valueOf(this.isPlural()));

		return element;
	}

	public static Scar loadFromXML(Element parentElement, Document doc) {
		try {
			return new Scar(ScarType.valueOf(parentElement.getAttribute("type")), Boolean.valueOf(parentElement.getAttribute("plural")));

		} catch(Exception ex) {
			System.err.println("Warning: An instance of Scar was unable to be imported!");
			return null;
		}
	}

	public String getName() {
		if(isPlural()) {
			return getType().getNamePlural();
		} else {
			return getType().getName();
		}
	}

	public ScarType getType() {
		return type;
	}

	public boolean isPlural() {
		return plural;
	}

}


code:
package com.lilithsthrone.game.character.markings;

/**
 * @since 0.2.6
 * @version 0.2.6
 * @author Innoxia
 */
public enum ScarType {

	CLAW_MARKS("claw mark", "claw marks", true),

	BURNS("burn mark", "burn marks", false),

	STRAIGHT_SCAR("straight scar", "straight scars", false),

	JAGGED_SCAR("jagged scar", "jagged scars", false);

	private String name;
	private String namePlural;
	private boolean alwaysPlural;

	private ScarType(String name, String namePlural, boolean alwaysPlural) {
		this.name = name;
		this.namePlural = namePlural;
		this.alwaysPlural = alwaysPlural;
	}

	public String getName() {
		return name;
	}

	public String getNamePlural() {
		return namePlural;
	}

	public boolean isAlwaysPlural() {
		return alwaysPlural;
	}

}