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

import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import com.lilithsthrone.controller.xmlParsing.Element;
import com.lilithsthrone.controller.xmlParsing.XMLLoadException;
import com.lilithsthrone.controller.xmlParsing.XMLMissingTagException;
import com.lilithsthrone.game.character.EquipClothingSetting;
import com.lilithsthrone.game.character.GameCharacter;
import com.lilithsthrone.game.character.body.valueEnums.Femininity;
import com.lilithsthrone.game.character.body.valueEnums.LegConfiguration;
import com.lilithsthrone.game.character.fetishes.Fetish;
import com.lilithsthrone.game.combat.DamageType;
import com.lilithsthrone.game.dialogue.utils.UtilText;
import com.lilithsthrone.game.inventory.InventorySlot;
import com.lilithsthrone.game.inventory.ItemTag;
import com.lilithsthrone.game.inventory.Rarity;
import com.lilithsthrone.game.inventory.weapon.AbstractWeapon;
import com.lilithsthrone.game.inventory.weapon.AbstractWeaponType;
import com.lilithsthrone.game.inventory.weapon.WeaponType;
import com.lilithsthrone.utils.Colour;
import com.lilithsthrone.utils.ColourListPresets;
import com.lilithsthrone.utils.Util;
import com.lilithsthrone.world.WorldType;

/**
 * @since 0.3.1
 * @version 0.3.2
 * @author Innoxia
 */
public abstract class AbstractOutfit {

	// Initially read and stored for quick checking:

	private String filePath;
	private String name;
	private String description;
	private List<WorldType> worldTypes;
	private Femininity femininity;
	private List<OutfitType> outfitTypes;
	private List<LegConfiguration> acceptableLegConfigurations;
	private String conditional;
	private int weight;

	// For use when reading:

	private List<String> innerConditionals;
	private List<List<Colour>> presetColourGroups;

	public AbstractOutfit(File outfitXMLFile) throws XMLLoadException {
		try {
			this.filePath =  outfitXMLFile.getAbsolutePath();

			Element outfitElement = Element.getDocumentRootElement(outfitXMLFile); // Loads the document and returns the root element - in outfit mods it's <outfit>
			Element coreAttributes = outfitElement.getMandatoryFirstOf("coreAttributes");

			this.name =        	coreAttributes.getMandatoryFirstOf("name").getTextContent();
			this.description = 	coreAttributes.getMandatoryFirstOf("description").getTextContent();
			this.femininity = 	Femininity.valueOf(coreAttributes.getMandatoryFirstOf("femininity").getTextContent());
			this.conditional = 	coreAttributes.getMandatoryFirstOf("conditional").getTextContent();
			this.weight = 		Integer.valueOf(coreAttributes.getMandatoryFirstOf("weight").getTextContent());

			if(coreAttributes.getOptionalFirstOf("worldTypes").isPresent()) {
				this.worldTypes = coreAttributes
					.getMandatoryFirstOf("worldTypes")
					.getAllOf("world") // Get all child elements with this tag (checking only contents of parent element) and return them as List<Element>
					.stream() // Convert this list to Stream<Element>, which lets us do some nifty operations on every element at once
					.map( e -> WorldType.valueOf(e.getTextContent())) // Take every element and do something with them, return a Stream of results after this action. Here we load outfit types and get Stream<OutfitType>
					.filter(Objects::nonNull) // Ensure that we only add non-null effects
					.collect(Collectors.toList()); // Collect stream back into a list, but this time we get List<OutfitType> we need!
			}

			this.outfitTypes = coreAttributes
				.getMandatoryFirstOf("outfitTypes")
				.getAllOf("type") // Get all child elements with this tag (checking only contents of parent element) and return them as List<Element>
				.stream() // Convert this list to Stream<Element>, which lets us do some nifty operations on every element at once
				.map( e -> OutfitType.valueOf(e.getTextContent())) // Take every element and do something with them, return a Stream of results after this action. Here we load outfit types and get Stream<OutfitType>
				.filter(Objects::nonNull) // Ensure that we only add non-null effects
				.collect(Collectors.toList()); // Collect stream back into a list, but this time we get List<OutfitType> we need!

			// Same as above, but now with leg configurations:
			try {
				this.acceptableLegConfigurations = coreAttributes
						.getMandatoryFirstOf("acceptableLegConfigurations")
						.getAllOf("legConfiguration")
						.stream()
						.map( e -> LegConfiguration.valueOf(e.getTextContent()))
						.filter(Objects::nonNull)
						.collect(Collectors.toList());
			} catch(Exception ex) {
				this.acceptableLegConfigurations = null;
			}

		} catch(XMLMissingTagException ex){
			throw new XMLLoadException(ex, outfitXMLFile);

		} catch(Exception e){
			System.out.println(e);
			throw new XMLLoadException(e, outfitXMLFile);
		}
	}

	public boolean isAvailableForCharacter(OutfitType type, GameCharacter character) {
		switch(this.getFemininity()) {
			case FEMININE:
			case FEMININE_STRONG:
				if(character.isFeminine()
						?character.hasFetish(Fetish.FETISH_CROSS_DRESSER)
						:!character.hasFetish(Fetish.FETISH_CROSS_DRESSER)) {
					return false;
				}
				break;
			case ANDROGYNOUS:
				break;
			case MASCULINE:
			case MASCULINE_STRONG:
				if(character.isFeminine()
						?!character.hasFetish(Fetish.FETISH_CROSS_DRESSER)
						:character.hasFetish(Fetish.FETISH_CROSS_DRESSER)) {
					return false;
				}
				break;
		}

		if(this.getWorldTypes()!=null
				&& !this.getWorldTypes().isEmpty()
				&& !this.getWorldTypes().contains(character.getWorldLocation())) {
			return false;
		}

		if(!this.getOutfitTypes().contains(type)) {
			return false;
		}

		if(this.getAcceptableLegConfigurations()!=null
				&& !this.getAcceptableLegConfigurations().isEmpty()
				&& !this.getAcceptableLegConfigurations().contains(character.getLegConfiguration())) {
			return false;
		}

		return conditional.isEmpty() || evalConditional(character, conditional);
	}

