code:
package com.lilithsthrone.game.inventory;

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

import com.lilithsthrone.game.character.GameCharacter;
import com.lilithsthrone.game.character.body.BodyPartInterface;
import com.lilithsthrone.game.dialogue.utils.UtilText;
import com.lilithsthrone.game.inventory.clothing.BodyPartClothingBlock;
import com.lilithsthrone.utils.Util;

/**
 * @since 0.1.0
 * @version 0.3.2
 * @author Innoxia
 */
public enum InventorySlot {

	// HEAD:

	/** Clothing slot "head". Used for headgear.

	 *  Tattoo slot "head".*/
	HEAD(40, "head", false, false, false, "head") {
		@Override
		protected String getNameForParsing() {
			return "head";
		}
	},

	/** Clothing slot "eyes". Used for glasses.

	 *  Tattoo slot "upper face".*/
	EYES(50, "eyes", true, false, false, "upper face") {
		@Override
		protected String getNameForParsing() {
			return "[npc.eyes]";
		}
	},

	/** Clothing slot "hair". Used for ribbons and hairbands.

	 *  Tattoo slot "ears".*/
	HAIR(20, "hair", false, false, false, "ears") {
		@Override
		public boolean isPhysicallyAvailable(GameCharacter character) {
			return character.hasHair();
		}

		@Override
		protected String getNameForParsing() {
			return "[npc.hair]";
		}
	},

	/** Clothing slot "mouth". Used for ballgags.

	 *  Tattoo slot "lower face".*/
	MOUTH(10, "mouth", false, false, false, "lower face") {
		@Override
		protected String getNameForParsing() {
			return "[npc.mouth]";
		}
	},

	/** Clothing slot "neck". Used for necklaces and scarfs.

	 *  Tattoo slot "neck".*/
	NECK(30, "neck", false, false, false, "neck") {
		@Override
		protected String getNameForParsing() {
			return "neck";
		}
	},

	// TORSO:

	/** Clothing slot "over-torso". Used for coats.

	 *  Tattoo slot "upper back".*/
	TORSO_OVER(50, "over-torso", false, false, false, "upper back") {
		@Override
		protected String getNameForParsing() {
			return "over-torso";
		}
	},

	/** Clothing slot "torso". Used for shirts.

	 *  Tattoo slot "lower back".*/
	TORSO_UNDER(40, "torso", false, true, false, "lower back") {
		@Override
		protected String getNameForParsing() {
			return "torso";
		}
	},

	/** Clothing slot "chest". Used for bras.

	 *  Tattoo slot "chest".*/
	CHEST(10, "chest", false, true, false, "chest") {
		@Override
		protected String getNameForParsing() {
			return "[npc.breasts]";
		}
	},

	/** Clothing slot "nipples". Used for nipple shields, plugs.

	 *  Tattoo slot "nipples".*/
	NIPPLE(5, "nipples", false, false, false, "nipples") {
		@Override
		protected String getNameForParsing() {
			return "[npc.nipples]";
		}
	},

	/** Clothing slot "stomach". Used for corsets.

	 *  Tattoo slot "stomach".*/
	STOMACH(10, "stomach", false, false, false, "stomach") {
		@Override
		protected String getNameForParsing() {
			return "stomach";
		}
	},

	// HAND:

	/** Clothing slot "hands". Used for gloves.

	 *  Tattoo slot "forearms".*/
	HAND(20, "hands", true, false, false, "forearms") {
		@Override
		protected String getNameForParsing() {
			return "[npc.hands]";
		}
	},

	/** Clothing slot "wrists". Used for bracelets.

	 *  Tattoo slot "upper arms".*/
	WRIST(30, "wrists", true, false, false, "upper arms") {
		@Override
		protected String getNameForParsing() {
			return "wrists";
		}
	},

	/** Clothing slot "fingers". Used for rings.

	 *  Tattoo slot "hands".*/
	FINGER(30, "fingers", true, false, false, "hands") {
		@Override
		protected String getNameForParsing() {
			return "[npc.fingers]";
		}
	},

	/** Clothing slot "hips". Used for belts.

	 *  Tattoo slot "hips".*/
	HIPS(40, "hips", true, false, false, "hips") {
		@Override
		protected String getNameForParsing() {
			return "[npc.hips]";
		}
	},

//	/** Clothing slot "hips-under". Used for suspender belts.

//	 *  Tattoo slot "".*/
//	HIPS_UNDER(20, "hips-under", false, false), // suspender belts

	/** Clothing slot "anus". Used for plugs.

	 *  Tattoo slot "ass".*/
	ANUS(0, "anus", false, false, false, "ass") {
		@Override
		protected String getNameForParsing() {
			return "[npc.asshole]";
		}
	},

	// LEG & FOOT:

	/** Clothing slot "legs". Used for trousers.

	 *  Tattoo slot "upper leg".*/
	LEG(30, "legs", true, true, false, "upper leg") {
		@Override
		protected String getNameForParsing() {
			return "[npc.legs]";
		}
	},

	/** Clothing slot "groin". Used for underwear.

	 *  Tattoo slot "lower abdomen".*/
	GROIN(10, "groin", false, true, false, "lower abdomen") {
		@Override
		protected String getNameForParsing() {
			return "groin";
		}
	},

	/** Clothing slot "feet". Used for shoes.

	 *  Tattoo slot "feet".*/
	FOOT(40, "feet", true, true, false, "feet") {
		@Override
		protected String getNameForParsing() {
			return "[npc.feet]";
		}
	},

	/** Clothing slot "calves". Used for socks.

	 *  Tattoo slot "lower leg".*/
	SOCK(10, "calves", true, true, false, "lower leg") {
		@Override
		protected String getNameForParsing() {
			return "calves";
		}
	},

	/** Clothing slot "ankles". Used for bracelets.

	 *  Tattoo slot "ankles".*/
	ANKLE(50, "ankles", true, false, false, "ankles") {
		@Override
		protected String getNameForParsing() {
			return "ankles";
		}
	},

	// OPTIONAL EXTRAS:

	/** Clothing slot "horns". Used for horn decorations.

	 *  Tattoo slot "horns".*/
	HORNS(50, "horns", true, false, false, "horns") {
		@Override
		public boolean isPhysicallyAvailable(GameCharacter character) {
			return character.hasHorns();
		}

		@Override
		protected String getNameForParsing() {
			return "[npc.horns]";
		}
	},

	/** Clothing slot "wings". Used for wing decorations.

	 *  Tattoo slot "wings".*/
	WINGS(50, "wings", true, false, false, "wings") {
		@Override
		public boolean isPhysicallyAvailable(GameCharacter character) {
			return character.hasWings();
		}

		@Override
		protected String getNameForParsing() {
			return "[npc.wings]";
		}
	},

	/** Clothing slot "tail". Used for tail decorations.

	 *  Tattoo slot "tail".*/
	TAIL(50, "tail", false, false, false, "tail") {
		@Override
		public boolean isPhysicallyAvailable(GameCharacter character) {
			return character.hasTail();
		}

		@Override
		protected String getNameForParsing() {
			return "[npc.tail]";
		}
	},

	/** Clothing slot "penis". Used for cock socks, cages, and plugs.

	 *  Tattoo slot "penis".*/
	PENIS(0, "penis", false, false, false, "penis") {
		@Override
		public boolean isPhysicallyAvailable(GameCharacter character) {
			return character.hasPenis();
		}

		@Override
		protected String getNameForParsing() {
			return "[npc.cock]";
		}
	},

	/** Clothing slot "vagina". Used for plugs.

	 *  Tattoo slot "vagina".*/
	VAGINA(0, "vagina", false, false, false, "vagina") {
		@Override
		public boolean isPhysicallyAvailable(GameCharacter character) {
			return character.hasVagina();
		}

		@Override
		protected String getNameForParsing() {
			return "[npc.pussy]";
		}
	},

	// PIERCING:
	PIERCING_EAR(0, "ear piercing", false, false, true, null) {
		@Override
		protected String getNameForParsing() {
			return "[npc.ears]";
		}
	},
	PIERCING_NOSE(0, "nose piercing", false, false, true, null) {
		@Override
		protected String getNameForParsing() {
			return "[npc.nose]";
		}
	},
	PIERCING_TONGUE(0, "tongue piercing", false, false, true, null) {
		@Override
		protected String getNameForParsing() {
			return "[npc.tongue]";
		}
	},
	PIERCING_LIP(0, "lip piercing", false, false, true, null) {
		@Override
		protected String getNameForParsing() {
			return "[npc.lips]";
		}
	},
	PIERCING_STOMACH(0, "navel piercing", false, false, true, null) {
		@Override
		protected String getNameForParsing() {
			return "navel";
		}
	},
	PIERCING_NIPPLE(0, "nipple piercing", false, false, true, null) {
		@Override
		protected String getNameForParsing() {
			return "[npc.nipples]";
		}
	},
	PIERCING_VAGINA(0, "vaginal piercing", false, false, true, null) {
		@Override
		public boolean isPhysicallyAvailable(GameCharacter character) {
			return character.hasVagina();
		}

		@Override
		protected String getNameForParsing() {
			return "[npc.pussy]";
		}
	},
	PIERCING_PENIS(0, "cock piercing", false, false, true, null) {
		@Override
		public boolean isPhysicallyAvailable(GameCharacter character) {
			return character.hasPenis();
		}

		@Override
		protected String getNameForParsing() {
			return "[npc.cock]";
		}
	},

	// EQUIPPABLE:
	WEAPON_MAIN_1(0, "primary weapon", false, false, false, null) {
		@Override
		protected String getNameForParsing() {
			return null;
		}
		@Override
		public boolean isWeapon() {
			return true;
		}
	},
	WEAPON_MAIN_2(0, "primary weapon (2nd)", false, false, false, null) {
		@Override
		protected String getNameForParsing() {
			return null;
		}
		@Override
		public boolean isWeapon() {
			return true;
		}
	},
	WEAPON_MAIN_3(0, "primary weapon (3rd)", false, false, false, null) {
		@Override
		protected String getNameForParsing() {
			return null;
		}
		@Override
		public boolean isWeapon() {
			return true;
		}
	},

	WEAPON_OFFHAND_1(0, "secondary weapon", false, false, false, null) {
		@Override
		protected String getNameForParsing() {
			return null;
		}
		@Override
		public boolean isWeapon() {
			return true;
		}
	},
	WEAPON_OFFHAND_2(0, "secondary weapon (2nd)", false, false, false, null) {
		@Override
		protected String getNameForParsing() {
			return null;
		}
		@Override
		public boolean isWeapon() {
			return true;
		}
	},
	WEAPON_OFFHAND_3(0, "secondary weapon (3rd)", false, false, false, null) {
		@Override
		protected String getNameForParsing() {
			return null;
		}
		@Override
		public boolean isWeapon() {
			return true;
		}
	};

	private int zLayer;
	private String name;
	private boolean plural;
	private String tattooSlotName;
	private boolean jewellery;
	private boolean coreClothing;

	private static List<InventorySlot> humanoidSlots;
	private static List<InventorySlot> clothingSlots;
	private static List<InventorySlot> mainClothingSlots;
	private static List<InventorySlot> extraClothingSlots;
	private static List<InventorySlot> piercingSlots;