	/**
	 * @param character The character to which this outfit should be applied.
	 * @param stripCharacterBeforehand Pass in true if you want the character to be stripped. Should probably usually be true.
	 * @param addWeapons true if you want weapons to be added from this outfit.
	 * @param addScarsAndTattoos true if you want scars and tattoos to be added from this outfit. TODO
	 * @param addAccessories true if you want non-core clothing to be added from this outfit.
	 * @return A description of all the items added.
	 * @throws XMLLoadException
	 */
	@SuppressWarnings("deprecation")
	public String applyOutfit(GameCharacter character, List<EquipClothingSetting> settings) throws XMLLoadException {
		StringBuilder sb = new StringBuilder();

		boolean debug = false;

		File outfitXMLFile = new File(filePath);

		if(outfitXMLFile.exists()) {
			try {
				if(settings.contains(EquipClothingSetting.REPLACE_CLOTHING)) {
					character.unequipAllClothingIntoVoid(settings.contains(EquipClothingSetting.REMOVE_SEALS), false);
				}

				Element outfitElement = Element.getDocumentRootElement(outfitXMLFile); // Loads the document and returns the root element - in outfit mods it's <outfit>
				Element generationAttributes = outfitElement.getMandatoryFirstOf("generationAttributes");

				innerConditionals = new ArrayList<>();
				try {
					for(int i=1; i<20; i++) {
						Element innerConditional = generationAttributes.getMandatoryFirstOf("clothingConditional"+i);
						if(Boolean.valueOf(innerConditional.getAttribute("constant"))) {
							innerConditionals.add(String.valueOf(evalConditional(character, innerConditional.getTextContent())));
						} else {
							innerConditionals.add(innerConditional.getTextContent());
						}
					}
				} catch(Exception ex) {
					// Just catch and continue when there are no more clothingConditional elements.
				}

				if(debug) {
					System.out.println("1");
				}

				presetColourGroups = new ArrayList<>();
				try {
					for(int i=1; i<20; i++) {
						Element presetColourGroup = generationAttributes.getMandatoryFirstOf("presetColourGroup"+i);
						List<Colour> randomColours = new ArrayList<>();

						if(!presetColourGroup.getAttribute("values").isEmpty()) {
							randomColours.addAll(ColourListPresets.getColourListFromId(presetColourGroup.getAttribute("values")));

						} else {
							for(Element e : presetColourGroup.getAllOf("randomColour")) {
								try {
									String text = e.getTextContent();
									if(text.startsWith("presetColourGroup")) {
										randomColours.addAll(presetColourGroups.get(Integer.valueOf(text.substring(text.length()-1))-1));
									} else {
										randomColours.add(Colour.valueOf(text));
									}
								} catch(Exception ex) {
									// Just catch and continue when there are no more clothingConditional elements.
								}
							}
						}
						if(Boolean.valueOf(presetColourGroup.getAttribute("singleColour"))) {
							Collections.shuffle(randomColours);
							randomColours.subList(1, randomColours.size()).clear();
						}
						presetColourGroups.add(randomColours);
					}
				} catch(Exception ex) {
					// Just catch and continue when there are no more clothingConditional elements.
				}

				if(debug) {
					System.out.println("2a");
				}

				// Main weapon:
				if(settings.contains(EquipClothingSetting.ADD_WEAPONS)) {
					try {
						List<AbstractWeapon> weapons = new ArrayList<>();
						for(Element e : generationAttributes.getMandatoryFirstOf("mainWeapons").getAllOf("weapon")) {
							try {
								String weaponConditional = e.getMandatoryFirstOf("conditional").getTextContent();
								if(!weaponConditional.isEmpty() && !evalConditional(character, weaponConditional)) {
									continue;
								}
							} catch(Exception ex) {
							}
							weapons.add(getWeapon(e));
						}
						if(!weapons.isEmpty()) {
							character.equipMainWeaponFromNowhere(Util.randomItemFrom(weapons));
						}
					} catch(Exception e){
						e.printStackTrace();
					}

					// Offhand weapon:
					try {
						List<AbstractWeapon> weapons = new ArrayList<>();
						for(Element e : generationAttributes.getMandatoryFirstOf("offhandWeapons").getAllOf("weapon")) {
							try {
								String weaponConditional = e.getMandatoryFirstOf("conditional").getTextContent();
								if(!weaponConditional.isEmpty() && !evalConditional(character, weaponConditional)) {
									continue;
								}
							} catch(Exception ex) {
							}
							weapons.add(getWeapon(e));
						}
						if(!weapons.isEmpty()) {
							character.equipOffhandWeaponFromNowhere(Util.randomItemFrom(weapons));
						}
					} catch(Exception e){
						e.printStackTrace();
					}
				}

				if(debug) {
					System.out.println("2b");
				}

				if(generationAttributes.getOptionalFirstOf("guaranteedClothingEquips").isPresent()) {
					List<AbstractClothing> guaranteedClothingEquips = new ArrayList<>();

					guaranteedClothingEquips = generationAttributes
							.getMandatoryFirstOf("guaranteedClothingEquips")
							.getAllOf("uniqueClothing")
							.stream()
							.map( e -> {
								try {
									AbstractClothing ac = AbstractClothing.loadFromXML(e.getMandatoryFirstOf("clothing").getInnerElement(), e.getDocument());

									try {
										UtilText.setClothingTypeForParsing(ac.getClothingType());
										String conditional = e.getMandatoryFirstOf("conditional").getTextContent();
										if(!evalConditional(character, conditional)) {
											return null;
										}
									} catch (XMLMissingTagException e1) {
									}

									String colourText = e.getAttribute("colour");
									if(colourText.startsWith("presetColourGroup")) {
										int index = Integer.valueOf(colourText.substring(colourText.length()-1))-1;
										List<Colour> colours = presetColourGroups.get(index);
										ac.setColour(Util.randomItemFrom(colours));
									}

									colourText = e.getAttribute("colourSecondary");
									if(colourText.startsWith("presetColourGroup")) {
										int index = Integer.valueOf(colourText.substring(colourText.length()-1))-1;
										List<Colour> colours = presetColourGroups.get(index);
										ac.setSecondaryColour(Util.randomItemFrom(colours));
									}

									colourText = e.getAttribute("colourTertiary");
									if(colourText.startsWith("presetColourGroup")) {
										int index = Integer.valueOf(colourText.substring(colourText.length()-1))-1;
										List<Colour> colours = presetColourGroups.get(index);
										ac.setTertiaryColour(Util.randomItemFrom(colours));
									}

									return ac;
								} catch (XMLMissingTagException e1) {
									e1.printStackTrace();
									System.err.println("Error in guaranteedClothingEquips()");
									return null;
								}
							})
							.filter(Objects::nonNull)
							.collect(Collectors.toList());

					if(debug) {
						System.out.println("3");
					}

					Collections.shuffle(guaranteedClothingEquips);
					for(AbstractClothing c : guaranteedClothingEquips) {
						if(c.getClothingType().getEquipSlots().get(0).isCoreClothing() || settings.contains(EquipClothingSetting.ADD_ACCESSORIES)) {
							c.setName(UtilText.parse(character, c.getName()));
							if(!character.isSlotIncompatible(c.getClothingType().getEquipSlots().get(0))) {
								character.equipClothingOverride(c, c.getClothingType().getEquipSlots().get(0), false, false);
							}
						}
					}
				}

				if(debug) {
					System.out.println("4");
				}

				List<OutfitPotential> outfitPotentials = new ArrayList<>();

				for(Element genericClothingType : generationAttributes.getAllOf("genericClothingType")) {
					List<AbstractClothingType> ctList = new ArrayList<>();

					boolean anyConditionalsFound = false;

					for(AbstractClothingType ct : ClothingType.getAllClothing()) {
						// Check for required tags:
						try {
							if(genericClothingType.getOptionalFirstOf("itemTags").isPresent()) {
								anyConditionalsFound = true;
								List<ItemTag> tags = genericClothingType.getMandatoryFirstOf("itemTags")
										.getAllOf("tag")
										.stream()
										.map( e -> ItemTag.valueOf(e.getTextContent()))
										.filter(Objects::nonNull)
										.collect(Collectors.toList());
								if(!ct.getDefaultItemTags().containsAll(tags)) {
									continue;
								}
							}
						}catch(Exception ex) {
							System.err.println("genericClothingType error: itemTags");
						}

						// Check femininity:
						try {
							if(genericClothingType.getOptionalFirstOf("acceptableFemininities").isPresent()
									&& genericClothingType.getMandatoryFirstOf("acceptableFemininities").getOptionalFirstOf("femininity").isPresent()) {
								anyConditionalsFound = true;

								List<Femininity> femininities = genericClothingType.getMandatoryFirstOf("acceptableFemininities")
										.getAllOf("femininity")
										.stream()
										.map( e -> {
											return Femininity.valueOf(e.getTextContent());
										})
										.filter(Objects::nonNull)
										.collect(Collectors.toList());

								if(ct.getFemininityRestriction()!=null) {
									switch(ct.getFemininityRestriction()) {
										case FEMININE:
										case FEMININE_STRONG:
											if(!femininities.contains(Femininity.FEMININE)
													&& !femininities.contains(Femininity.FEMININE_STRONG)) {
												continue;
											}
											break;
										case ANDROGYNOUS:
											if(!femininities.contains(Femininity.ANDROGYNOUS)) {
												continue;
											}
											break;
										case MASCULINE:
										case MASCULINE_STRONG:
											if(!femininities.contains(Femininity.MASCULINE)
													&& !femininities.contains(Femininity.MASCULINE_STRONG)) {
												continue;
											}
											break;
									}
								} else {
									if(!femininities.contains(Femininity.ANDROGYNOUS)) {
										continue;
									}
								}
							}
						} catch(Exception ex) {
							ex.printStackTrace();
							System.err.println("genericClothingType error: femininity");
						}

						// Check slot:
						try {
							if(genericClothingType.getOptionalFirstOf("slot").isPresent()
									&& !genericClothingType.getMandatoryFirstOf("slot").getTextContent().isEmpty()) {
								anyConditionalsFound = true;

								InventorySlot slot = InventorySlot.valueOf(genericClothingType.getMandatoryFirstOf("slot").getTextContent());
								if(ct.getEquipSlots().get(0)!=slot) {
									continue;
								}
							}
						} catch(Exception ex) {
							System.err.println("genericClothingType error: slot");
						}

						// Check rarity:
						try {
							if(genericClothingType.getOptionalFirstOf("rarity").isPresent()) {
								anyConditionalsFound = true;

								Rarity rarity = Rarity.valueOf(genericClothingType.getMandatoryFirstOf("rarity").getTextContent());
								if(ct.getRarity()!=rarity) {
									continue;
								}
							}
						} catch(Exception ex) {
							System.err.println("genericClothingType error: rarity");
						}

						// Check conditional:
						try {
							UtilText.setClothingTypeForParsing(ct);
							if(genericClothingType.getOptionalFirstOf("conditional").isPresent()) {
								anyConditionalsFound = true;

								String genericClothingConditional = genericClothingType.getMandatoryFirstOf("conditional").getTextContent();

								if(!genericClothingConditional.isEmpty() && !evalConditional(character, genericClothingConditional)) {
									continue;
								}
							}
						} catch(Exception ex) {
							System.err.println("genericClothingType error: conditional");
						}

						if(!anyConditionalsFound) {
							break;
						}
						if(ct.isAbleToBeBeEquipped(character, ct.getEquipSlots().get(0)).getKey()) {
							ctList.add(ct);
						}
					}

					outfitPotentials.add(getOutfitPotential(ctList, genericClothingType));
				}

				if(debug) {
					System.out.println("5");
				}

				// Add clothing types:

				for(Element clothingTypeElement : generationAttributes.getAllOf("clothingType")) {
					List<AbstractClothingType> clothingTypeList = clothingTypeElement
							.getMandatoryFirstOf("types")
							.getAllOf("type")
							.stream()
							.map( e -> {
								AbstractClothingType ct = ClothingType.getClothingTypeFromId(e.getTextContent());
								if(!ct.isAbleToBeBeEquipped(character, ct.getEquipSlots().get(0)).getKey()) {
									return null;
								}
								return ct;
							})
							.filter(Objects::nonNull)
							.collect(Collectors.toList());

					// Check conditional:
					for(AbstractClothingType ct : new ArrayList<>(clothingTypeList)) {
						try {
							UtilText.setClothingTypeForParsing(ct);
							String clothingConditional = clothingTypeElement.getMandatoryFirstOf("conditional").getTextContent();

							if(!clothingConditional.isEmpty() && !evalConditional(character, clothingConditional)) {
								clothingTypeList.remove(ct);
							}
						} catch(Exception ex) {
							ex.printStackTrace();
						}
					}
					if(!clothingTypeList.isEmpty()) {
						outfitPotentials.add(getOutfitPotential(clothingTypeList, clothingTypeElement));
					}
				}

				if(debug) {
					System.out.println("6");
				}

				// Add clothing from all potential outfit entries:
				Collections.shuffle(outfitPotentials);
				for(OutfitPotential ot : outfitPotentials) {
					Collections.shuffle(ot.getTypes());
					for(AbstractClothingType ct : ot.getTypes()) {
						if(character.getClothingInSlot(ct.getEquipSlots().get(0))==null
								&& (ct.getEquipSlots().get(0).isCoreClothing() || settings.contains(EquipClothingSetting.ADD_ACCESSORIES))) {
							if(!character.isSlotIncompatible(ct.getEquipSlots().get(0))) {
								AbstractClothing clothing = AbstractClothingType.generateClothing(
										ct,
										ot.getPrimaryColours().isEmpty()?null:Util.randomItemFrom(ot.getPrimaryColours()),
										ot.getSecondaryColours().isEmpty()?null:Util.randomItemFrom(ot.getSecondaryColours()),
										ot.getTertiaryColours().isEmpty()?null:Util.randomItemFrom(ot.getTertiaryColours()), false);

								character.equipClothingOverride(
										clothing,
										ct.getEquipSlots().get(0),
										true, // Need to replace clothing as otherwise you get things like bras and overbust corsets being equipped together, with each of them blocking the other's removal.
										false);
							}
							// Patterns are set when the clothing is created, so this was only used for testing. I've commented it out instead of deleting it as I may need it for further testing use.
//							if(clothing.getClothingType().isPatternAvailable()) {
//								clothing.setPattern(Util.randomItemFrom(new ArrayList<>(Pattern.getAllDefaultPatterns().values())).getName());
//								clothing.setPatternColour(clothing.getColour());
//								clothing.setPatternSecondaryColour(clothing.getSecondaryColour());
//								clothing.setPatternTertiaryColour(clothing.getPatternTertiaryColour());
//							}
						}
					}
				}

			} catch(Exception e){
				System.out.println(e);
				throw new XMLLoadException(e, outfitXMLFile);
			}
		}

		return sb.toString();
	}

	private boolean evalConditional(GameCharacter character, String conditional) {
		if(innerConditionals!=null) {
			for(int i=1; i<=innerConditionals.size(); i++) {
				conditional = conditional.replaceAll("clothingConditional"+String.valueOf(i), innerConditionals.get(i-1));
			}
		}
		try {
			return Boolean.valueOf(UtilText.parse(character, ("[#"+conditional+"]").replaceAll("\u200b", "")));
		} catch(Exception ex){
			System.err.println("error in AbstractOutfit evalConditional(): cannot parse to Boolean");
			return false;
		}
	}

	private OutfitPotential getOutfitPotential(List<AbstractClothingType> ctList, Element baseElement) {
		List<Colour> primaryColours = new ArrayList<>();
		try {
			if(baseElement.getOptionalFirstOf("primaryColours").isPresent()) {
				if(!baseElement.getMandatoryFirstOf("primaryColours").getAttribute("values").isEmpty()) {
					primaryColours.addAll(ColourListPresets.getColourListFromId(baseElement.getMandatoryFirstOf("primaryColours").getAttribute("values")));

				} else {
					for(Element colour : baseElement.getMandatoryFirstOf("primaryColours").getAllOf("colour")) {
						String text = colour.getTextContent();
						if(text.startsWith("presetColourGroup")) {
							primaryColours.addAll(presetColourGroups.get(Integer.valueOf(text.substring(text.length()-1))-1));
						} else {
							primaryColours.add(Colour.valueOf(text));
						}
					}
				}
			}
		} catch(Exception ex) {
			ex.printStackTrace();
			System.err.println("AbstractOutfit error: primary fail 1");
		}

		List<Colour> secondaryColours = new ArrayList<>();
		try {
			if(baseElement.getOptionalFirstOf("secondaryColours").isPresent()) {
				if(!baseElement.getMandatoryFirstOf("secondaryColours").getAttribute("values").isEmpty()) {
					primaryColours.addAll(ColourListPresets.getColourListFromId(baseElement.getMandatoryFirstOf("secondaryColours").getAttribute("values")));

				} else {
					for(Element colour : baseElement.getMandatoryFirstOf("secondaryColours").getAllOf("colour")) {
						String text = colour.getTextContent();
						if(text.startsWith("presetColourGroup")) {
							secondaryColours.addAll(presetColourGroups.get(Integer.valueOf(text.substring(text.length()-1))-1));
						} else {
							secondaryColours.add(Colour.valueOf(text));
						}
					}
				}
			}
		} catch(Exception ex) {
			ex.printStackTrace();
			System.err.println("AbstractOutfit error: secondary fail 1");
		}

		List<Colour> tertiaryColours = new ArrayList<>();
		try {
			if(baseElement.getOptionalFirstOf("tertiaryColours").isPresent()) {
				if(!baseElement.getMandatoryFirstOf("tertiaryColours").getAttribute("values").isEmpty()) {
					primaryColours.addAll(ColourListPresets.getColourListFromId(baseElement.getMandatoryFirstOf("tertiaryColours").getAttribute("values")));

				} else {
					for(Element colour : baseElement.getMandatoryFirstOf("tertiaryColours").getAllOf("colour")) {
						String text = colour.getTextContent();
						if(text.startsWith("presetColourGroup")) {
							tertiaryColours.addAll(presetColourGroups.get(Integer.valueOf(text.substring(text.length()-1))-1));
						} else {
							tertiaryColours.add(Colour.valueOf(text));
						}
					}
				}
			}
		} catch(Exception ex) {
			ex.printStackTrace();
			System.err.println("AbstractOutfit error: tertiary fail 1");
		}

		return new OutfitPotential(ctList, primaryColours, secondaryColours, tertiaryColours);
	}