	static {
		humanoidSlots = new ArrayList<>();
		clothingSlots = new ArrayList<>();
		piercingSlots = new ArrayList<>();
		extraClothingSlots = new ArrayList<>();

		clothingSlots.add(HEAD);
		clothingSlots.add(EYES);
		clothingSlots.add(HAIR);
		clothingSlots.add(MOUTH);
		clothingSlots.add(NECK);

		clothingSlots.add(TORSO_OVER);
		clothingSlots.add(TORSO_UNDER);
		clothingSlots.add(CHEST);
		clothingSlots.add(NIPPLE);
		clothingSlots.add(STOMACH);

		clothingSlots.add(HAND);
		clothingSlots.add(WRIST);
		clothingSlots.add(FINGER);
		clothingSlots.add(HIPS);
		clothingSlots.add(ANUS);

		clothingSlots.add(LEG);
		clothingSlots.add(GROIN);
		clothingSlots.add(FOOT);
		clothingSlots.add(SOCK);
		clothingSlots.add(ANKLE);

		clothingSlots.add(HORNS);
		clothingSlots.add(TAIL);
		clothingSlots.add(WINGS);
		clothingSlots.add(PENIS);
		clothingSlots.add(VAGINA);

		extraClothingSlots.add(HORNS);
		extraClothingSlots.add(TAIL);
		extraClothingSlots.add(WINGS);
		extraClothingSlots.add(PENIS);
		extraClothingSlots.add(VAGINA);

		mainClothingSlots = new ArrayList<>(clothingSlots);
		mainClothingSlots.removeAll(extraClothingSlots);

		piercingSlots.add(PIERCING_EAR);
		piercingSlots.add(PIERCING_NOSE);
		piercingSlots.add(PIERCING_TONGUE);
		piercingSlots.add(PIERCING_LIP);
		piercingSlots.add(PIERCING_STOMACH);
		piercingSlots.add(PIERCING_NIPPLE);
		piercingSlots.add(PIERCING_VAGINA);
		piercingSlots.add(PIERCING_PENIS);

		humanoidSlots.add(HEAD);
		humanoidSlots.add(EYES);
		humanoidSlots.add(HAIR);
		humanoidSlots.add(MOUTH);
		humanoidSlots.add(NECK);

		humanoidSlots.add(TORSO_OVER);
		humanoidSlots.add(TORSO_UNDER);
		humanoidSlots.add(CHEST);
		humanoidSlots.add(NIPPLE);
		humanoidSlots.add(STOMACH);

		humanoidSlots.add(HAND);
		humanoidSlots.add(WRIST);
		humanoidSlots.add(FINGER);
		humanoidSlots.add(HIPS);
//		humanoidSlots.add(ANUS);

//		humanoidSlots.add(LEG);
//		humanoidSlots.add(GROIN);
//		humanoidSlots.add(FOOT);
//		humanoidSlots.add(SOCK);
//		humanoidSlots.add(ANKLE);

		humanoidSlots.add(HORNS);
		humanoidSlots.add(TAIL);
//		humanoidSlots.add(WINGS); //TODO special case, as can be either
		humanoidSlots.add(PENIS);
		humanoidSlots.add(VAGINA);

		humanoidSlots.add(PIERCING_EAR);
		humanoidSlots.add(PIERCING_NOSE);
		humanoidSlots.add(PIERCING_TONGUE);
		humanoidSlots.add(PIERCING_LIP);
		humanoidSlots.add(PIERCING_STOMACH);
		humanoidSlots.add(PIERCING_NIPPLE);
//		humanoidSlots.add(PIERCING_VAGINA);
//		humanoidSlots.add(PIERCING_PENIS);
	}

	private InventorySlot(int zLayer, String name, boolean plural, boolean coreClothing, boolean jewellery, String tattooSlotName) {
		this.zLayer = zLayer;
		this.name = name;
		this.plural = plural;
		this.coreClothing = coreClothing;
		this.jewellery = jewellery;
		this.tattooSlotName = tattooSlotName;
	}

	public int getZLayer() {
		return zLayer;
	}

	public String getName() {
		return name;
	}

	public String getNameOfAssociatedPart(GameCharacter owner) {
		return UtilText.parse(owner, getNameForParsing());
	}

	protected abstract String getNameForParsing();

	public boolean isPlural() {
		return plural;
	}

	public String getTattooSlotName() {
		return tattooSlotName;
	}

	public boolean isCoreClothing() {
		return coreClothing;
	}

	public boolean isJewellery() {
		return jewellery;
	}

	public boolean isWeapon() {
		return false;
	}

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

	public static List<InventorySlot> getHumanoidSlots() {
		return humanoidSlots;
	}

	public static List<InventorySlot> getClothingSlots() {
		return clothingSlots;
	}

	public static List<InventorySlot> getPiercingSlots() {
		return piercingSlots;
	}

	public static List<InventorySlot> getMainClothingSlots() {
		return mainClothingSlots;
	}

	public static List<InventorySlot> getExtraClothingSlots() {
		return extraClothingSlots;
	}

	/**
	 * Returns the first applicable BodyPartClothingBlock found from the supplied character's body parts.
	 *
	 * @param character The character to check.
	 * @return A BodyPartClothingBlock object which represents how this part is being blocked. Returns null if nothing is blocking the slot.
	 */
	public BodyPartClothingBlock getBodyPartClothingBlock(GameCharacter character) {
		if (character == null) {
			return null;
		}
		if(character.getHairRawLengthValue()==0 && this == InventorySlot.HAIR) {
			return new BodyPartClothingBlock(
					Util.newArrayListOfValues(InventorySlot.HAIR),
					character.getHairRace(),
					UtilText.parse(character, "[npc.Name] [npc.do]n't have any hair, so [npc.she] can't wear any hair accessories!"),
					Util.newArrayListOfValues());
		}
		// leg configuration:
		BodyPartClothingBlock block = character.getLegConfiguration().getBodyPartClothingBlock(character);
		if(block!=null) {
			if(block.getBlockedSlots().contains(this)) {
				return block;
			}
		}

		for(BodyPartInterface bodypart : character.getBody().getAllBodyParts()) {
			block = bodypart.getType().getBodyPartClothingBlock();
			if(block!=null) {
				if(block.getBlockedSlots().contains(this)) {
					return block;
				}
			}
		}
		return null;
	}
}

repo_name: klorpa/Pedo

license: unknown


code:
package com.lilithsthrone.game.inventory;

import java.util.List;

import com.lilithsthrone.main.Main;
import com.lilithsthrone.utils.Util;

/**
 * @since 0.2.1
 * @version 0.3.4
 * @author Innoxia
 */
public enum ItemTag {

	HIDDEN_IN_DEBUG_SPAWNER(false),

	REMOVE_FROM_DEBUG_SPAWNER(false),

	NOT_FOR_SALE(false),

	REINDEER_GIFT(false), // Can be found in the presents that the reindeer sell (who appear in Dominion during winter months).
	SOLD_BY_RALPH(false), // Will also be used for any future consumable and miscellaneous item vendors.
	SOLD_BY_NYAN(false), // Clothing
	SOLD_BY_KATE(false), // Jewellery
	SOLD_BY_FINCH(false), // BDSM and sex-related stuff
	SOLD_BY_VICKY(false), // Weapons

	SPELL_BOOK(false),
	SPELL_SCROLL(false),
	ESSENCE(false),
	ATTRIBUTE_TF_ITEM(false),
	RACIAL_TF_ITEM(false),
	MISC_TF_ITEM(false), // Fetish or non-racial body part transformations
	BOOK(false),
	GIFT(false),
	DOMINION_ALLEYWAY_SPAWN(false),
	SUBMISSION_TUNNEL_SPAWN(false),
	BAT_CAVERNS_SPAWN(false),

	//-------------- WEAPONS & CLOTHING --------------//

	REVEALS_CONCEALABLE_SLOT(false), // If a piece of clothing has this tag, it will always be visible, even if another item of clothing is concealing its slot. (Used for spreader bar.)

	TRANSPARENT( // This item of clothing does not conceal any areas. Used for chastity cages & condoms (so penis is still visible). Could also be used for sheer clothing material.
			Util.newArrayListOfValues(
					"[style.colourSex(Cannot conceal any body parts)]"),
			false),

	WEAPON_BLADE(false), // Should be added to all weapons that use an arcane blade

	DRESS(false), // For helping to generate clothing in CharacterUtils

	SPREADS_FEET( // Prevents double foot actions, like wrap-around footjobs
			Util.newArrayListOfValues(
					"[style.colourBad(Restricts sex actions)]"),
			false),

	MUFFLES_SPEECH( // Causes the wearer to not be able to talk. E.g. Ball gags
			Util.newArrayListOfValues(
					"[style.colourBad(Muffles speech)]"),
			false),

	HINDERS_ARM_MOVEMENT( // Hinders the ability of the wearer to use their arms. E.g. Hand cuffs
			Util.newArrayListOfValues(
					"[style.colourBad(Hinders arm movement)]",
					"[style.colourTerrible(Blocks arm-wing flight)]"),
			false),

	HINDERS_LEG_MOVEMENT(  // Hinders the ability of the wearer to run or use their legs properly. E.g. Spreader bar
			Util.newArrayListOfValues(
					"[style.colourBad(Hinders leg movement)]",
					"[style.colourTerrible(Blocks non-flight combat escape)]"),
			false),

	DISCARDED_WHEN_UNEQUIPPED( //  Makes the clothing be thrown away when unequipped. E.g. Condoms
			Util.newArrayListOfValues(
					"[style.colourMinorBad(Discarded when unequipped)]"),
			false),

	ENABLE_SEX_EQUIP( // Allows this clothing to be equipped during sex. E.g. Condoms or strapons
			Util.newArrayListOfValues(
					"[style.colourSex(Sex-equip enabled)]"),
			false),

	// Self-explanatory requirements in order to equip this clothing:
	REQUIRES_PENIS(
			Util.newArrayListOfValues(
					"[style.colourSex(Requires penis)]"),
			false),
	REQUIRES_NO_PENIS(
			Util.newArrayListOfValues(
					"[style.colourSex(Requires no penis)]"),
			false),
	REQUIRES_VAGINA(
			Util.newArrayListOfValues(
					"[style.colourSex(Requires vagina)]"),
			false),
	REQUIRES_NO_VAGINA(
			Util.newArrayListOfValues(
					"[style.colourSex(Requires no vagina)]"),
			false),
	REQUIRES_FUCKABLE_NIPPLES(
			Util.newArrayListOfValues(
					"[style.colourSex(Requires fuckable nipples)]"),
			false),

	// These 'FITS' tags are used to check for whether clothing is suitable for certain body parts. They should be pretty self-explanatory.
	FITS_HOOFS_EXCLUSIVE(
			Util.newArrayListOfValues(
					"[style.colourBestial(Only fits hoofs)]"),
			false),
	FITS_HOOFS(
			Util.newArrayListOfValues(
					"[style.colourBestial(Fits hoofs)]"),
			false),

	FITS_TALONS_EXCLUSIVE(
			Util.newArrayListOfValues(
					"[style.colourBestial(Only fits talons)]"),
			false),
	FITS_TALONS(
			Util.newArrayListOfValues(
					"[style.colourBestial(Fits talons)]"),
			false),

	FITS_HARPY_WINGS_EXCLUSIVE(
			Util.newArrayListOfValues(
					"[style.colourBestial(Only fits arm-wings)]"),
			false),
	FITS_HARPY_WINGS(
			Util.newArrayListOfValues(
					"[style.colourBestial(Fits arm-wings)]"),
			false),

	FITS_NON_BIPED_BODY_HUMANOID(
			Util.newArrayListOfValues(
					"[style.colourHuman(Fits humanoid parts of non-biped bodies)]"),
			false),
	FITS_TAUR_BODY(
			Util.newArrayListOfValues(
					"[style.colourBestial(Only fits taur bodies)]"),
			false),
	FITS_LONG_TAIL_BODY(
			Util.newArrayListOfValues(
					"[style.colourBestial(Only fits long-tailed bodies)]"),false), //lamia, eels
	FITS_TAIL_BODY(
			Util.newArrayListOfValues(
					"[style.colourBestial(Only fits tailed bodies)]"),false), //mermaids
	FITS_ARACHNID_BODY(
			Util.newArrayListOfValues(
					"[style.colourBestial(Only fits arachnid bodies)]"),false), //spiders and scorpions
	FITS_CEPHALOPOD_BODY(
			Util.newArrayListOfValues(
					"[style.colourBestial(Only fits cephalopod bodies)]"),false), //octopuses and squids

	RIGID_MATERIAL( // The clothing is made out of a rigid material, and as such, groping actions cannot be performed on it. Used for chastity cages/belts.
			Util.newArrayListOfValues(
					"[style.colourTerrible(Blocks groping actions)]"),
			false),

	PREVENTS_ERECTION_PHYSICAL( // Prevents the wearer from getting an erection during sex, by means of physically limiting the space into which the erection could take shape (i.e. chastity cages). As of 0.3.1, only affects descriptors.
			Util.newArrayListOfValues(
					"[style.colourTerrible(Prevents erection)]"),
			false),
	PREVENTS_ERECTION_OTHER( // Prevents the wearer from getting an erection during sex, by means other than physical limitations. As of 0.3.1, only affects descriptors.
			Util.newArrayListOfValues(
					"[style.colourTerrible(Prevents erection)]"),
			false),

	// Sex-related clothing:

	/**<b>IMPORTANT</b> This tag should only ever be given to clothing going into the PENIS InventorySlot, as otherwise it will throw errors.*/
	CONDOM(true), // Gives this clothing condom behaviour

	// To detect whether creampies should leak out or not:

	PLUGS_ANUS( // Counts as being inserted into the wearer's anus. E.g. butt plugs or anal beads
			Util.newArrayListOfValues(
					"[style.colourSex(Plugs asshole (does not get dirty from creampies))]"),
			true),
	SEALS_ANUS( // Counts as sealing(false), but not inserted into(false), the wearer's anus. E.g. Tape
			Util.newArrayListOfValues(
					"[style.colourSex(Seals asshole (does not get dirty from creampies))]"),
			true),

	PLUGS_VAGINA( // Counts as being inserted into the wearer's vagina. E.g. insertable dildo
			Util.newArrayListOfValues(
					"[style.colourSex(Plugs pussy (does not get dirty from creampies))]"),
			true),
	SEALS_VAGINA( // Counts as sealing, but not inserted into, the wearer's vagina. E.g. Tape
			Util.newArrayListOfValues(
					"[style.colourSex(Seals pussy (does not get dirty from creampies))]"),
			true) {
		@Override
		public List<String> getClothingTooltipAdditions() {
			if(Main.game.isUrethraEnabled()) {
				return Util.newArrayListOfValues("[style.colourSex(Seals pussy (including urethra))]");
			} else {
				return Util.newArrayListOfValues("[style.colourSex(Seals pussy)]");
			}
		}
	},

	PLUGS_NIPPLES( // Counts as being inserted into the wearer's nipples. E.g. insertable nipple-dildos
			Util.newArrayListOfValues(
					"[style.colourSex(Plugs nipples (does not get dirty from creampies))]"),
			true),
	SEALS_NIPPLES( // Counts as sealing, but not inserted into, the wearer's nipples. E.g. Pasties
			Util.newArrayListOfValues(
					"[style.colourSex(Seals nipples (does not get dirty from creampies))]"),
			true),

	DILDO_TINY( // 3 inches
			Util.newArrayListOfValues(
					"[style.colourSex(3-inch dildo)]"),
			true),
	DILDO_AVERAGE( // 6 inches
			Util.newArrayListOfValues(
					"[style.colourSex(6-inch dildo)]"),
			true),
	DILDO_LARGE( // 10 inches
			Util.newArrayListOfValues(
					"[style.colourSex(10-inch dildo)]"),
			true),
	DILDO_HUGE( // 14 inches
			Util.newArrayListOfValues(
					"[style.colourSex(14-inch dildo)]"),
			true),
	DILDO_ENORMOUS( // 18 inches
			Util.newArrayListOfValues(
					"[style.colourSex(18-inch dildo)]"),
			true),
	DILDO_GIGANTIC( // 22 inches
			Util.newArrayListOfValues(
					"[style.colourSex(22-inch dildo)]"),
			true),
	DILDO_STALLION( // 32 inches
			Util.newArrayListOfValues(
					"[style.colourSex(32-inch dildo)]"),
			true),
	;

	private List<String> clothingTooltipAdditions;
	private boolean sexToy;

	/**
	 * @param clothingTooltipAdditions The descriptions that should be appended to clothing tooltips for when this ItemTag is present on clothing.
	 * @param sexToy If true, then this item tag marks the associating clothing as a sex toy, which, if worn by a dom, is unable to be removed by subs in sex.
	 */
	private ItemTag(List<String> clothingTooltipAdditions, boolean sexToy) {
		this.clothingTooltipAdditions = clothingTooltipAdditions;
		this.sexToy = sexToy;
	}
	/**
	 * @param sexToy If true, then this item tag marks the associating clothing as a sex toy, which, if worn by a dom, is unable to be removed by subs in sex.
	 */
	private ItemTag(boolean sexToy) {
		this.clothingTooltipAdditions = null;
		this.sexToy = sexToy;
	}

	public List<String> getClothingTooltipAdditions() {
		return clothingTooltipAdditions;
	}

	public boolean isSexToy() {
		return sexToy;
	}

}


code:
package com.lilithsthrone.game.inventory;

import com.lilithsthrone.utils.Colour;

/**
 * @since 0.1.0
 * @version 0.3.5.1
 * @author Innoxia
 */
public enum Rarity {

	COMMON("common", Colour.RARITY_COMMON, Colour.RARITY_COMMON_BACKGROUND),

	UNCOMMON("uncommon", Colour.RARITY_UNCOMMON, Colour.RARITY_UNCOMMON_BACKGROUND),

	RARE("rare", Colour.RARITY_RARE, Colour.RARITY_RARE_BACKGROUND),

	EPIC("epic", Colour.RARITY_EPIC, Colour.RARITY_EPIC_BACKGROUND),

	LEGENDARY("legendary", Colour.RARITY_LEGENDARY, Colour.RARITY_LEGENDARY_BACKGROUND),

	QUEST("unique", Colour.RARITY_QUEST, Colour.RARITY_QUEST_BACKGROUND),

	JINXED("jinxed", Colour.RARITY_JINXED, Colour.RARITY_JINXED_BACKGROUND);

	private String name;
	private Colour colour;
	private Colour backgroundColour;

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

	public String getName() {
		return name;
	}

	public Colour getColour() {
		return colour;
	}

	public Colour getBackgroundColour() {
		return backgroundColour;
	}
}


code:
package com.lilithsthrone.game.inventory;

/**
 * @since 0.1.65
 * @version 0.3.4.5
 * @author Innoxia
 */
public class ShopTransaction {

	private AbstractCoreItem abstractItemSold;
	private int price;
	private int count;

	public ShopTransaction(AbstractCoreItem abstractItemSold, int price, int count) {
		this.abstractItemSold = abstractItemSold;
		this.price = price;
		this.count = count;
	}

	public AbstractCoreItem getAbstractItemSold() {
		return abstractItemSold;
	}

	public int getPrice() {
		return price;
	}

	public int getCount() {
		return count;
	}

	public void incrementCount(int increment) {
		count+=increment;
	}
}


code:
package com.lilithsthrone.game.inventory.clothing;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

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

import com.lilithsthrone.game.Game;
import com.lilithsthrone.game.character.CharacterUtils;
import com.lilithsthrone.game.character.GameCharacter;
import com.lilithsthrone.game.character.attributes.Attribute;
import com.lilithsthrone.game.character.body.CoverableArea;
import com.lilithsthrone.game.character.body.types.PenisType;
import com.lilithsthrone.game.character.body.types.VaginaType;
import com.lilithsthrone.game.dialogue.utils.UtilText;
import com.lilithsthrone.game.inventory.AbstractCoreItem;
import com.lilithsthrone.game.inventory.AbstractCoreType;
import com.lilithsthrone.game.inventory.InventorySlot;
import com.lilithsthrone.game.inventory.ItemTag;
import com.lilithsthrone.game.inventory.Rarity;
import com.lilithsthrone.game.inventory.enchanting.AbstractItemEffectType;
import com.lilithsthrone.game.inventory.enchanting.ItemEffect;
import com.lilithsthrone.game.inventory.enchanting.ItemEffectType;
import com.lilithsthrone.game.inventory.enchanting.TFEssence;
import com.lilithsthrone.game.inventory.enchanting.TFModifier;
import com.lilithsthrone.game.inventory.enchanting.TFPotency;
import com.lilithsthrone.main.Main;
import com.lilithsthrone.rendering.Pattern;
import com.lilithsthrone.utils.Colour;
import com.lilithsthrone.utils.Util;
import com.lilithsthrone.utils.XMLSaving;

/**
 * @since 0.1.0
 * @version 0.3.4
 * @author Innoxia
 */
public abstract class AbstractClothing extends AbstractCoreItem implements XMLSaving {

	private InventorySlot slotEquippedTo;

	private AbstractClothingType clothingType;
	protected List<ItemEffect> effects;

	private Colour secondaryColour;
	private Colour tertiaryColour;
	private boolean dirty;
	private boolean enchantmentKnown;
	private List<DisplacementType> displacedList;

	private String pattern; // name of the pattern.
	private Colour patternColour;
	private Colour patternSecondaryColour;
	private Colour patternTertiaryColour;

	public AbstractClothing(AbstractClothingType clothingType, Colour colour, Colour secondaryColour, Colour tertiaryColour, boolean allowRandomEnchantment) {
		super(clothingType.getName(),
				clothingType.getNamePlural(),
				clothingType.getPathName(),
				clothingType.getAllAvailablePrimaryColours().contains(colour) ? colour : Util.randomItemFrom(clothingType.getAllAvailablePrimaryColours()),
				clothingType.getRarity(),
				null);

		this.slotEquippedTo = null;

		this.clothingType = clothingType;
		if(clothingType.getEffects()==null) {
			this.effects = new ArrayList<>();
		} else {
			this.effects = new ArrayList<>(clothingType.getEffects());
		}

		dirty = false;
		enchantmentKnown = true;

		this.secondaryColour = secondaryColour;
		this.tertiaryColour = tertiaryColour;

		handlePatternCreation();

		displacedList = new ArrayList<>();

		if (effects.isEmpty() && allowRandomEnchantment && getClothingType().getRarity() == Rarity.COMMON) {
			int chance = Util.random.nextInt(100) + 1;

			List<TFModifier> attributeMods = new ArrayList<>(TFModifier.getClothingAttributeList());

			TFModifier rndMod = attributeMods.get(Util.random.nextInt(attributeMods.size()));
			attributeMods.remove(rndMod);
			TFModifier rndMod2 = attributeMods.get(Util.random.nextInt(attributeMods.size()));

			if (chance <= 20) { // Jinxed:

				if (chance <= 1) {
					effects.add(new ItemEffect(ItemEffectType.CLOTHING, TFModifier.CLOTHING_SPECIAL, TFModifier.CLOTHING_SEALING, TFPotency.MAJOR_DRAIN, 0));
				} else if (chance <= 4) {
					effects.add(new ItemEffect(ItemEffectType.CLOTHING, TFModifier.CLOTHING_SPECIAL, TFModifier.CLOTHING_SEALING, TFPotency.DRAIN, 0));
				} else if (chance <= 10) {
					effects.add(new ItemEffect(ItemEffectType.CLOTHING, TFModifier.CLOTHING_SPECIAL, TFModifier.CLOTHING_SEALING, TFPotency.MINOR_DRAIN, 0));
				} else {
					effects.add(new ItemEffect(ItemEffectType.CLOTHING, TFModifier.CLOTHING_SPECIAL, TFModifier.CLOTHING_SEALING, TFPotency.MINOR_BOOST, 0));
				}

				effects.add(new ItemEffect(ItemEffectType.CLOTHING, TFModifier.CLOTHING_ATTRIBUTE, rndMod, TFPotency.getRandomWeightedNegativePotency(), 0));
				if(chance <10) {
					effects.add(new ItemEffect(ItemEffectType.CLOTHING, TFModifier.CLOTHING_ATTRIBUTE, rndMod2, TFPotency.getRandomWeightedNegativePotency(), 0));
				}

				enchantmentKnown = false;

			} else if (chance >= 80) { // Enchanted:
				effects.add(new ItemEffect(ItemEffectType.CLOTHING, TFModifier.CLOTHING_ATTRIBUTE, rndMod, TFPotency.getRandomWeightedPositivePotency(), 0));
				if(chance > 90) {
					effects.add(new ItemEffect(ItemEffectType.CLOTHING, TFModifier.CLOTHING_ATTRIBUTE, rndMod2, TFPotency.getRandomWeightedPositivePotency(), 0));
				}
				enchantmentKnown = false;
			}

		}
	}