	private AbstractWeapon getWeapon(Element e) {
		try {
			AbstractWeaponType wt;
			List<DamageType> dtList;
			DamageType dt = null;

			wt = WeaponType.getWeaponTypeFromId(e.getMandatoryFirstOf("type").getTextContent());

			dtList = e
					.getMandatoryFirstOf("damageTypes")
					.getAllOf("damage")
					.stream()
					.map( element -> DamageType.valueOf(element.getTextContent()))
					.filter(Objects::nonNull)
					.collect(Collectors.toList());

			if(!dtList.isEmpty()) {
				dt = Util.randomItemFrom(dtList);
			}

			List<Colour> primaryColours = new ArrayList<>();
			try {
				for(Element colour : e.getMandatoryFirstOf("primaryColours").getAllOf("colour")) {
					String text = colour.getTextContent();
					if(text.startsWith("presetColourGroup")) {
						primaryColours.addAll(presetColourGroups.get(Integer.valueOf(text.substring(text.length()-1))-1));
					} else {
						primaryColours.add(Colour.valueOf(text));
					}
				}
			} catch(Exception ex) {
				System.err.println("AbstractOutfit error: main weapon primary fail 1");
			}

			List<Colour> secondaryColours = new ArrayList<>();
			try {
				for(Element colour : e.getMandatoryFirstOf("secondaryColours").getAllOf("colour")) {
					String text = colour.getTextContent();
					if(text.startsWith("presetColourGroup")) {
						secondaryColours.addAll(presetColourGroups.get(Integer.valueOf(text.substring(text.length()-1))-1));
					} else {
						secondaryColours.add(Colour.valueOf(text));
					}
				}
			} catch(Exception ex) {
				System.err.println("AbstractOutfit error: main weapon secondary fail 1");
			}

			AbstractWeapon weapon;
			if(dt!=null) {
				weapon = AbstractWeaponType.generateWeapon(wt, dt);
			} else {
				weapon = AbstractWeaponType.generateWeapon(wt);
			}

			if(!primaryColours.isEmpty()) {
				weapon.setPrimaryColour(Util.randomItemFrom(primaryColours));
			}

			if(!secondaryColours.isEmpty()) {
				weapon.setSecondaryColour(Util.randomItemFrom(secondaryColours));
			}

			return weapon;

		} catch (XMLMissingTagException e1) {
			e1.printStackTrace();
		}
		return null;
	}