	public AbstractClothing(AbstractClothingType clothingType, Colour colour, Colour secondaryColour, Colour tertiaryColour, List<ItemEffect> effects) {
		super(clothingType.getName(),
				clothingType.getNamePlural(),
				clothingType.getPathName(),
				clothingType.getAllAvailablePrimaryColours().contains(colour) ? colour : clothingType.getAllAvailablePrimaryColours().get(Util.random.nextInt(clothingType.getAllAvailablePrimaryColours().size())),
				clothingType.getRarity(),
				null);

		this.slotEquippedTo = null;

		this.clothingType = clothingType;

		dirty = false;
		enchantmentKnown = true;

		this.secondaryColour = secondaryColour;
		this.tertiaryColour = tertiaryColour;

		handlePatternCreation();

		displacedList = new ArrayList<>();
		if(effects!=null) {
			this.effects = new ArrayList<>(effects);
		} else {
			this.effects = new ArrayList<>();
		}

		enchantmentKnown = false;
	}

	public AbstractClothing(AbstractClothing clothing) {
		this(clothing.getClothingType(), clothing.getColour(), clothing.getSecondaryColour(), clothing.getTertiaryColour(), clothing.getEffects());

		this.setEnchantmentKnown(null, clothing.isEnchantmentKnown());

		this.setPattern(clothing.getPattern());
		this.setPatternColour(clothing.getPatternColour());
		this.setPatternSecondaryColour(clothing.getPatternSecondaryColour());
		this.setPatternTertiaryColour(clothing.getPatternTertiaryColour());

		this.displacedList = new ArrayList<>(clothing.getDisplacedList());

		this.dirty = clothing.isDirty();

		this.slotEquippedTo = clothing.getSlotEquippedTo();

		if(!clothing.name.isEmpty()) {
			this.setName(clothing.name);
		}
	}

	private void handlePatternCreation() {
		if(Math.random()<clothingType.getPatternChance()) {
			pattern = Util.randomItemFrom(clothingType.getDefaultPatterns()).getName();
			patternColour = Util.randomItemFrom(clothingType.getAvailablePatternPrimaryColours());

			List<Colour> secondariesExclusive = new ArrayList<>(clothingType.getAvailablePatternSecondaryColours());
			if(secondariesExclusive.size()>1) {
				secondariesExclusive.remove(patternColour);
			}
			patternSecondaryColour = Util.randomItemFrom(secondariesExclusive);

			List<Colour> tertiariesExclusive = new ArrayList<>(clothingType.getAvailablePatternTertiaryColours());
			if(secondariesExclusive.size()>2) {
				secondariesExclusive.remove(patternColour);
				secondariesExclusive.remove(patternSecondaryColour);
			}
			patternTertiaryColour = Util.randomItemFrom(tertiariesExclusive);

		} else {
			pattern = "none";
			patternColour = null;
			patternSecondaryColour = null;
			patternTertiaryColour = null;
		}
	}

	public String getId() {
		StringBuilder sb = new StringBuilder();

		sb.append(ClothingType.getIdFromClothingType(this.getClothingType()));
		sb.append(this.getColour().toString());
		sb.append(this.getSecondaryColour()!=null?this.getSecondaryColour().toString():"n");
		sb.append(this.getTertiaryColour()!=null?this.getTertiaryColour().toString():"n");
		sb.append(this.getPattern()=="none"
				?"nnn"
				:(this.getPatternColour()==null?"n":this.getPatternColour().toString()
						+ (this.getPatternSecondaryColour()==null?"n":this.getPatternSecondaryColour().toString())
						+ (this.getPatternTertiaryColour()==null?"n":this.getPatternTertiaryColour().toString())));
		sb.append(this.isSealed()?"s":"n");
		sb.append(this.isDirty()?"d":"n");
		sb.append(this.isEnchantmentKnown()?"e":"n");

		for(ItemEffect ie : this.getEffects()) {
			sb.append(ie.getId());
		}

		return sb.toString();
	}

	@Override
	public boolean equals(Object o) {
		if(super.equals(o)){
			if(o instanceof AbstractClothing){
				if(((AbstractClothing)o).getClothingType().equals(getClothingType())
						&& ((AbstractClothing)o).getSecondaryColour()==getSecondaryColour()
						&& ((AbstractClothing)o).getTertiaryColour()==getTertiaryColour()
						&& ((AbstractClothing)o).getPattern().equals(getPattern())
						&& (this.getPattern()!="none"
							?((AbstractClothing)o).getPatternColour()==this.getPatternColour()
								&& ((AbstractClothing)o).getPatternSecondaryColour()==this.getPatternSecondaryColour()
								&& ((AbstractClothing)o).getPatternTertiaryColour()==this.getPatternTertiaryColour()
							:true)
						&& ((AbstractClothing)o).isSealed()==this.isSealed()
						&& ((AbstractClothing)o).isDirty()==this.isDirty()
						&& ((AbstractClothing)o).isEnchantmentKnown()==this.isEnchantmentKnown()
						&& ((AbstractClothing)o).isBadEnchantment()==this.isBadEnchantment()
						&& ((AbstractClothing)o).getEffects().equals(this.getEffects())
						){
					return true;
				}
			}
		}
		return false;
	}

	@Override
	public int hashCode() {
		int result = super.hashCode();
		result = 31 * result + getClothingType().hashCode();
		if(getSecondaryColour()!=null) {
			result = 31 * result + getSecondaryColour().hashCode();
		}
		if(getTertiaryColour()!=null) {
			result = 31 * result + getTertiaryColour().hashCode();
		}
		result = 31 * result + getPattern().hashCode();
		if(this.getPattern()!="none") {
			if(this.getPatternColour()!=null) {
				result = 31 * result + getPatternColour().hashCode();
			}
			if(getPatternSecondaryColour()!=null) {
				result = 31 * result + getPatternSecondaryColour().hashCode();
			}
			if(getPatternTertiaryColour()!=null) {
				result = 31 * result + getPatternTertiaryColour().hashCode();
			}
		}
		result = 31 * result + (this.isSealed() ? 1 : 0);
		result = 31 * result + (this.isDirty() ? 1 : 0);
		result = 31 * result + (this.isEnchantmentKnown() ? 1 : 0);
		result = 31 * result + (this.isBadEnchantment() ? 1 : 0);
		result = 31 * result + this.getEffects().hashCode();
		return result;
	}

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

		CharacterUtils.addAttribute(doc, element, "id", this.getClothingType().getId());
		CharacterUtils.addAttribute(doc, element, "name", name);
		if(slotEquippedTo!=null) {
			CharacterUtils.addAttribute(doc, element, "slotEquippedTo", slotEquippedTo.toString());
		}
		if(this.getColour()!=AbstractClothingType.DEFAULT_COLOUR_VALUE) {
			CharacterUtils.addAttribute(doc, element, "colour", this.getColour().toString());
		}
		if(this.getSecondaryColour()!=AbstractClothingType.DEFAULT_COLOUR_VALUE) {
			CharacterUtils.addAttribute(doc, element, "colourSecondary", this.getSecondaryColour().toString());
		}
		if(this.getTertiaryColour()!=AbstractClothingType.DEFAULT_COLOUR_VALUE) {
			CharacterUtils.addAttribute(doc, element, "colourTertiary", this.getTertiaryColour().toString());
		}

		if(!this.getPattern().equals("none")) {
			if(this.getPatternColour()!=AbstractClothingType.DEFAULT_COLOUR_VALUE) {
				CharacterUtils.addAttribute(doc, element, "patternColour", this.getPatternColour().toString());
			}
			if(this.getPatternSecondaryColour()!=AbstractClothingType.DEFAULT_COLOUR_VALUE) {
				CharacterUtils.addAttribute(doc, element, "patternColourSecondary", this.getPatternSecondaryColour().toString());
			}
			if(this.getPatternTertiaryColour()!=AbstractClothingType.DEFAULT_COLOUR_VALUE) {
				CharacterUtils.addAttribute(doc, element, "patternColourTertiary", this.getPatternTertiaryColour().toString());
			}
			CharacterUtils.addAttribute(doc, element, "pattern", this.getPattern());
		}

		CharacterUtils.addAttribute(doc, element, "isDirty", String.valueOf(this.isDirty()));
		CharacterUtils.addAttribute(doc, element, "enchantmentKnown", String.valueOf(this.isEnchantmentKnown()));

		if(!this.getEffects().isEmpty()) {
			Element innerElement = doc.createElement("effects");
			element.appendChild(innerElement);

			for(ItemEffect ie : this.getEffects()) {
				ie.saveAsXML(innerElement, doc);
			}
		}

		if(!this.getDisplacedList().isEmpty()) {
			Element innerElement = doc.createElement("displacedList");
			element.appendChild(innerElement);
			for(DisplacementType dt : this.getDisplacedList()) {
				Element displacementType = doc.createElement("displacementType");
				innerElement.appendChild(displacementType);
				CharacterUtils.addAttribute(doc, displacementType, "value", dt.toString());
			}
		}

		return element;
	}

	public static AbstractClothing loadFromXML(Element parentElement, Document doc) {
		AbstractClothing clothing = null;

		try {
			clothing = AbstractClothingType.generateClothing(ClothingType.getClothingTypeFromId(parentElement.getAttribute("id")), false);
		} catch(Exception ex) {
			System.err.println("Warning: An instance of AbstractClothing was unable to be imported. ("+parentElement.getAttribute("id")+")");
			return null;
		}

		if(clothing==null) {
			System.err.println("Warning: An instance of AbstractClothing was unable to be imported. ("+parentElement.getAttribute("id")+")");
			return null;
		}

		if(!parentElement.getAttribute("name").isEmpty()) {
			clothing.setName(parentElement.getAttribute("name"));
		}

		if(!parentElement.getAttribute("slotEquippedTo").isEmpty()) {
			clothing.setSlotEquippedTo(InventorySlot.valueOf(parentElement.getAttribute("slotEquippedTo")));
		}
//		else { // If this is pre-version 0.3.3.9, set slot to default:
//			clothing.setSlotEquippedTo(clothing.getClothingType().getEquipSlots().get(0));
//		}

		// Try to load colours:
		if((clothing.getClothingType().equals(ClothingType.getClothingTypeFromId("BDSM_CHOKER")) && Main.isVersionOlderThan(Game.loadingVersion, "0.2.12.6"))
				|| (clothing.getClothingType().equals(ClothingType.getClothingTypeFromId("innoxia_ankle_shin_guards")) && Main.isVersionOlderThan(Game.loadingVersion, "0.3.0.6"))
				|| (clothing.getClothingType().equals(ClothingType.getClothingTypeFromId("FOOT_TRAINERS")) && Main.isVersionOlderThan(Game.loadingVersion, "0.3.1.2"))
				|| (clothing.getClothingType().equals(ClothingType.getClothingTypeFromId("innoxia_sock_toeless_striped_stockings")) && Main.isVersionOlderThan(Game.loadingVersion, "0.3.2"))) {
			try {
				clothing.setColour(Colour.valueOf(parentElement.getAttribute("colourSecondary")));
				clothing.setSecondaryColour(Colour.valueOf(parentElement.getAttribute("colour")));
			} catch(Exception ex) {
			}

		} else if(clothing.getClothingType().equals(ClothingType.getClothingTypeFromId("FOOT_LOW_TOP_SKATER_SHOES")) && Main.isVersionOlderThan(Game.loadingVersion, "0.3.1.2")){
			try {
				clothing.setSecondaryColour(Colour.CLOTHING_WHITE);
				if(!parentElement.getAttribute("colour").isEmpty()) {
					clothing.setColour(Colour.valueOf(parentElement.getAttribute("colour")));
				} else {
					clothing.setColour(AbstractClothingType.DEFAULT_COLOUR_VALUE);
				}
			} catch(Exception ex) {
			}

		} else {
			try {
				if(!parentElement.getAttribute("colour").isEmpty()) {
					clothing.setColour(Colour.valueOf(parentElement.getAttribute("colour")));
				} else {
					clothing.setColour(AbstractClothingType.DEFAULT_COLOUR_VALUE);
				}
			} catch(Exception ex) {
			}

			try {
				if(!parentElement.getAttribute("colourSecondary").isEmpty()) {
					Colour secColour = Colour.valueOf(parentElement.getAttribute("colourSecondary"));
					if(clothing.clothingType.getAllAvailableSecondaryColours().contains(secColour)) {
						clothing.setSecondaryColour(secColour);
					}
				} else {
					clothing.setSecondaryColour(AbstractClothingType.DEFAULT_COLOUR_VALUE);
				}
			} catch(Exception ex) {
			}
		}
		try {
			if(!parentElement.getAttribute("colourTertiary").isEmpty()) {
				Colour terColour = Colour.valueOf(parentElement.getAttribute("colourTertiary"));
				if(clothing.clothingType.getAllAvailableTertiaryColours().contains(terColour)) {
					clothing.setTertiaryColour(terColour);
				}
			} else {
				clothing.setTertiaryColour(AbstractClothingType.DEFAULT_COLOUR_VALUE);
			}
		} catch(Exception ex) {
		}

		// Try to load patterns:
		try {
			if(!parentElement.getAttribute("pattern").isEmpty()) {
				String pat = parentElement.getAttribute("pattern");
				clothing.setPattern(pat);
			} else {
				clothing.setPattern("none");
			}

			if(!parentElement.getAttribute("patternColour").isEmpty()) {
				Colour colour = Colour.valueOf(parentElement.getAttribute("patternColour"));
				clothing.setPatternColour(colour);
			} else {
				clothing.setPatternColour(AbstractClothingType.DEFAULT_COLOUR_VALUE);
			}

			if(!parentElement.getAttribute("patternColourSecondary").isEmpty()) {
				Colour secColour = Colour.valueOf(parentElement.getAttribute("patternColourSecondary"));
				clothing.setPatternSecondaryColour(secColour);
			} else {
				clothing.setPatternSecondaryColour(AbstractClothingType.DEFAULT_COLOUR_VALUE);
			}

			if(!parentElement.getAttribute("patternColourTertiary").isEmpty()) {
				Colour terColour = Colour.valueOf(parentElement.getAttribute("patternColourTertiary"));
				clothing.setPatternTertiaryColour(terColour);
			} else {
				clothing.setPatternTertiaryColour(AbstractClothingType.DEFAULT_COLOUR_VALUE);
			}

		} catch(Exception ex) {
		}

		// Try to load core features:
		try {
			if(!parentElement.getAttribute("sealed").isEmpty()) {
				clothing.setSealed(Boolean.valueOf(parentElement.getAttribute("sealed")));
			}
			clothing.setDirty(null, Boolean.valueOf(parentElement.getAttribute("isDirty")));
			clothing.setEnchantmentKnown(null, Boolean.valueOf(parentElement.getAttribute("enchantmentKnown")));
		} catch(Exception ex) {
		}

		// Try to load attributes:
		if(!Main.isVersionOlderThan(Game.loadingVersion, "0.3.0.5") || !clothing.getClothingType().isCondom(clothing.getClothingType().getEquipSlots().get(0))) { // Do not load condom effects from versions prior to 0.3.0.5
			if(parentElement.getElementsByTagName("attributeModifiers")!=null && parentElement.getElementsByTagName("attributeModifiers").getLength()>0) {
				if(clothing.getClothingType().getClothingSet()==null) {
					clothing.getEffects().clear();

					Element element = (Element)parentElement.getElementsByTagName("attributeModifiers").item(0);
					NodeList modifierElements = element.getElementsByTagName("modifier");
					for(int i = 0; i < modifierElements.getLength(); i++){
						Element e = ((Element)modifierElements.item(i));
						try {
							Attribute att = Attribute.getAttributeFromId(e.getAttribute("attribute"));
							int value = Integer.valueOf(e.getAttribute("value"));

							TFPotency pot = TFPotency.BOOST;
							if(value <= -5) {
								pot = TFPotency.MAJOR_DRAIN;
							} else if(value <= -3) {
								pot = TFPotency.DRAIN;
							} else if(value <= -1) {
								pot = TFPotency.MINOR_DRAIN;
							} else if(value <= 1) {
								pot = TFPotency.MINOR_BOOST;
							} else if(value <= 3) {
								pot = TFPotency.BOOST;
							} else {
								pot = TFPotency.MAJOR_BOOST;
							}

							for(TFModifier mod : TFModifier.getClothingAttributeList()) {
								if(mod.getAssociatedAttribute()==att) {
									clothing.addEffect(new ItemEffect(ItemEffectType.CLOTHING, TFModifier.CLOTHING_ATTRIBUTE, mod, pot, 0));
									break;
								}
							}

							for(TFModifier mod : TFModifier.getClothingMajorAttributeList()) {
								if(mod.getAssociatedAttribute()==att) {
									clothing.addEffect(new ItemEffect(ItemEffectType.CLOTHING, TFModifier.CLOTHING_MAJOR_ATTRIBUTE, mod, pot, 0));
									break;
								}
							}

						} catch(Exception ex) {
						}
					}
				}

			} else {
				try {
					clothing.getEffects().clear();

					Element element = (Element)parentElement.getElementsByTagName("effects").item(0);
					if(element!=null) {
						NodeList effectElements = element.getElementsByTagName("effect");
						for(int i=0; i<effectElements.getLength(); i++){
							Element e = ((Element)effectElements.item(i));
							ItemEffect ie = ItemEffect.loadFromXML(e, doc);
							if(ie!=null) {
								clothing.addEffect(ie);
							}
						}
					}
				} catch(Exception ex) {
				}
			}
		} else {
			clothing.setEnchantmentKnown(null, true);
		}

		// Try to load displacements:
		try {
			clothing.displacedList = new ArrayList<>();
			Element displacementElement = (Element)parentElement.getElementsByTagName("displacedList").item(0);
			if(displacementElement!=null) {
				NodeList displacementTypeElements = displacementElement.getElementsByTagName("displacementType");
				for(int i = 0; i < displacementTypeElements.getLength(); i++){
					Element e = ((Element)displacementTypeElements.item(i));

					DisplacementType dt = DisplacementType.valueOf(e.getAttribute("value"));
					boolean displacementTypeFound = false;
					for (BlockedParts bp : clothing.getClothingType().getBlockedPartsMap(null, clothing.getSlotEquippedTo())) {
						if (bp.displacementType == dt) {
							displacementTypeFound = true;
						}
					}
					if(displacementTypeFound) {
						clothing.displacedList.add(dt);
					} else {
						System.err.println("Warning: Invalid displacement");
					}
				}
			}
		} catch(Exception ex) {
		}

		return clothing;
	}

	public Colour getSecondaryColour() {
		if(secondaryColour==null) {
			return AbstractClothingType.DEFAULT_COLOUR_VALUE;
		}
		return secondaryColour;
	}

	public void setSecondaryColour(Colour secondaryColour) {
		this.secondaryColour = secondaryColour;
	}

	public Colour getTertiaryColour() {
		if(tertiaryColour==null) {
			return AbstractClothingType.DEFAULT_COLOUR_VALUE;
		}
		return tertiaryColour;
	}

	public void setTertiaryColour(Colour tertiaryColour) {
		this.tertiaryColour = tertiaryColour;
	}

	/**
	 * Returns the name of a pattern that the clothing has.
	 * @return
	 */
	public String getPattern() {
		if(pattern == null) {
			return "none";
		}
		return pattern;
	}

	/**
	 * Changes pattern to specified one. Will not render that pattern if it doesn't exist or the item doesn't support it anyway.
	 * @param pattern
	 */
	public void setPattern(String pattern) {
		this.pattern = pattern;
	}

	public Colour getPatternColour() {
		if(patternColour==null) {
			return AbstractClothingType.DEFAULT_COLOUR_VALUE;
		}
		return patternColour;
	}

	public Colour getPatternSecondaryColour() {
		if(patternSecondaryColour==null) {
			return AbstractClothingType.DEFAULT_COLOUR_VALUE;
		}
		return patternSecondaryColour;
	}

	public Colour getPatternTertiaryColour() {
		if(patternTertiaryColour==null) {
			return AbstractClothingType.DEFAULT_COLOUR_VALUE;
		}
		return patternTertiaryColour;
	}

	public void setPatternColour(Colour patternColour) {
		this.patternColour = patternColour;
	}

	public void setPatternSecondaryColour(Colour patternSecondaryColour) {
		this.patternSecondaryColour = patternSecondaryColour;
	}

	public void setPatternTertiaryColour(Colour patternTertiaryColour) {
		this.patternTertiaryColour = patternTertiaryColour;
	}

	private static StringBuilder descriptionSB = new StringBuilder();

	public String getTypeDescription() {
		return this.getClothingType().getDescription();
	}

	@Override
	public String getDescription() {
		descriptionSB.setLength(0);

		descriptionSB.append(
				"<p>"
					+ getTypeDescription()
				+ "</p>");

		// Physical resistance
		if(getClothingType().getPhysicalResistance()>0) {
			descriptionSB.append("<p>"
							+ (getClothingType().isPlural()
									? "They are armoured, and provide "
									: "It is armoured, and provides ")
								+ " <b>" + getClothingType().getPhysicalResistance() + "</b> [style.colourResPhysical(" + Attribute.RESISTANCE_PHYSICAL.getName() + ")]."
							+ "</p>");
		}
		if (enchantmentKnown) {
			if (!this.getEffects().isEmpty()) {
				descriptionSB.append("<p>Effects:");
				for (ItemEffect e : this.getEffects()) {
					if(e.getPrimaryModifier()!=TFModifier.CLOTHING_ATTRIBUTE
							&& e.getPrimaryModifier()!=TFModifier.CLOTHING_MAJOR_ATTRIBUTE) {
						for(String s : e.getEffectsDescription(Main.game.getPlayer(), Main.game.getPlayer())) {
							descriptionSB.append("<br/>"+ s);
						}
					}
				}
				for(Entry<Attribute, Integer> entry : this.getAttributeModifiers().entrySet()) {
					descriptionSB.append("<br/>"+
							(entry.getValue()<0
									?"[style.boldBad("+entry.getValue()+")] "
									:"[style.boldGood(+"+entry.getValue()+")] ")
							+ "<b style='color:"+entry.getKey().getColour().toWebHexString()+";'>"+Util.capitaliseSentence(entry.getKey().getName())+"</b>");
				}
				descriptionSB.append("</p>");
			}

			descriptionSB.append("<p>" + (getClothingType().isPlural() ? "They have" : "It has") + " a value of " + UtilText.formatAsMoney(getValue()) + ".");
		} else {
			descriptionSB.append("<p>" + (getClothingType().isPlural() ? "They have" : "It has") + " an <b>unknown value</b>!");
		}

		descriptionSB.append("</p>");

		if (getClothingType().getClothingSet() != null) {
			descriptionSB.append("<p>" + (getClothingType().isPlural() ? "They are" : "It is") + " part of the <b style='color:" + Colour.RARITY_EPIC.toWebHexString() + ";'>"
					+ getClothingType().getClothingSet().getName() + "</b> set." + "</p>");
		}

		return descriptionSB.toString();
	}

	public AbstractClothingType getClothingType() {
		return clothingType;
	}

	public InventorySlot getSlotEquippedTo() {
		return slotEquippedTo;
	}

	public void setSlotEquippedTo(InventorySlot slotEquippedTo) {
		this.slotEquippedTo = slotEquippedTo;
	}

	public boolean isCanBeEquipped(GameCharacter clothingOwner, InventorySlot slot) {
		return this.getClothingType().isAbleToBeBeEquipped(clothingOwner, slot).getKey();
	}

	public String getCannotBeEquippedText(GameCharacter clothingOwner, InventorySlot slot) {
		return UtilText.parse(clothingOwner, this.getClothingType().isAbleToBeBeEquipped(clothingOwner, slot).getValue());
	}

	@Override
	public Rarity getRarity() {
		if(this.isCondom(this.getClothingType().getEquipSlots().get(0))) {
			if(!this.getEffects().isEmpty() && this.getEffects().get(0).getPotency().isNegative()) {
				return Rarity.JINXED;
			} else {
				return rarity;
			}
		}

		if(rarity==Rarity.LEGENDARY || rarity==Rarity.QUEST) {
			return rarity;
		}
		if(this.getClothingType().getClothingSet()!=null || rarity==Rarity.EPIC) {
			return Rarity.EPIC;
		}

		if(this.isSealed() || this.isBadEnchantment()) {
			return Rarity.JINXED;
		}
		if(rarity==Rarity.COMMON) {
			if(this.getEffects().size()>1) {
				return Rarity.RARE;
			}
			if(!this.getEffects().isEmpty()) {
				return Rarity.UNCOMMON;
			}

			return Rarity.COMMON;
		}

		return rarity;
	}

	@Override
	public int getValue() {
		float runningTotal = this.getClothingType().getBaseValue();

		if (colourShade == Colour.CLOTHING_PLATINUM) {
			runningTotal *= 2f;

		} else if (colourShade == Colour.CLOTHING_GOLD) {
			runningTotal *= 1.75f;

		} else if (colourShade == Colour.CLOTHING_ROSE_GOLD) {
			runningTotal *= 1.5f;

		} else if (colourShade == Colour.CLOTHING_SILVER) {
			runningTotal *= 1.25f;
		}

		if(rarity==Rarity.JINXED) {
			runningTotal *= 0.5;
		}

		float attributeBonuses = 0;//getModifiedDropoffValue
		if (attributeModifiers != null) {
			for (Integer i : attributeModifiers.values()) {
				attributeBonuses += i * 15;
			}
		}

		if (getClothingType().getClothingSet() != null) {
			if (getClothingType().getClothingSet().getAssociatedStatusEffect().getAttributeModifiers(Main.game.getPlayer()) != null) {
				for (Float f : getClothingType().getClothingSet().getAssociatedStatusEffect().getAttributeModifiers(Main.game.getPlayer()).values()) {
					attributeBonuses += f * 15;
				}
			}
		}

		attributeBonuses = Util.getModifiedDropoffValue(attributeBonuses, 500);

		runningTotal += Math.max(0, attributeBonuses);

		if (runningTotal < 1) {
			runningTotal = 1;
		}

		return (int) runningTotal;
	}

	@Override
	public int getPrice(float modifier) {
		if (!enchantmentKnown) {
			return 50;
		}
		return super.getPrice(modifier);
	}

	@Override
	public String getName() {
		return !this.getEffects().isEmpty() || !name.isEmpty()
					?name
					:this.getClothingType().getName();
	}

	private String getColourName() {
		if(this.getClothingType().isColourDerivedFromPattern() && this.getPattern()!="none") {
			return this.getPatternColour().getName();
		}
		return getColour().getName();
	}

	/**
	 * @param withDeterminer
	 *            True if you want the determiner to prefix the name
	 * @return A string in the format "blue shirt" or "a blue shirt"
	 */
	public String getName(boolean withDeterminer) {
		return (withDeterminer
				? (getClothingType().isPlural()
						? getClothingType().getDeterminer() + " "
						: (Util.isVowel(getColourName().charAt(0))
								? "an "
								: "a "))
				: "") + getColourName() + " " + getName();
	}

	public String getName(boolean withDeterminer, boolean withRarityColour) {
		if (!enchantmentKnown) {
			return (withDeterminer
						? (getClothingType().isPlural()
								? getClothingType().getDeterminer() + " "
								: (Util.isVowel(getColourName().charAt(0)) ? "an " : "a "))
						: "")
					+ getColourName()
					+ (withRarityColour
							? (" <span style='color: " + Colour.RARITY_UNKNOWN.toWebHexString() + ";'>" + getName() + "</span>")
							: " "+getName());
		} else {
			return (withDeterminer
					? (getClothingType().isPlural()
							? getClothingType().getDeterminer() + " "
							: (Util.isVowel(getColourName().charAt(0)) ? "an " : "a "))
					: "")
					+ getColourName()
					+ (withRarityColour
							? (" <span style='color: " + this.getRarity().getColour().toWebHexString() + ";'>" + getName() + "</span>")
							: " "+getName());
		}
	}

	/**
	 * @param withRarityColour
	 *            If true, the name will be coloured to its rarity.
	 * @return A string in the format "Blue cap of frostbite" or
	 *         "Gold circlet of anti-magic"
	 */
	public String getDisplayName(boolean withRarityColour) {

		if(!this.getName().replaceAll("\u00A0"," ").equalsIgnoreCase(this.getClothingType().getName().replaceAll("\u00A0"," "))) { // If this item has a custom name, just display that:
			return (withRarityColour
					? (" <span style='color: " + (!this.isEnchantmentKnown()?Colour.RARITY_UNKNOWN:this.getRarity().getColour()).toWebHexString() + ";'>" + getName() + "</span>")
					: getName());
		}

		return Util.capitaliseSentence(getColourName()) + " "
				+ (!this.getPattern().equalsIgnoreCase("none")?Pattern.getPattern(this.getPattern()).getNiceName():"")
				+ (withRarityColour
					? (" <span style='color: " + (!this.isEnchantmentKnown()?Colour.RARITY_UNKNOWN:this.getRarity().getColour()).toWebHexString() + ";'>" + getName() + "</span>")
					: getName())
				+(!this.getEffects().isEmpty() && this.isEnchantmentKnown() && this.getRarity()!=Rarity.QUEST && this.getRarity()!=Rarity.LEGENDARY && this.getRarity()!=Rarity.EPIC
						? " "+getEnchantmentPostfix(withRarityColour, "span")
						: "");
	}

	@Override
	public String getSVGString() {
		InventorySlot slotEquippedTo = this.getSlotEquippedTo();
		if(slotEquippedTo==null) {
			slotEquippedTo = this.getClothingType().getEquipSlots().get(0);
		}
		return getClothingType().getSVGImage(slotEquippedTo, getColour(), getSecondaryColour(), getTertiaryColour(), pattern, getPatternColour(), getPatternSecondaryColour(), getPatternTertiaryColour());
	}

	public String getSVGEquippedString(GameCharacter character) {
		InventorySlot slotEquippedTo = this.getSlotEquippedTo();
		if(slotEquippedTo==null) {
			slotEquippedTo = this.getClothingType().getEquipSlots().get(0);
		}
		return getClothingType().getSVGEquippedImage(character, slotEquippedTo, getColour(), getSecondaryColour(), getTertiaryColour(), pattern, getPatternColour(), getPatternSecondaryColour(), getPatternTertiaryColour());
	}

	/**
	 * Applies any extra effects this clothing causes when being equipped. To be called <b>immediately after</b> equipping clothing.
	 *
	 * @return A description of this clothing being equipped.
	 */
	public String onEquipApplyEffects(GameCharacter clothingOwner, GameCharacter clothingEquipper, boolean rough) {
		if (!enchantmentKnown) {
			this.setEnchantmentKnown(clothingOwner, true);

			pointlessSB.setLength(0);
				if (this.isBadEnchantment()) {
					clothingOwner.incrementAttribute(Attribute.MAJOR_CORRUPTION, 1);
					pointlessSB.append(
							getClothingType().equipText(clothingOwner, clothingEquipper, this.getSlotEquippedTo(), rough, this, true)
							+ "<p style='text-align:center;'>"
									+ "<b style='color:" + Colour.GENERIC_BAD.toWebHexString() + ";'>Jinx revealed:</b> "+getDisplayName(true));

					for(Entry<Attribute, Integer> att : attributeModifiers.entrySet()) {
						pointlessSB.append("<br/><b>(" + att.getValue()+"</b> <b style='color:"+att.getKey().getColour().toWebHexString()+";'>"+ Util.capitaliseSentence(att.getKey().getName()) + "</b><b>)</b>");
					}

					pointlessSB.append("<br/>"
							+ "<b>"+(clothingOwner.isPlayer()?"You gain":UtilText.parse(clothingOwner, "[npc.Name] gains"))
									+" +1</b> <b style='color:" + Colour.GENERIC_TERRIBLE.toWebHexString()+ ";'>core</b> <b style='color:" + Colour.ATTRIBUTE_CORRUPTION.toWebHexString() + ";'>corruption</b> <b>from discovering their jinx...</b>"
							+ "</p>");

				} else {
					pointlessSB.append(
							getClothingType().equipText(clothingOwner, clothingEquipper, this.getSlotEquippedTo(), rough, this, true)
							+ "<p style='text-align:center;'>"
									+ "<b style='color:" + Colour.GENERIC_GOOD.toWebHexString() + ";'>Enchantment revealed:</b> "+getDisplayName(true));

					for(Entry<Attribute, Integer> att : attributeModifiers.entrySet()) {
						pointlessSB.append("<br/><b>(+" + att.getValue()+"</b> <b style='color:"+att.getKey().getColour().toWebHexString()+";'>"+ Util.capitaliseSentence(att.getKey().getName()) + "</b><b>)</b>");
					}

					pointlessSB.append("</p>");
				}

			return pointlessSB.toString();

		} else {
			return getClothingType().equipText(clothingOwner, clothingEquipper, this.getSlotEquippedTo(), rough, this, true);
		}
	}

	/**
	 * @return A description of this clothing being equipped. To be called <b>immediately after</b> equipping clothing.
	 */
	public String onEquipText(GameCharacter clothingOwner, GameCharacter clothingEquipper, boolean rough) {
		return getClothingType().equipText(clothingOwner, clothingEquipper, this.getSlotEquippedTo(), rough, this, false);
	}

	/**
	 * Applies any extra effects this clothing causes when being unequipped. To be called <b>immediately before</b> actually unequipping.
	 *
	 * @return A description of this clothing being unequipped.
	 */
	public String onUnequipApplyEffects(GameCharacter clothingOwner, GameCharacter clothingEquipper, boolean rough) {
		return getClothingType().unequipText(clothingOwner, clothingEquipper, this.getSlotEquippedTo(), rough, this, true);
	}

	/**
	 * @return A description of this clothing being unequipped. To be called <b>immediately before</b> actually unequipping.
	 */
	public String onUnequipText(GameCharacter clothingOwner, GameCharacter clothingEquipper, boolean rough) {
		return getClothingType().unequipText(clothingOwner, clothingEquipper, this.getSlotEquippedTo(), rough, this, false);
	}

	private static List<String> incompatibleClothing = new ArrayList<>();

	/**
	 * Returns a formatted description of if this clothing is sealed, cummedIn, too feminine/masculine and what slots it is blocking.
	 */
	public String clothingExtraInformation(GameCharacter equippedToCharacter, InventorySlot slotToBeEquippedTo) {
		StringBuilder extraInformationSB = new StringBuilder();

		if (equippedToCharacter == null) { // The clothing is not currently equipped by anyone:

			incompatibleClothing.clear();
			if (!getClothingType().getIncompatibleSlots(null, slotToBeEquippedTo).isEmpty()) {
				for (InventorySlot invSlot : getClothingType().getIncompatibleSlots(null, slotToBeEquippedTo))
					if (Main.game.getPlayer().getClothingInSlot(invSlot) != null)
						incompatibleClothing.add(Main.game.getPlayer().getClothingInSlot(invSlot).getClothingType().getName());
			}
			for (AbstractClothing c : Main.game.getPlayer().getClothingCurrentlyEquipped())
				for (InventorySlot invSlot : c.getClothingType().getIncompatibleSlots(null, c.getSlotEquippedTo()))
					if (slotToBeEquippedTo == invSlot)
						incompatibleClothing.add(c.getClothingType().getName());

			if(!getClothingType().getIncompatibleSlots(null, slotToBeEquippedTo).isEmpty()) {
				extraInformationSB.append("Equipping "+(getClothingType().isPlural()?"them":"it")+" will [style.boldBad(block)] your "+ Util.inventorySlotsToStringList(getClothingType().getIncompatibleSlots(null, slotToBeEquippedTo))+".<br/>");
			}

			if(Main.game.getPlayer().getClothingInSlot(slotToBeEquippedTo)!=null && Main.game.getPlayer().getClothingInSlot(slotToBeEquippedTo).getClothingType().isDiscardedOnUnequip(slotToBeEquippedTo)) {
				extraInformationSB.append("[style.boldBad(Equipping this will cause the "+Main.game.getPlayer().getClothingInSlot(slotToBeEquippedTo).getName()+" you're already wearing to be discarded!)]<br/>");
			}

			if(this.isSealed() && enchantmentKnown) {
				extraInformationSB.append((getClothingType().isPlural() ? "They" : "It") + " will [style.boldJinx(jinx)] " + (getClothingType().isPlural() ? "themselves" : "itself") + " onto you!<br/>");
			}

			if(!enchantmentKnown) {
				extraInformationSB.append("You can either take " + (getClothingType().isPlural() ? "them" : "it") + " to a suitable vendor, or equip " + (getClothingType().isPlural() ? "them" : "it") + " now to identify the"
						+ " <b style='color: "+ Colour.RARITY_UNKNOWN.toWebHexString() + ";'>unknown enchantment</b>!<br/>");
			}

			if(dirty) {
				extraInformationSB.append((getClothingType().isPlural() ? "They have" : "It has") + " been <b style='color: " + Colour.CUM.toWebHexString() + ";'>covered in sexual fluids</b>!<br/>");
			}

			for(ItemTag tag : this.getClothingType().getItemTags(slotToBeEquippedTo)) {
				if(tag.getClothingTooltipAdditions()!=null) {
					for(String description : tag.getClothingTooltipAdditions()) {
						extraInformationSB.append(description+"<br/>");
					}
				}
			}

			if(getClothingType().getFemininityMaximum() < Main.game.getPlayer().getFemininityValue()) {
				extraInformationSB.append((getClothingType().isPlural() ? "They are" : "It is") + " <b style='color: " + Colour.MASCULINE.toWebHexString() + ";'>too masculine</b> for you.<br/>");
			}

			if(getClothingType().getFemininityMinimum() > Main.game.getPlayer().getFemininityValue()) {
				extraInformationSB.append((getClothingType().isPlural() ? "They are" : "It is") + " <b style='color: " + Colour.FEMININE.toWebHexString() + ";'>too feminine</b> for you.<br/>");
			}

			if(!incompatibleClothing.isEmpty()) {
				extraInformationSB.append((getClothingType().isPlural() ? "They are" : "It is") + " <b style='color:" + Colour.GENERIC_BAD.toWebHexString() + ";'>incompatible</b> with your "
						+ Util.stringsToStringList(incompatibleClothing, false) + ".<br/>");
			}

			if(extraInformationSB.length()==0) {
				return "";
			}
			return "<p>"+extraInformationSB.toString().substring(0, extraInformationSB.length()-5)+"</p>";

		} else {
			if(!getClothingType().getIncompatibleSlots(equippedToCharacter, slotToBeEquippedTo).isEmpty()) {
				extraInformationSB.append((getClothingType().isPlural() ? "They are" : "It is") + " [style.boldBad(blocking)] [npc.her] "
						+ Util.inventorySlotsToStringList(getClothingType().getIncompatibleSlots(equippedToCharacter, slotToBeEquippedTo)) + "!<br/>");
			}

			if(this.isSealed()) {
				extraInformationSB.append((getClothingType().isPlural() ? "They are" : "It is") + " [style.boldCorruption(sealed)] and can't be removed!<br/>");

			} else if(this.getClothingType().isDiscardedOnUnequip(this.getSlotEquippedTo())) {
				extraInformationSB.append("[style.boldBad(Removing [npc.namePos] "+this.getName()+" will cause "+(getClothingType().isPlural() ? "them" : "it")+" to be discarded!)]<br/>");
			}

			if(dirty) {
				extraInformationSB.append((getClothingType().isPlural() ? "They have" : "It has") + " been <b style='color: " + Colour.CUM.toWebHexString() + ";'>covered in sexual fluids</b>!<br/>");
			}

			for(ItemTag tag : this.getClothingType().getItemTags(slotToBeEquippedTo)) {
				if(tag.getClothingTooltipAdditions()!=null) {
					for(String description : tag.getClothingTooltipAdditions()) {
						extraInformationSB.append(description+"<br/>");
					}
				}
			}

			if(getClothingType().getFemininityMaximum() < equippedToCharacter.getFemininityValue()) {
				extraInformationSB.append((getClothingType().isPlural() ? "They are" : "It is") + " <b style='color: " + Colour.MASCULINE.toWebHexString() + ";'>too masculine</b> for [npc.herHim].<br/>");
			}

			if(getClothingType().getFemininityMinimum() > equippedToCharacter.getFemininityValue()) {
				extraInformationSB.append((getClothingType().isPlural() ? "They are" : "It is") + " <b style='color: " + Colour.FEMININE.toWebHexString() + ";'>too feminine</b> for [npc.herHim].<br/>");
			}

			if(!displacedList.isEmpty()) {
				extraInformationSB.append((getClothingType().isPlural() ? "They have been" : "It has been")
						+ " <b style='color: " + Colour.GENERIC_BAD.toWebHexString() + ";'>"+ Util.displacementTypesToStringList(displacedList) + "</b>!<br/>");
			}

			if(extraInformationSB.length()==0) {
				return "";
			}
			return "<p>"+UtilText.parse(equippedToCharacter, extraInformationSB.toString().substring(0, extraInformationSB.length()-5))+"</p>";
		}

	}

	public String getDisplacementBlockingDescriptions(GameCharacter equippedToCharacter){
		descriptionSB = new StringBuilder("<p><b>Displacement types:</b>");
		for(BlockedParts bp : getClothingType().getBlockedPartsMap(equippedToCharacter, this.getSlotEquippedTo())){
			descriptionSB.append("<br/><b>"+Util.capitaliseSentence(bp.displacementType.getDescription())+":</b> ");
			if(bp.displacementType==DisplacementType.REMOVE_OR_EQUIP) {
				if(equippedToCharacter.isAbleToUnequip(this, false, equippedToCharacter)) {
					descriptionSB.append("<b style='color:"+Colour.GENERIC_GOOD.toWebHexString()+";'>Available</b>");
				} else {
					descriptionSB.append("<b style='color:"+Colour.GENERIC_BAD.toWebHexString()+";'>Blocked</b> by "+equippedToCharacter.getBlockingClothing().getName()+"");
				}
			} else {
				if(equippedToCharacter.isAbleToBeDisplaced(this, bp.displacementType, false, false, equippedToCharacter)) {
					descriptionSB.append("<b style='color:"+Colour.GENERIC_GOOD.toWebHexString()+";'>Available</b>");
				} else {
					descriptionSB.append("<b style='color:"+Colour.GENERIC_BAD.toWebHexString()+";'>Blocked</b> by "+equippedToCharacter.getBlockingClothing().getName()+"");
				}
			}
		}
		descriptionSB.append("</p>");

		return descriptionSB.toString();
	}

	public List<String> getExtraDescriptions(GameCharacter equippedToCharacter, InventorySlot slotToBeEquippedTo) {
		List<String> descriptionsList = new ArrayList<>();

		for(ItemTag tag : this.getClothingType().getItemTags(slotToBeEquippedTo)) {
			if(tag.getClothingTooltipAdditions()!=null) {
				for(String description : tag.getClothingTooltipAdditions()) {
					descriptionsList.add(description);
				}
			}
		}

		if (equippedToCharacter == null) { // The clothing is not currently equipped by anyone:
			incompatibleClothing.clear();
			if(!getClothingType().getIncompatibleSlots(null, slotToBeEquippedTo).isEmpty()) {
				for (InventorySlot invSlot : getClothingType().getIncompatibleSlots(null, slotToBeEquippedTo)) {
					if (Main.game.getPlayer().getClothingInSlot(invSlot) != null) {
						incompatibleClothing.add(Main.game.getPlayer().getClothingInSlot(invSlot).getClothingType().getName());
					}
				}
			}
			for(AbstractClothing c : Main.game.getPlayer().getClothingCurrentlyEquipped()) {
				for (InventorySlot invSlot : c.getClothingType().getIncompatibleSlots(null, c.getSlotEquippedTo())) {
					if (slotToBeEquippedTo == invSlot) {
						incompatibleClothing.add(c.getClothingType().getName());
					}
				}
			}

			if (!getClothingType().getIncompatibleSlots(null, slotToBeEquippedTo).isEmpty()) {
				// descriptionsList.add("-<b style='color:
				// "+Colour.GENERIC_BAD.toWebHexString()+";'>Equipping
				// blocks</b>");
				for (InventorySlot slot : getClothingType().getIncompatibleSlots(null, slotToBeEquippedTo)) {
					descriptionsList.add("<b style='color: " + Colour.GENERIC_BAD.toWebHexString() + ";'>Blocks " + Util.capitaliseSentence(slot.getName()) + "</b>");
				}
			}

			if (this.isSealed() && enchantmentKnown) {
				descriptionsList.add("<b style='color: " + Colour.GENERIC_ARCANE.toWebHexString() + ";'>Jinxed</b>");
			}

			if (dirty) {
				descriptionsList.add("<b style='color: " + Colour.CUM.toWebHexString() + ";'>Dirty</b>");
			}

			if (getClothingType().getFemininityMaximum() < Main.game.getPlayer().getFemininityValue()) {
				descriptionsList.add("<b style='color: " + Colour.MASCULINE.toWebHexString() + ";'>Too masculine</b>");
			}

			if (getClothingType().getFemininityMinimum() > Main.game.getPlayer().getFemininityValue()) {
				descriptionsList.add("<b style='color: " + Colour.FEMININE.toWebHexString() + ";'>Too feminine</b>");
			}

			if (!incompatibleClothing.isEmpty()) {
				descriptionsList.add("<b style='color: " + Colour.GENERIC_BAD.toWebHexString() + ";'>Incompatible with:</b>");
				descriptionsList.addAll(incompatibleClothing);
			}

		} else { // Being worn:

			if (!getClothingType().getIncompatibleSlots(equippedToCharacter, slotToBeEquippedTo).isEmpty()) {
				// descriptionsList.add("-<b style='color:
				// "+Colour.GENERIC_BAD.toWebHexString()+";'>Blocking</b>");
				for (InventorySlot slot : getClothingType().getIncompatibleSlots(equippedToCharacter, slotToBeEquippedTo))
					descriptionsList.add("<b style='color: " + Colour.GENERIC_BAD.toWebHexString() + ";'>Blocking " + Util.capitaliseSentence(slot.getName()) + "</b>");
			}

			if (this.isSealed() && enchantmentKnown) {
				descriptionsList.add("<b style='color: " + Colour.GENERIC_ARCANE.toWebHexString() + ";'>Jinxed</b>");
			}

			if (dirty) {
				descriptionsList.add("<b style='color: " + Colour.CUM.toWebHexString() + ";'>Dirty</b>");
			}

			if (getClothingType().getFemininityMaximum() < equippedToCharacter.getFemininityValue()) {
				descriptionsList.add("<b style='color: " + Colour.MASCULINE.toWebHexString() + ";'>Too masculine</b>");
			}

			if (getClothingType().getFemininityMinimum() > equippedToCharacter.getFemininityValue()) {
				descriptionsList.add("<b style='color: " + Colour.FEMININE.toWebHexString() + ";'>Too feminine</b>");
			}

			if (!displacedList.isEmpty()) {
				// descriptionsList.add("-<b style='color:
				// "+Colour.GENERIC_BAD.toWebHexString()+";'>Displaced</b>");
				for (DisplacementType dt : displacedList)
					descriptionsList.add("<b style='color: " + Colour.GENERIC_BAD.toWebHexString() + ";'>" + Util.capitaliseSentence(dt.getDescriptionPast()) + "</b>");
			}

		}

		return descriptionsList;
	}

	/**
	 * @return A list of blocked body parts. e.g. "Penis, Anus and Vagina" or "Nipples"
	 */
	public String getClothingBlockingDescription(DisplacementType dt, GameCharacter owner, InventorySlot slotToBeEquippedTo, String preFix, String postFix) {
		Set<CoverableArea> coveredAreas = new HashSet<>();// EnumSet.noneOf(CoverableArea.class);

		if (dt == null) {
			for (BlockedParts bp : this.getClothingType().getBlockedPartsMap(owner, slotToBeEquippedTo)) {
				if (!this.getDisplacedList().contains(bp.displacementType)) {
					coveredAreas.addAll(bp.blockedBodyParts);
				}
			}
		} else {
			for (BlockedParts bp : this.getClothingType().getBlockedPartsMap(owner, slotToBeEquippedTo)) {
				if (bp.displacementType == dt) {
					coveredAreas.addAll(bp.blockedBodyParts);
				}
			}
		}

		if(owner!=null) {
			if (owner.getVaginaType() == VaginaType.NONE)
				coveredAreas.remove(CoverableArea.VAGINA);
			if (owner.getPenisType() == PenisType.NONE)
				coveredAreas.remove(CoverableArea.PENIS);
		}

		if (!coveredAreas.isEmpty())
			return preFix + Util.setToStringListCoverableArea(coveredAreas) + postFix;
		else
			return "";
	}

	public void removeBadEnchantments() {
		this.getEffects().removeIf(e -> (e.getPrimaryModifier() == TFModifier.CLOTHING_ATTRIBUTE || e.getPrimaryModifier() == TFModifier.CLOTHING_MAJOR_ATTRIBUTE) && e.getPotency().isNegative());
	}

	public boolean isSealed() {
		for(ItemEffect effect : this.getEffects()) {
			if(effect!=null && effect.getSecondaryModifier()==TFModifier.CLOTHING_SEALING) {
				return true;
			} else if(effect==null) {
				System.err.println("AbstractClothing.isSealed() for "+this.getName()+" is encountering a null ItemEffect!");
			}
		}
		return false;
	}

	public void setSealed(boolean sealed) {
		// If this clothing is not equipped, adding/removing effects will cause the Map of AbstractClothing in the owner's inventory to break.
		if(sealed) {
			this.addEffect(new ItemEffect(ItemEffectType.CLOTHING, TFModifier.CLOTHING_SPECIAL, TFModifier.CLOTHING_SEALING, TFPotency.MINOR_BOOST, 0));
		} else {
			this.getEffects().removeIf(e -> e.getSecondaryModifier() == TFModifier.CLOTHING_SEALING);
		}
	}

	public int getJinxRemovalCost() {
		for(ItemEffect effect : this.getEffects()) {
			if(effect.getSecondaryModifier()==TFModifier.CLOTHING_SEALING) {
				switch(effect.getPotency()) {
					case BOOST:
						break;
					case DRAIN:
						return ItemEffect.SEALED_COST_DRAIN;
					case MAJOR_BOOST:
						break;
					case MAJOR_DRAIN:
						return ItemEffect.SEALED_COST_MAJOR_DRAIN;
					case MINOR_BOOST:
						return ItemEffect.SEALED_COST_MINOR_BOOST;
					case MINOR_DRAIN:
						return ItemEffect.SEALED_COST_MINOR_DRAIN;
				}
			}
		}
		return ItemEffect.SEALED_COST_MINOR_BOOST;
	}

	public boolean isDirty() {
		return dirty;
	}

	public void setDirty(GameCharacter owner, boolean dirty) {
		if(owner!=null) {
			if(owner.getClothingCurrentlyEquipped().contains(this)) {
//				System.out.println("1");
				AbstractClothing c = new AbstractClothing(this) {};
				owner.forceUnequipClothingIntoVoid(owner, this);
				c.dirty = dirty;
				owner.equipClothingOverride(c, c.getSlotEquippedTo(), false, false);

			} else if(owner.removeClothing(this)) {
//				System.out.println("2");
				AbstractClothing c = new AbstractClothing(this) {};
				c.dirty = dirty;
				owner.addClothing(c, false);
//				enchantmentRemovedClothing = c;

			} else {
//				System.out.println("3");
				this.dirty = dirty;
			}
		} else {
//			System.out.println("4");
			this.dirty = dirty;
		}

//		if(Main.game.getPlayer()!=null) {
//			if(Main.game.getPlayer().getClothingCurrentlyEquipped().contains(this)) {
//				Main.game.getPlayer().updateInventoryListeners();
//			}
//		}
	}

	public List<DisplacementType> getDisplacedList() {
		return displacedList;
	}

	public void clearDisplacementList() {
		displacedList.clear();
	}

	public boolean isEnchantmentKnown() {
		return enchantmentKnown;
	}

	private StringBuilder pointlessSB = new StringBuilder();
	public static AbstractClothing enchantmentRemovedClothing;
	public String setEnchantmentKnown(GameCharacter owner, boolean enchantmentKnown) {
		pointlessSB.setLength(0);

		if(owner!=null) {
			if(owner.removeClothing(this)) {
				AbstractClothing c = new AbstractClothing(this) {};
				c.enchantmentKnown = enchantmentKnown;
				owner.addClothing(c, false);
				enchantmentRemovedClothing = c;
			} else {
				this.enchantmentKnown = enchantmentKnown;
			}
		} else {
			this.enchantmentKnown = enchantmentKnown;
		}

		if(enchantmentKnown && !attributeModifiers.isEmpty()){
			if (isBadEnchantment()) {
				pointlessSB.append(
						"<p style='text-align:center;'>"
								+ "<b style='color:" + Colour.GENERIC_BAD.toWebHexString() + ";'>Jinx revealed:</b><br/>"
										+ "<b>"+Util.capitaliseSentence(getDisplayName(true))+"</b>");

			} else {
				pointlessSB.append(
						"<p style='text-align:center;'>"
								+ "<b style='color:" + Colour.GENERIC_GOOD.toWebHexString() + ";'>Enchantment revealed:</b><br/>"
										+ "<b>"+Util.capitaliseSentence(getDisplayName(true))+"</b>");
			}

			for(ItemEffect ie : this.getEffects()) {
				for(String s : ie.getEffectsDescription(Main.game.getPlayer(), Main.game.getPlayer())) {
					pointlessSB.append("<br/>"+s);
				}
			}
			pointlessSB.append("</p>");

		} else {
			return "";
		}

		return pointlessSB.toString();
	}

	public Attribute getCoreEnchantment() {
		Attribute att = null;
		int max = 0;
		for(Entry<Attribute, Integer> entry : getAttributeModifiers().entrySet()) {
			att = entry.getKey();
			if(Math.abs(entry.getValue()) > max) {
				att = entry.getKey();
				max = Math.abs(entry.getValue());
			}
		}
		if(att==null) {
			return Attribute.MAJOR_PHYSIQUE;
		}
		return att;
	}

	public String getEnchantmentPostfix(boolean coloured, String tag) {
		if(!this.getEffects().isEmpty() && !this.isCondom(this.getClothingType().getEquipSlots().get(0))) {
			for(ItemEffect ie : this.getEffects()) {
				if(ie.getSecondaryModifier() == TFModifier.CLOTHING_ENSLAVEMENT) {
					return "of "+(coloured?"<"+tag+" style='color:"+TFModifier.CLOTHING_ENSLAVEMENT.getColour().toWebHexString()+";'>enslavement</"+tag+">":"enslavement");

				} else if(ie.getPrimaryModifier() == TFModifier.TF_MOD_FETISH_BEHAVIOUR || ie.getPrimaryModifier() == TFModifier.TF_MOD_FETISH_BODY_PART) {
					return "of "+(coloured?"<"+tag+" style='color:"+Colour.FETISH.toWebHexString()+";'>"+ie.getSecondaryModifier().getDescriptor()+"</"+tag+">":ie.getSecondaryModifier().getDescriptor());

				} else if(ie.getPrimaryModifier() == TFModifier.CLOTHING_ATTRIBUTE || ie.getPrimaryModifier() == TFModifier.CLOTHING_MAJOR_ATTRIBUTE) {
					String name = (this.isBadEnchantment()?this.getCoreEnchantment().getNegativeEnchantment():this.getCoreEnchantment().getPositiveEnchantment());
					return "of "+(coloured?"<"+tag+" style='color:"+this.getCoreEnchantment().getColour().toWebHexString()+";'>"+name+"</"+tag+">":name);

				} else if(ie.getSecondaryModifier() == TFModifier.CLOTHING_SEALING) {
					return "of "+(coloured?"<"+tag+" style='color:"+Colour.SEALED.toWebHexString()+";'>sealing</"+tag+">":"sealing");

				} else {
					return "of "+(coloured?"<"+tag+" style='color:"+Colour.TRANSFORMATION_GENERIC.toWebHexString()+";'>transformation</"+tag+">":"transformation");
				}
			}
		}
		return "";
	}

	public boolean isBadEnchantment() {
		return this.getEffects().stream().mapToInt(e ->
			((e.getPrimaryModifier() == TFModifier.CLOTHING_ATTRIBUTE || e.getPrimaryModifier() == TFModifier.CLOTHING_MAJOR_ATTRIBUTE))
				?e.getPotency().getClothingBonusValue()*(e.getSecondaryModifier()==TFModifier.CORRUPTION?-1:1)
				:0
			).sum()<0;
	}

	public boolean isEnslavementClothing() {
		return this.getEffects().stream().anyMatch(e -> e.getSecondaryModifier() == TFModifier.CLOTHING_ENSLAVEMENT);
	}

	@Override
	public List<ItemEffect> getEffects() {
		return effects;
	}

	/**
	 * <b>Do not call when equipped to someone!</b> (It will not update the wearer's attributes.)
	 */
	public void addEffect(ItemEffect effect) {
		effects.add(effect);
	}

	/**
	 * <b>Do not call when equipped to someone!</b> (It will not update the wearer's attributes.)
	 */
	public void removeEffect(ItemEffect effect) {
		effects.remove(effect);
	}

	/**
	 * <b>Do not call when equipped to someone!</b> (It will not update the wearer's attributes.)
	 */
	public void clearEffects() {
		effects.clear();
	}

	@Override
	public Map<Attribute, Integer> getAttributeModifiers() {
		attributeModifiers.clear();

		for(ItemEffect ie : getEffects()) {
			if(ie.getPrimaryModifier() == TFModifier.CLOTHING_ATTRIBUTE || ie.getPrimaryModifier() == TFModifier.CLOTHING_MAJOR_ATTRIBUTE) {
				attributeModifiers.merge(ie.getSecondaryModifier().getAssociatedAttribute(), ie.getPotency().getClothingBonusValue(), Integer::sum);
			}
		}

		return attributeModifiers;
	}

	/**
	 * @return An integer value of the 'enchantment capacity cost' for this particular piece of clothing. Does not count negative attribute values, and values of Corruption are reversed (so reducing corruption costs enchantment stability).
	 */
	public int getEnchantmentCapacityCost() {
		Map<Attribute, Integer> noCorruption = new HashMap<>();
		attributeModifiers.entrySet().stream().filter(ent -> ent.getKey()!=Attribute.FERTILITY && ent.getKey()!=Attribute.VIRILITY).forEach(ent -> noCorruption.put(ent.getKey(), ent.getValue()*(ent.getKey()==Attribute.MAJOR_CORRUPTION?-1:1)));
		return noCorruption.values().stream().reduce(0, (a, b) -> a + Math.max(0, b));
	}

	@Override
	public int getEnchantmentLimit() {
		return clothingType.getEnchantmentLimit();
	}

	@Override
	public AbstractItemEffectType getEnchantmentEffect() {
		return clothingType.getEnchantmentEffect();
	}

	@Override
	public AbstractCoreType getEnchantmentItemType(List<ItemEffect> effects) {
		return clothingType.getEnchantmentItemType(effects);
	}

	@Override
	public TFEssence getRelatedEssence() {
		return clothingType.getRelatedEssence();
	}

	public boolean isCondom(InventorySlot slotEquippedTo) {
		return this.getClothingType().getItemTags(slotEquippedTo).contains(ItemTag.CONDOM);
	}

	public boolean isCondom() {
		if(this.getSlotEquippedTo()==null) {
			return this.getClothingType().getItemTags(this.getClothingType().getEquipSlots().get(0)).contains(ItemTag.CONDOM);
		}
		return this.getClothingType().getItemTags(slotEquippedTo).contains(ItemTag.CONDOM);
	}

	public ItemEffect getCondomEffect() {
		for(ItemEffect ie : this.getEffects()) {
			if(ie.getPrimaryModifier()==TFModifier.CLOTHING_CONDOM) {
				return ie;
			}
		}
		return null;
	}

	@Override
	public Set<ItemTag> getItemTags() {
		if(this.getSlotEquippedTo()==null) {
			return new HashSet<>(this.getClothingType().getDefaultItemTags());
		}
		return new HashSet<>(this.getClothingType().getItemTags(this.getSlotEquippedTo()));
	}
}