	public String getFilePath() {
		return filePath;
	}

	public String getName() {
		return name;
	}

	public String getDescription() {
		return description;
	}

	public Femininity getFemininity() {
		return femininity;
	}

	public List<WorldType> getWorldTypes() {
		return worldTypes;
	}

	public List<OutfitType> getOutfitTypes() {
		return outfitTypes;
	}

	public List<LegConfiguration> getAcceptableLegConfigurations() {
		return acceptableLegConfigurations;
	}

	public String getConditional() {
		return conditional;
	}

	public int getWeight() {
		return weight;
	}
}

repo_name: klorpa/Pedo

license: unknown


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

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringJoiner;

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

import com.lilithsthrone.game.character.body.CoverableArea;
import com.lilithsthrone.game.inventory.InventorySlot;
import com.lilithsthrone.utils.XMLSaving;

/**
 * @since 0.1.6?
 * @version 0.2.12
 * @author Innoxia, Pimgd
 */
public class BlockedParts implements XMLSaving {

	public DisplacementType displacementType;
	public List<ClothingAccess> clothingAccessRequired;
	public List<CoverableArea> blockedBodyParts;
	public List<ClothingAccess> clothingAccessBlocked;
	public List<InventorySlot> concealedSlots;

	/**
	 * A class that holds information about what clothing-related parts are
	 * blocked.
	 *
	 * @param displacementType The type of displacement that's required to block/unblock the areas.
	 * @param clothingAccessRequired The access required in order to achieve the  DisplacementType.
	 * @param blockedBodyParts The body parts that this displacement blocks/reveals.
	 * @param clothingAccessBlocked The clothing access that this displacement blocks/reveals.
	 * @param concealedSlots Slots that are concealed by this displacementType.
	 */
	public BlockedParts(
			DisplacementType displacementType,
			List<ClothingAccess> clothingAccessRequired,
			List<CoverableArea> blockedBodyParts,
			List<ClothingAccess> clothingAccessBlocked,
			List<InventorySlot> concealedSlots) {

		this.displacementType = displacementType;

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

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

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

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

	/**
	 * @param blockedParts The BlockedParts to copy.
	 */
	public BlockedParts(BlockedParts blockedParts) {
		this(blockedParts.displacementType,
				blockedParts.clothingAccessRequired,
				blockedParts.blockedBodyParts,
				blockedParts.clothingAccessBlocked,
				blockedParts.concealedSlots);
	}

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

		Element displacementTypeElement = doc.createElement("displacementType");
		blockedParts.appendChild(displacementTypeElement);
		displacementTypeElement.setTextContent(this.displacementType.toString());

		Element clothingAccessRequiredElement = doc.createElement("clothingAccessRequired");
		blockedParts.appendChild(clothingAccessRequiredElement);
		for(ClothingAccess clothingAccess : this.clothingAccessRequired) {
			Element clothingAccessElement = doc.createElement("clothingAccess");
			clothingAccessRequiredElement.appendChild(clothingAccessElement);
			clothingAccessElement.setTextContent(clothingAccess.toString());
		}

		Element blockedBodyPartsElement = doc.createElement("blockedBodyParts");
		blockedParts.appendChild(blockedBodyPartsElement);
		for(CoverableArea coverableArea : this.blockedBodyParts) {
			Element coverableAreaElement = doc.createElement("bodyPart");
			blockedBodyPartsElement.appendChild(coverableAreaElement);
			coverableAreaElement.setTextContent(coverableArea.toString());
		}

		Element clothingAccessBlockedElement = doc.createElement("clothingAccessBlocked");
		blockedParts.appendChild(clothingAccessBlockedElement);
		for(ClothingAccess clothingAccess : this.clothingAccessBlocked) {
			Element clothingAccessElement = doc.createElement("clothingAccess");
			clothingAccessBlockedElement.appendChild(clothingAccessElement);
			clothingAccessElement.setTextContent(clothingAccess.toString());
		}

		Element concealedSlotsElement = doc.createElement("concealedSlots");
		blockedParts.appendChild(concealedSlotsElement);
		for(InventorySlot inventorySlot : this.concealedSlots) {
			Element inventorySlotElement = doc.createElement("slot");
			concealedSlotsElement.appendChild(inventorySlotElement);
			inventorySlotElement.setTextContent(inventorySlot.toString());
		}

		return blockedParts;
	}

	public static BlockedParts loadFromXML(Element parentElement, Document doc, String contextForErrors) {

		DisplacementType displacementType = DisplacementType.valueOf(parentElement.getElementsByTagName("displacementType").item(0).getTextContent());

		String errorCode = "Unknown Clothing";
		Element clothingElement = ((Element) doc.getElementsByTagName("clothing").item(0));
		try { // try, as when loaded when not embedded in clothing, the "clothing" element will not be found.
			if(clothingElement.getElementsByTagName("coreAtributes").getLength()>0) {
				errorCode = ((Element) clothingElement.getElementsByTagName("coreAtributes").item(0)).getElementsByTagName("name").item(0).getTextContent(); // Support for old versions
			} else {
				errorCode = ((Element) clothingElement.getElementsByTagName("coreAttributes").item(0)).getElementsByTagName("name").item(0).getTextContent(); // Fix typo
			}
		} catch(Exception ex) {
		}

		List<ClothingAccess> loadedClothingAccessRequired = new ArrayList<>();
		Element clothingAccessRequiredElement = (Element)parentElement.getElementsByTagName("clothingAccessRequired").item(0);
		try {
			for(int i=0; i<clothingAccessRequiredElement.getElementsByTagName("clothingAccess").getLength(); i++){
				Element e = ((Element)clothingAccessRequiredElement.getElementsByTagName("clothingAccess").item(i));
				loadedClothingAccessRequired.add(ClothingAccess.valueOf(e.getTextContent()));
			}
		} catch(Exception ex) {
			System.err.println("BlockedParts loading failed for "+errorCode+". Code 1 (clothingAccessRequired -> clothingAccess); Context: "+contextForErrors);
			System.err.println(ex);
			printHelpfulErrorForEnumValueMismatches(ex, getPossibleEnumValues());
		}

		List<CoverableArea> loadedBlockedBodyParts = new ArrayList<>();
		Element blockedBodyPartsElement = (Element)parentElement.getElementsByTagName("blockedBodyParts").item(0);
		try {
			for(int i=0; i<blockedBodyPartsElement.getElementsByTagName("bodyPart").getLength(); i++){
				Element e = ((Element)blockedBodyPartsElement.getElementsByTagName("bodyPart").item(i));
				loadedBlockedBodyParts.add(CoverableArea.valueOf(e.getTextContent()));
			}
		} catch(Exception ex) {
			System.err.println("BlockedParts loading failed for "+errorCode+". Code 2 (blockedBodyParts -> bodyPart); Context: "+contextForErrors);
			System.err.println(ex);
			printHelpfulErrorForEnumValueMismatches(ex, getPossibleEnumValues());
		}

		List<ClothingAccess> loadedClothingAccessBlocked = new ArrayList<>();
		Element clothingAccessBlockedElement = (Element)parentElement.getElementsByTagName("clothingAccessBlocked").item(0);
		try {
			for(int i=0; i<clothingAccessBlockedElement.getElementsByTagName("clothingAccess").getLength(); i++){
				Element e = ((Element)clothingAccessBlockedElement.getElementsByTagName("clothingAccess").item(i));
				loadedClothingAccessBlocked.add(ClothingAccess.valueOf(e.getTextContent()));
			}
		} catch(Exception ex) {
			System.err.println("BlockedParts loading failed for "+errorCode+". Code 3 (clothingAccessBlocked -> clothingAccess); Context: "+contextForErrors);
			System.err.println(ex);
			printHelpfulErrorForEnumValueMismatches(ex, getPossibleEnumValues());
		}

		List<InventorySlot> loadedConcealedSlots = new ArrayList<>();
		Element concealedSlotsElement = (Element)parentElement.getElementsByTagName("concealedSlots").item(0);
		if(!concealedSlotsElement.getAttribute("values").isEmpty()) {
			try {
				loadedConcealedSlots = PresetConcealmentLists.valueOf(concealedSlotsElement.getAttribute("values")).getPresetInventorySlotList();
			} catch(Exception ex) {
				System.err.println("BlockedParts loading failed for "+errorCode+". Code 4a (concealedSlots.values); Context: "+contextForErrors);
				System.err.println(ex);
				printHelpfulErrorForEnumValueMismatches(ex, getPossibleEnumValues());
			}
		} else {
			try {
				for(int i=0; i<concealedSlotsElement.getElementsByTagName("slot").getLength(); i++){
					Element e = ((Element)concealedSlotsElement.getElementsByTagName("slot").item(i));
					loadedConcealedSlots.add(InventorySlot.valueOf(e.getTextContent()));
				}
			} catch(Exception ex) {
				System.err.println("BlockedParts loading failed for "+errorCode+". Code 4b (concealedSlots -> slot); Context: "+contextForErrors);
				System.err.println(ex);
				printHelpfulErrorForEnumValueMismatches(ex, getPossibleEnumValues());
			}
		}

		return new BlockedParts(displacementType,
				loadedClothingAccessRequired,
				loadedBlockedBodyParts,
				loadedClothingAccessBlocked,
				loadedConcealedSlots);
	}

	@SuppressWarnings("rawtypes")
	private static Map<Class, Object[]> getPossibleEnumValues() {
		Map<Class, Object[]> possibleEnumValues = new HashMap<>();
		possibleEnumValues.put(ClothingAccess.class, ClothingAccess.values());
		possibleEnumValues.put(InventorySlot.class, InventorySlot.values());
		possibleEnumValues.put(CoverableArea.class, CoverableArea.values());
		possibleEnumValues.put(PresetConcealmentLists.class, PresetConcealmentLists.values());
		return possibleEnumValues;
	}

	@SuppressWarnings("rawtypes")
	private static void printHelpfulErrorForEnumValueMismatches(Exception ex, Map<Class, Object[]> possibleEnumValues) {
		String exMessage = ex.getMessage();
		if (exMessage.startsWith("No enum constant")){
			for (Map.Entry<Class, Object[]> possibleMatch : possibleEnumValues.entrySet()) {
				if (exMessage.contains(possibleMatch.getKey().getCanonicalName())) {
					StringJoiner valueLister = new StringJoiner(",");
					Arrays.asList(possibleMatch.getValue()).forEach(enumValue -> valueLister.add(enumValue.toString()));
					System.err.println("Possible values for "+possibleMatch.getKey().getSimpleName()+" are " + valueLister.toString());
				}
			}
		}
	}
}


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

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

import com.lilithsthrone.game.character.race.Race;
import com.lilithsthrone.game.inventory.InventorySlot;
import com.lilithsthrone.game.inventory.ItemTag;

/**
 * @since 0.3.1
 * @version 0.3.1
 * @author Innoxia
 */
public class BodyPartClothingBlock {

	private List<InventorySlot> blockedSlots;
	private Race race;
	private String description;
	private List<ItemTag> requiredTags;

	public BodyPartClothingBlock(List<InventorySlot> blockedSlots, Race race, String description, List<ItemTag> requiredTags) {
		this.blockedSlots = blockedSlots;
		this.race = race;
		this.description = description;

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

	public List<InventorySlot> getBlockedSlots() {
		return blockedSlots;
	}

	public Race getRace() {
		return race;
	}

	public String getDescription() {
		return description;
	}

	public List<ItemTag> getRequiredTags() {
		return requiredTags;
	}

}


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

/**
 * The types of possible access you'd need in order to equip a piece of clothing.
 *
 * @since 0.1.64
 * @version 0.2.10
 * @author Innoxia
 */
public enum ClothingAccess {

	/** Placing something onto your eyes, like glasses. */
	EYES("place onto eyes"),

	/** Placing something into your mouth, like a ball-gag. */
	MOUTH("place onto mouth"),

	/** Pulling something down over your head, like a t-shirt. */
	HEAD("pull down over head"),

	/**
	 * Sliding something up your arms to rest on your shoulders, like the loops in a bra.
	 */
	ARMS_UP_TO_SHOULDER("slide over arms"),

	/** Sliding something up your wrists, like elbow-length gloves. */
	WRISTS("slide up wrists"),

	/** Sliding something down your fingers, like a ring. */
	FINGERS("slide down fingers"),

	/** Sliding something over your chest, like the cups of a bra. */
	CHEST("slide over chest"),

	/** Sliding something over your waist, like a corset or swimsuit. */
	WAIST("slide over waist"),

	/** Pulling something up your legs, like a pair of panties. */
	LEGS_UP_TO_GROIN_LOW_LEVEL("pull up between legs"),

	/** Sliding something up your legs, like the holes in a pair of trousers. */
	LEGS_UP_TO_GROIN("slide up legs"),

	/** Sliding something over your groin, like a pair of panties.*/
	GROIN("slide over groin"),

	/** Inserting something into your anus, like a butt-plug. */
	ANUS("insert into anus"),

	/** Sliding something over your calves, like a pair of knee-high socks. */
	CALVES("slide over calves"),

	/** Sliding something over your feet, like a pair of socks. */
	FEET("slide over feet");

	String descriptor;

	private ClothingAccess(String descriptor) {
		this.descriptor = descriptor;
	}

	public String getDescriptor() {
		return descriptor;
	}

	public void setDescriptor(String descriptor) {
		this.descriptor = descriptor;
	}
}


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

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

import com.lilithsthrone.game.character.GameCharacter;
import com.lilithsthrone.game.character.effects.StatusEffect;
import com.lilithsthrone.game.combat.CombatMove;
import com.lilithsthrone.game.combat.Spell;
import com.lilithsthrone.game.inventory.InventorySlot;
import com.lilithsthrone.game.inventory.weapon.AbstractWeapon;
import com.lilithsthrone.utils.Util;

/**
 * @since 0.1.0
 * @version 0.3.5
 * @author Innoxia
 */
public enum ClothingSet {

	ENFORCER("Commanding Enforcer",
			StatusEffect.SET_ENFORCER,
			2,
			Util.newArrayListOfValues(
					InventorySlot.TORSO_UNDER,
					InventorySlot.LEG),
			null,
			null),

	SLUTTY_ENFORCER("Slutty Enforcer",
			StatusEffect.SET_SLUTTY_ENFORCER,
			2,
			Util.newArrayListOfValues(
					InventorySlot.TORSO_UNDER,
					InventorySlot.LEG),
			null,
			null),

	MAID("Hard-working Maid",
			StatusEffect.SET_MAID,
			5,
			Util.newArrayListOfValues(
					InventorySlot.HEAD,
					InventorySlot.TORSO_UNDER,
					InventorySlot.SOCK,
					InventorySlot.FOOT,
					InventorySlot.HAND),
			null,
			null),

	BUTLER("Butler",
			StatusEffect.SET_BUTLER,
			5,
			Util.newArrayListOfValues(
					InventorySlot.TORSO_OVER,
					InventorySlot.TORSO_UNDER,
					InventorySlot.LEG,
					InventorySlot.FOOT,
					InventorySlot.HAND),
			null,
			null),

	WITCH("Witch",
			StatusEffect.SET_WITCH,
			3,
			Util.newArrayListOfValues(
					InventorySlot.TORSO_UNDER,
					InventorySlot.FOOT,
					InventorySlot.HEAD),
			null,
			null),

	SCIENTIST("Brilliant Scientist",
			StatusEffect.SET_SCIENTIST,
			2,
			Util.newArrayListOfValues(
					InventorySlot.TORSO_OVER,
					InventorySlot.EYES),
			null,
			null),

	MILK_MAID("Milk Maid",
			StatusEffect.SET_MILK_MAID,
			2,
			Util.newArrayListOfValues(
					InventorySlot.TORSO_UNDER,
					InventorySlot.HEAD),
			null,
			null),

	BDSM("Locked in Bondage",
			StatusEffect.SET_BDSM,
			4,
			null,
			null,
			null),

	CATTLE("Cattle",
			StatusEffect.SET_CATTLE,
			3,
			Util.newArrayListOfValues(
					InventorySlot.PIERCING_EAR,
					InventorySlot.PIERCING_NOSE,
					InventorySlot.NECK),
			null,
			null),

	GEISHA("Geisha",
			StatusEffect.SET_GEISHA,
			3,
			Util.newArrayListOfValues(
					InventorySlot.HAIR,
					InventorySlot.TORSO_UNDER,
					InventorySlot.FOOT),
			null,
			null), // "We want the /jp/ audience." - Innoxia, probably.

	RONIN("Ronin",
			StatusEffect.SET_RONIN,
			3,
			Util.newArrayListOfValues(
					InventorySlot.TORSO_OVER,
					InventorySlot.TORSO_UNDER,
					InventorySlot.LEG,
					InventorySlot.FOOT),
			null,
			null),

	WEAPON_DAISHO("Daisho",
			StatusEffect.SET_DAISHO,
			2,
			null,
			null,
			null),

	JOLNIR("Jólnir",
			StatusEffect.SET_JOLNIR,
			3,
			Util.newArrayListOfValues(
					InventorySlot.TORSO_OVER,
					InventorySlot.TORSO_UNDER,
					InventorySlot.FOOT,
					InventorySlot.HEAD),
			null,
			null),

	SUN("Solar Power",
			StatusEffect.SET_SUN,
			3,
			null,
			null,
			null),

	SNOWFLAKE("Blizzard",
			StatusEffect.SET_SNOWFLAKE,
			3,
			null,
			null,
			null),

	RAINBOW("Rainbow",
			StatusEffect.SET_RAINBOW,
			2,
			null,
			null,
			null),

	DARK_SIREN("Dark Siren",
			StatusEffect.SET_DARK_SIREN,
			3,
			Util.newArrayListOfValues(
					InventorySlot.TORSO_OVER,
					InventorySlot.NECK,
					InventorySlot.EYES),
			null,
			null),

	LYSSIETH_GUARD("Lyssieth's Guard",
			StatusEffect.SET_LYSSIETH_GUARD,
			4,
			Util.newArrayListOfValues(
					InventorySlot.FOOT,
					InventorySlot.TORSO_OVER,
					InventorySlot.LEG,
					InventorySlot.HEAD),
			null,
			null)

	;

	private String name;
	private int numberRequiredForCompleteSet;
	private List<InventorySlot> blockedSlotsCountingTowardsFullSet;
	private List<CombatMove> combatMoves;
	private List<Spell> spells;
	private StatusEffect associatedStatusEffect;

	private ClothingSet(String name, StatusEffect associatedStatusEffect, int numberRequiredForCompleteSet, List<InventorySlot> blockedSlotsCountingTowardsFullSet, List<CombatMove> combatMoves, List<Spell> spells) {
		this.name = name;
		this.numberRequiredForCompleteSet = numberRequiredForCompleteSet;

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

		this.combatMoves = combatMoves;
		this.spells = spells;
		this.associatedStatusEffect = associatedStatusEffect;
	}

	public boolean isCharacterWearingCompleteSet(GameCharacter target) {
		int setCount = 0;

		for(InventorySlot slot : this.getBlockedSlotsCountingTowardsFullSet()) {
			if(slot.getBodyPartClothingBlock(target) != null) {
				setCount++;
			}
		}

		boolean atLeastOneClothingFound = false;
		for (AbstractClothing c : target.getClothingCurrentlyEquipped()) {
			if (c.getClothingType().getClothingSet() == this) {
				setCount++;
				atLeastOneClothingFound = true;
			}
		}

		int weaponSetCount = 0;
		for(AbstractWeapon weapon : target.getMainWeaponArray()) {
			if(weapon!=null && weapon.getWeaponType().getClothingSet() == this) {
				weaponSetCount++;
				atLeastOneClothingFound = true;
			}
		}
		for(AbstractWeapon weapon : target.getOffhandWeaponArray()) {
			if(weapon!=null && weapon.getWeaponType().getClothingSet() == this) {
				weaponSetCount++;
				atLeastOneClothingFound = true;
			}
		}

		setCount += Math.min(2, weaponSetCount);

		return atLeastOneClothingFound && setCount >= this.getNumberRequiredForCompleteSet();
	}

	public String getName() {
		return name;
	}

	public int getNumberRequiredForCompleteSet() {
		return numberRequiredForCompleteSet;
	}

	public List<CombatMove> getCombatMoves() {
		return combatMoves;
	}

	public List<Spell> getSpells() {
		return spells;
	}

	public StatusEffect getAssociatedStatusEffect() {
		return associatedStatusEffect;
	}

	public List<InventorySlot> getBlockedSlotsCountingTowardsFullSet() {
		return blockedSlotsCountingTowardsFullSet;
	}
}