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

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

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

import com.lilithsthrone.game.character.CharacterUtils;
import com.lilithsthrone.game.character.GameCharacter;
import com.lilithsthrone.game.character.attributes.Attribute;
import com.lilithsthrone.game.dialogue.utils.UtilText;
import com.lilithsthrone.game.inventory.AbstractCoreItem;
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.main.Main;
import com.lilithsthrone.utils.Colour;
import com.lilithsthrone.utils.SvgUtil;
import com.lilithsthrone.utils.Util;
import com.lilithsthrone.utils.XMLSaving;

/**
 * @since 0.2.6
 * @version 0.2.6
 * @author Innoxia
 */
public class Tattoo extends AbstractCoreItem implements XMLSaving {

	private AbstractTattooType type;

	private Colour primaryColour;
	private Colour secondaryColour;
	private Colour tertiaryColour;

	private boolean glowing;

	private TattooWriting writing;

	private TattooCounter counter;

	protected List<ItemEffect> effects;

	protected Map<Attribute, Integer> attributeModifiers;

	private static Map<Colour, String> SVGGlowMap = new HashMap<>();

	public Tattoo(AbstractTattooType type,
			Colour primaryColour,
			Colour secondaryColour,
			Colour tertiaryColour,
			boolean glowing,
			TattooWriting writing,
			TattooCounter counter) {
		super(type.getName(),
				type.getName(),
				type.getPathName(),
				primaryColour,
				Rarity.COMMON,
				null);

		this.type = type;

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

		this.glowing = glowing;

		this.writing = writing;

		this.counter = counter;

		this.effects = new ArrayList<>();

		attributeModifiers = new HashMap<>();
	}

	@Override
	public boolean equals(Object o) {
		if(super.equals(o)) {
			return (o instanceof Tattoo)
					&& ((Tattoo)o).getType()==type
					&& ((Tattoo)o).getPrimaryColour()==primaryColour
					&& ((Tattoo)o).getPrimaryColour()==secondaryColour
					&& ((Tattoo)o).getPrimaryColour()==tertiaryColour
					&& ((Tattoo)o).isGlowing()==glowing
					&& ((Tattoo)o).getWriting().equals(this.getWriting())
					&& ((Tattoo)o).getCounter().equals(this.getCounter())
					&& ((Tattoo)o).getEffects().equals(this.getEffects());
		} else {
			return false;
		}
	}

	@Override
	public int hashCode() {
		int result = super.hashCode();
		result = 31 * result + type.hashCode();
		result = 31 * result + this.getPrimaryColour().hashCode();
		if(this.getSecondaryColour()!=null) {
			result = 31 * result + this.getSecondaryColour().hashCode();
		}
		if(this.getTertiaryColour()!=null) {
			result = 31 * result + this.getTertiaryColour().hashCode();
		}
		result = 31 * result + (this.isGlowing() ? 1 : 0);
		result = 31 * result + this.getWriting().hashCode();
		result = 31 * result + this.getCounter().hashCode();
		result = 31 * result + this.getEffects().hashCode();
		return result;
	}

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

		CharacterUtils.addAttribute(doc, element, "id", TattooType.getIdFromTattooType(getType()));

		CharacterUtils.addAttribute(doc, element, "name", this.getName());

		CharacterUtils.addAttribute(doc, element, "primaryColour", this.getPrimaryColour().toString());

		if(this.getSecondaryColour()!=null) {
			CharacterUtils.addAttribute(doc, element, "secondaryColour", this.getSecondaryColour().toString());
		}

		if(this.getTertiaryColour()!=null) {
			CharacterUtils.addAttribute(doc, element, "tertiaryColour", this.getTertiaryColour().toString());
		}

		CharacterUtils.addAttribute(doc, element, "glowing", String.valueOf(this.isGlowing()));

		if(this.getWriting()!=null) {
			this.getWriting().saveAsXML(element, doc);
		}

		if(this.getCounter()!=null) {
			this.getCounter().saveAsXML(element, doc);
		}

		Element innerElement = doc.createElement("effects");
		element.appendChild(innerElement);

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

		return element;
	}

	public static Tattoo loadFromXML(Element parentElement, Document doc) {
		try {
			TattooWriting writing = null;
			if(parentElement.getElementsByTagName("tattooWriting").item(0)!=null) {
				writing = TattooWriting.loadFromXML((Element) parentElement.getElementsByTagName("tattooWriting").item(0), doc);
			}

			TattooCounter counter = null;
			if(parentElement.getElementsByTagName("tattooCounter").item(0)!=null) {
				counter = TattooCounter.loadFromXML((Element) parentElement.getElementsByTagName("tattooCounter").item(0), doc);
			}
			Tattoo tat = new Tattoo(
					TattooType.getTattooTypeFromId(parentElement.getAttribute("id")),
					Colour.valueOf(parentElement.getAttribute("primaryColour")),
					parentElement.getAttribute("secondaryColour").isEmpty()?null:Colour.valueOf(parentElement.getAttribute("secondaryColour")),
					parentElement.getAttribute("tertiaryColour").isEmpty()?null:Colour.valueOf(parentElement.getAttribute("tertiaryColour")),
					Boolean.valueOf(parentElement.getAttribute("glowing")),
					writing,
					counter);

			try {
				tat.setName(parentElement.getAttribute("name"));
			} catch(Exception ex) {
			}

			Element element = (Element)parentElement.getElementsByTagName("effects").item(0);
			if(element!=null) {
				NodeList nodeList = element.getElementsByTagName("effect");
				for(int i = 0; i < nodeList.getLength(); i++){
					Element e = ((Element)nodeList.item(i));
					ItemEffect ie = ItemEffect.loadFromXML(e, doc);
					if(ie!=null) {
						tat.addEffect(ie);
					}
				}
			}

			return tat;

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

	@Override
	public String getSVGString() {
		return getSVGImage(Main.game.getPlayer());
	}

	public String getSVGImage(GameCharacter character) {
		if(this.isGlowing()) {
			return getSVGGlow() + "
"+type.getSVGImage(character, primaryColour, secondaryColour, tertiaryColour)+"
";

		} else {
			return type.getSVGImage(character, primaryColour, secondaryColour, tertiaryColour);
		}
	}

	private String getSVGGlow() {
		String stringFromMap = SVGGlowMap.get(this.getPrimaryColour());
		if (stringFromMap!=null) {
			return stringFromMap;

		} else {
			try {
				InputStream is;
				String s;

				is = this.getClass().getResourceAsStream("/com/lilithsthrone/res/tattoos/glow.svg");
				s = Util.inputStreamToString(is);
				is.close();

				s = SvgUtil.colourReplacement("tattooGlow"+this.getPrimaryColour().toString(), this.getPrimaryColour(), this.getPrimaryColour(), this.getPrimaryColour(), s);

				SVGGlowMap.put(this.getPrimaryColour(), s);

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

		return "";
	}

	public Rarity getRarity() {
		if(effects.isEmpty()) {
			return Rarity.COMMON;

		} else if(effects.size()<=1) {
			return Rarity.UNCOMMON;

		} else if(effects.size()<=3) {
			return Rarity.RARE;

		} else if(effects.size()<=5) {
			return Rarity.EPIC;

		} else {
			return Rarity.LEGENDARY;
		}
	}

	public String getDisplayName(boolean withRarityColour) {

		if(!this.getName().replaceAll("\u00A0"," ").equalsIgnoreCase(this.getType().getName().replaceAll("\u00A0"," "))) { // If this tattoo has a custom name, just display that:
			return (withRarityColour
						? " " + getName() + ""
						: getName());
//					+" tattoo";
		}

		return Util.capitaliseSentence(this.getPrimaryColour().getName()) + " "
				+ (withRarityColour
					?" "
						+ (this.getType()==TattooType.NONE
							?"tattoo"
							:this.getName() + " tattoo")
						+ ""
					: this.getName()+" tattoo")
				+(!this.getEffects().isEmpty()
						? " "+getEnchantmentPostfix(withRarityColour, "b")
						: "");
	}

	public String getEnchantmentPostfix(boolean coloured, String tag) {
		if(!this.getEffects().isEmpty()) {
			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) {
					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 Attribute getCoreEnchantment() {
		Attribute att = Attribute.MAJOR_PHYSIQUE;
		int max = 0;
		for(Entry<Attribute, Integer> entry : getAttributeModifiers().entrySet()) {
			if(entry.getValue() > max) {
				att = entry.getKey();
				max = entry.getValue();
			}
		}

		return att;
	}

	public boolean isBadEnchantment() {
		return this.getEffects().stream().mapToInt(e -> (e.getPrimaryModifier() == TFModifier.CLOTHING_ATTRIBUTE || e.getPrimaryModifier() == TFModifier.CLOTHING_MAJOR_ATTRIBUTE)?e.getPotency().getClothingBonusValue():0).sum()<0;
//		return this.getEffects().stream().anyMatch(e -> e.getPrimaryModifier() == TFModifier.CLOTHING_ATTRIBUTE && e.getPotency().isNegative());
	}

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

		for(ItemEffect ie : getEffects()) {
			if(ie.getPrimaryModifier() == TFModifier.CLOTHING_ATTRIBUTE) {
				if(attributeModifiers.containsKey(ie.getSecondaryModifier().getAssociatedAttribute())) {
					attributeModifiers.put(ie.getSecondaryModifier().getAssociatedAttribute(), attributeModifiers.get(ie.getSecondaryModifier().getAssociatedAttribute()) + ie.getPotency().getClothingBonusValue());
				} else {
					attributeModifiers.put(ie.getSecondaryModifier().getAssociatedAttribute(), ie.getPotency().getClothingBonusValue());
				}
			}
		}

		return attributeModifiers;
	}

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

	public AbstractTattooType getType() {
		return type;
	}

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

	public void setEffects(List<ItemEffect> effects) {
		this.effects = effects;
	}

	public void addEffect(ItemEffect effect) {
		effects.add(effect);
	}

	public Colour getPrimaryColour() {
		return primaryColour;
	}

	public Colour getSecondaryColour() {
		return secondaryColour;
	}

	public Colour getTertiaryColour() {
		return tertiaryColour;
	}

	public boolean isGlowing() {
		return glowing;
	}

	public TattooWriting getWriting() {
		return writing;
	}

	/**
	 * For examples.
	 */
	public String getFormattedWritingOutput(String input) {
		return (this.getWriting().getStyles().contains(TattooWritingStyle.BOLD)?"":"")
				+ (this.getWriting().getStyles().contains(TattooWritingStyle.ITALICISED)?"":"")
				+""+(getWriting().isGlow()?UtilText.applyGlow(input, getWriting().getColour()):input)+""
				+(this.getWriting().getStyles().contains(TattooWritingStyle.BOLD)?"":"")
				+ (this.getWriting().getStyles().contains(TattooWritingStyle.ITALICISED)?"":"");
	}

	public String getFormattedWritingOutput() {
		return (this.getWriting().getStyles().contains(TattooWritingStyle.BOLD)?"":"")
				+ (this.getWriting().getStyles().contains(TattooWritingStyle.ITALICISED)?"":"")
				+""+(getWriting().isGlow()?UtilText.applyGlow(getWriting().getText(), getWriting().getColour()):getWriting().getText())+""
				+(this.getWriting().getStyles().contains(TattooWritingStyle.BOLD)?"":"")
				+ (this.getWriting().getStyles().contains(TattooWritingStyle.ITALICISED)?"":"");
	}

	public TattooCounter getCounter() {
		return counter;
	}

	/**
	 * For examples.
	 */
	public String getFormattedCounterOutput(int input) {
		return ""+(getCounter().isGlow()?UtilText.applyGlow(getCounter().getCountType().convertInt(input), getCounter().getColour()):getCounter().getCountType().convertInt(input))+"";
	}

	public String getFormattedCounterOutput(GameCharacter equippedToCharacter) {
		String convertedInt = getCounter().getCountType().convertInt(getCounter().getType().getCount(equippedToCharacter));
		return ""+(getCounter().isGlow()?UtilText.applyGlow(convertedInt, getCounter().getColour()):convertedInt)+"";
	}

	public void setType(AbstractTattooType type) {
		this.type = type;
	}

	public void setPrimaryColour(Colour primaryColour) {
		this.primaryColour = primaryColour;
	}

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

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

	public void setGlowing(boolean glowing) {
		this.glowing = glowing;
	}

	@Override
	public String getDescription() {
		return this.getType().getDescription();
	}

	@Override
	public int getValue() {
		return this.getType().getValue();
	}

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

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

	@Override
	public AbstractTattooType getEnchantmentItemType(List<ItemEffect> effects) {
		return this.getType();
	}

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


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

import com.lilithsthrone.utils.Util;

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

	NUMBERS("numbers") {
		@Override
		public String convertInt(int input) {
			return String.valueOf(input);
		}
	},
	TALLY("tally markings") {
		@Override
		public String convertInt(int input) {
			return Util.intToTally(input, 50);
		}
	},
	NUMERALS("Roman numerals") {
		@Override
		public String convertInt(int input) {
			return Util.intToNumerals(input);
		}
	},
	WRITTEN("written out") {
		@Override
		public String convertInt(int input) {
			return Util.intToString(input);
		}
	};

	private String name;

	private TattooCountType(String name) {
		this.name = name;
	}

	public abstract String convertInt(int input);

	public String getName() {
		return name;
	}
}

repo_name: klorpa/Pedo

license: unknown


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

import java.util.List;

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

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

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

	private TattooCounterType type;
	private TattooCountType countType;
	private Colour colour;
	private boolean glow;

	public TattooCounter(TattooCounterType type, TattooCountType countType, Colour colour, boolean glow) {
		this.type = type;
		this.countType = countType;
		this.colour = colour;
		this.glow = glow;
	}

	public static List<Colour> getAvailableColours() {
		return ColourListPresets.ALL;
	}

	@Override
	public boolean equals(Object o) {
		if(super.equals(o)) {
			return (o instanceof TattooCounter)
					&& ((TattooCounter)o).getType().equals(this.getType())
					&& ((TattooCounter)o).getCountType().equals(this.getCountType())
					&& ((TattooCounter)o).getColour().equals(this.getColour())
					&& ((TattooCounter)o).isGlow()==glow;
		} else {
			return false;
		}
	}

	@Override
	public int hashCode() {
		int result = 17;
		result = 31 * result + getType().hashCode();
		result = 31 * result + getCountType().hashCode();
		result = 31 * result + getColour().hashCode();
		result = 31 * result + (isGlow() ? 1 : 0);
		return result;
	}

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

		CharacterUtils.addAttribute(doc, element, "type", this.getType().toString());
		CharacterUtils.addAttribute(doc, element, "countType", this.getCountType().toString());
		CharacterUtils.addAttribute(doc, element, "colour", this.getColour().toString());
		CharacterUtils.addAttribute(doc, element, "glow", String.valueOf(this.isGlow()));

		return element;
	}

	public static TattooCounter loadFromXML(Element parentElement, Document doc) {
		try {
			return new TattooCounter(
					TattooCounterType.valueOf(parentElement.getAttribute("type")),
					TattooCountType.valueOf(parentElement.getAttribute("countType")),
					Colour.valueOf(parentElement.getAttribute("colour")),
					Boolean.valueOf(parentElement.getAttribute("glow")));

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

	public TattooCounterType getType() {
		return type;
	}

	public TattooCountType getCountType() {
		return countType;
	}

	public Colour getColour() {
		return colour;
	}

	public boolean isGlow() {
		return glow;
	}

	public void setType(TattooCounterType type) {
		this.type = type;
	}

	public void setCountType(TattooCountType countType) {
		this.countType = countType;
	}

	public void setColour(Colour colour) {
		this.colour = colour;
	}

	public void setGlow(boolean glow) {
		this.glow = glow;
	}

}


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

import com.lilithsthrone.game.PropertyValue;
import com.lilithsthrone.game.character.GameCharacter;
import com.lilithsthrone.game.sex.SexAreaOrifice;
import com.lilithsthrone.game.sex.SexAreaPenetration;
import com.lilithsthrone.game.sex.SexParticipantType;
import com.lilithsthrone.game.sex.SexType;
import com.lilithsthrone.main.Main;
import com.lilithsthrone.utils.Units;

/**
 * @since 0.2.6
 * @version 0.2.10
 * @author Innoxia
 */
public enum TattooCounterType {

	NONE("none", "Do not add a tracker to this tattoo.") {
		@Override
		public int getCount(GameCharacter bearer) {
			return 0;
		}
	},

	SEX_SUB("sub sex", "Keeps a count of how many times the bearer has had submissive sex.") {
		@Override
		public int getCount(GameCharacter bearer) {
			return bearer.getTotalSexAsSubCount();
		}
	},

	SEX_DOM("dom sex", "Keeps a count of how many times the bearer has had dominant sex.") {
		@Override
		public int getCount(GameCharacter bearer) {
			return bearer.getTotalSexAsDomCount();
		}
	},

	UNIQUE_SEX_PARTNERS("unique sex partners", "Keeps a count of how many different people the bearer has had sex with.") {
		@Override
		public int getCount(GameCharacter bearer) {
			return bearer.getUniqueSexPartnerCount();
		}
	},

	CUM_GIVEN("total creampies given", "Keeps a count of how many times the bearer has cummed inside someone.") {
		@Override
		public int getCount(GameCharacter bearer) {
			return bearer.getTotalCumCount(true, false);
		}
	},

	CUM_GIVEN_PUSSY("pussy creampies given", "Keeps a count of how many times the bearer has cummed into someone's pussy.") {
		@Override
		public int getCount(GameCharacter bearer) {
			return bearer.getTotalCumCountInArea(SexAreaOrifice.VAGINA, true, false);
		}
	},

	CUM_GIVEN_ANUS("anal creampies given", "Keeps a count of how many times the bearer has cummed into someone's ass.") {
		@Override
		public int getCount(GameCharacter bearer) {
			return bearer.getTotalCumCountInArea(SexAreaOrifice.ANUS, true, false);
		}
	},

	CUM_GIVEN_ORAL("oral loads given", "Keeps a count of how many times the bearer has cummed down someone's throat.") {
		@Override
		public int getCount(GameCharacter bearer) {
			return bearer.getTotalCumCountInArea(SexAreaOrifice.MOUTH, true, false);
		}
	},

	CUM_GIVEN_FEET("footjob climaxes received", "Keeps a count of how many times the bearer has cummed onto their partner's feet.") {
		@Override
		public int getCount(GameCharacter bearer) {
			return bearer.getTotalCumCountInArea(SexAreaPenetration.FOOT, true, false);
		}
	},

	CUM_TAKEN("total creampies received", "Keeps a count of how many times the bearer has taken a load of cum in their orifices.") {
		@Override
		public int getCount(GameCharacter bearer) {
			return bearer.getTotalCumCount(false, true);
		}
	},

	CUM_TAKEN_PUSSY("pussy creampies received", "Keeps a count of how many times the bearer has taken a load of cum in their pussy.") {
		@Override
		public int getCount(GameCharacter bearer) {
			return bearer.getTotalCumCountInArea(SexAreaOrifice.VAGINA, false, true);
		}
	},

	CUM_TAKEN_ANUS("anal creampies received", "Keeps a count of how many times the bearer has taken a load of cum in their ass.") {
		@Override
		public int getCount(GameCharacter bearer) {
			return bearer.getTotalCumCountInArea(SexAreaOrifice.ANUS, false, true);
		}
	},

	CUM_TAKEN_ORAL("loads swallowed", "Keeps a count of how many times the bearer has swallowed a load of cum.") {
		@Override
		public int getCount(GameCharacter bearer) {
			return bearer.getTotalCumCountInArea(SexAreaOrifice.MOUTH, false, true);
		}
	},

	CUM_TAKEN_FEET("footjob climaxes given", "Keeps a count of how many times the bearer has had their partner cum over their feet.") {
		@Override
		public int getCount(GameCharacter bearer) {
			return bearer.getTotalCumCountInArea(SexAreaPenetration.FOOT, false, true);
		}
	},

	VIRGINITIES_TAKEN_TOTAL("total deflowerments", "Keeps a count of how many virginities of all types (vaginal, anal, oral, nipple, urethral) the bearer has taken.") {
		@Override
		public int getCount(GameCharacter bearer) {
			int count = 0;

			for(GameCharacter character : Main.game.getAllNPCs()) {
				if(!character.equals(bearer)) {
					for(SexAreaOrifice orifice : SexAreaOrifice.values()) {
						if(orifice.isInternalOrifice()) {
							for(SexAreaPenetration pen : SexAreaPenetration.values()) {
								if(pen.isTakesVirginity()
										&& character.getVirginityLoss(new SexType(SexParticipantType.NORMAL, orifice, pen))!=null
										&& character.getVirginityLoss(new SexType(SexParticipantType.NORMAL, orifice, pen)).getKey().equals(bearer.getId())) {
									count++;
									break;
								}
							}
						}
					}
				}
			}

			GameCharacter character = Main.game.getPlayer();
			if(!character.equals(bearer)) {
				for(SexAreaOrifice orifice : SexAreaOrifice.values()) {
					if(orifice.isInternalOrifice()) {
						for(SexAreaPenetration pen : SexAreaPenetration.values()) {
							if(pen.isTakesVirginity()
									&& character.getVirginityLoss(new SexType(SexParticipantType.NORMAL, orifice, pen))!=null
									&& character.getVirginityLoss(new SexType(SexParticipantType.NORMAL, orifice, pen)).getKey().equals(bearer.getId())) {
								count++;
								break;
							}
						}
					}
				}
			}

			return count;
		}
	},

	VIRGINITIES_TAKEN_VAGINAL("vaginal deflowerments", "Keeps a count of how many vaginal virginities the bearer has taken.") {
		@Override
		public int getCount(GameCharacter bearer) {
			int count = 0;

			for(GameCharacter character : Main.game.getAllNPCs()) {
				if(!character.equals(bearer)) {
					for(SexAreaPenetration pen : SexAreaPenetration.values()) {
						if(pen.isTakesVirginity()
								&& character.getVirginityLoss(new SexType(SexParticipantType.NORMAL, SexAreaOrifice.VAGINA, pen))!=null
								&& character.getVirginityLoss(new SexType(SexParticipantType.NORMAL, SexAreaOrifice.VAGINA, pen)).getKey().equals(bearer.getId())) {
							count++;
							break;
						}
					}
				}
			}

			GameCharacter character = Main.game.getPlayer();
			if(!character.equals(bearer)) {
				for(SexAreaPenetration pen : SexAreaPenetration.values()) {
					if(pen.isTakesVirginity()
							&& character.getVirginityLoss(new SexType(SexParticipantType.NORMAL, SexAreaOrifice.VAGINA, pen))!=null
							&& character.getVirginityLoss(new SexType(SexParticipantType.NORMAL, SexAreaOrifice.VAGINA, pen)).getKey().equals(bearer.getId())) {
						count++;
						break;
					}
				}
			}

			return count;
		}
	},

	VIRGINITIES_TAKEN_ANAL("anal deflowerments", "Keeps a count of how many anal virginities the bearer has taken.") {
		@Override
		public int getCount(GameCharacter bearer) {
			int count = 0;

			for(GameCharacter character : Main.game.getAllNPCs()) {
				if(!character.equals(bearer)) {
					for(SexAreaPenetration pen : SexAreaPenetration.values()) {
						if(pen.isTakesVirginity()
								&& character.getVirginityLoss(new SexType(SexParticipantType.NORMAL, SexAreaOrifice.ANUS, pen))!=null
								&& character.getVirginityLoss(new SexType(SexParticipantType.NORMAL, SexAreaOrifice.ANUS, pen)).getKey().equals(bearer.getId())) {
							count++;
							break;
						}
					}
				}
			}

			GameCharacter character = Main.game.getPlayer();
			if(!character.equals(bearer)) {
				for(SexAreaPenetration pen : SexAreaPenetration.values()) {
					if(pen.isTakesVirginity()
							&& character.getVirginityLoss(new SexType(SexParticipantType.NORMAL, SexAreaOrifice.ANUS, pen))!=null
							&& character.getVirginityLoss(new SexType(SexParticipantType.NORMAL, SexAreaOrifice.ANUS, pen)).getKey().equals(bearer.getId())) {
						count++;
						break;
					}
				}
			}

			return count;
		}
	},

	CURRENT_PREGNANCY("litter size", "Counts how many children the bearer is currently pregnant with.") {
		@Override
		public int getCount(GameCharacter bearer) {
			if(bearer.getPregnantLitter()==null) {
				return 0;
			}
			return bearer.getPregnantLitter().getTotalLitterCount();
		}
	},

	PREGNANCY("pregnancy", "Keeps a count of the bearer's completed pregnancies.") {
		@Override
		public int getCount(GameCharacter bearer) {
			return bearer.getLittersBirthed().size();
		}
	},

	IMPREGNATIONS("impregnation", "Keeps a count of the number of times the bearer has impregnated someone.") {
		@Override
		public int getCount(GameCharacter bearer) {
			return bearer.getLittersFathered().size();
		}
	},

	CUM_IN_VAGINA("cum in womb", "Displays how much cum is currently in the bearer's womb (in "+(Main.getProperties().hasValue(PropertyValue.metricFluids)?"mL":"oz")+").") {
		@Override
		public int getCount(GameCharacter bearer) {
			if(Main.getProperties().hasValue(PropertyValue.metricFluids)) {
				return Math.round(bearer.getTotalFluidInArea(SexAreaOrifice.VAGINA));
			} else {
				return Math.round(Units.mlToOz(bearer.getTotalFluidInArea(SexAreaOrifice.VAGINA)));
			}
		}
	},

	CUM_IN_ASS("cum in ass", "Displays how much cum is currently in the bearer's ass (in "+(Main.getProperties().hasValue(PropertyValue.metricFluids)?"mL":"oz")+").") {
		@Override
		public int getCount(GameCharacter bearer) {
			if(Main.getProperties().hasValue(PropertyValue.metricFluids)) {
				return Math.round(bearer.getTotalFluidInArea(SexAreaOrifice.ANUS));
			} else {
				return Math.round(Units.mlToOz(bearer.getTotalFluidInArea(SexAreaOrifice.ANUS)));
			}
		}
	},

	CUM_IN_STOMACH("cum in stomach", "Displays how much cum is currently in the bearer's stomach (in "+(Main.getProperties().hasValue(PropertyValue.metricFluids)?"mL":"oz")+").") {
		@Override
		public int getCount(GameCharacter bearer) {
			if(Main.getProperties().hasValue(PropertyValue.metricFluids)) {
				return Math.round(bearer.getTotalFluidInArea(SexAreaOrifice.MOUTH));
			} else {
				return Math.round(Units.mlToOz(bearer.getTotalFluidInArea(SexAreaOrifice.MOUTH)));
			}
		}
	},

	VALUE_AS_SLAVE("value as slave", "Displays how much the bearer is worth as a slave.") {
		@Override
		public int getCount(GameCharacter bearer) {
			return bearer.getValueAsSlave(false);
		}
	}
	;

	private String name;
	private String description;

	private TattooCounterType(String name, String description) {
		this.name = name;
		this.description = description;
	}

	public abstract int getCount(GameCharacter bearer);

	public String getName() {
		return name;
	}

	public String getDescription() {
		return description;
	}
}


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

import java.io.File;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.lilithsthrone.utils.ColourListPresets;
import com.lilithsthrone.utils.Util;

/**
 * @since 0.2.6
 * @version 0.2.8
 * @author Innoxia
 */
public class TattooType {

	public static AbstractTattooType NONE = new AbstractTattooType(
			"none",
			"none",
			"This tattoo has no particular pattern, and simply displays either writing or a counter.",
			ColourListPresets.JUST_GREY,
			null,
			null,
			null);

	public static AbstractTattooType FLOWERS = new AbstractTattooType(
			"flowers",
			"flowers",
			"This tattoo depicts a flowing series of intertwined flowers.",
			ColourListPresets.ALL,
			ColourListPresets.ALL,
			ColourListPresets.ALL,
			null);

	public static AbstractTattooType TRIBAL = new AbstractTattooType(
			"tribal",
			"tribal",
			"A series of flowing lines and intricate patterns.",
			ColourListPresets.ALL,
			null,
			null,
			null);

	public static AbstractTattooType BUTTERFLIES = new AbstractTattooType(
			"butterflies",
			"butterflies",
			"An artistic depiction of a trio of butterflies in mid-flight.",
			ColourListPresets.ALL,
			ColourListPresets.ALL,
			ColourListPresets.ALL,
			null);

	public static AbstractTattooType LINES = new AbstractTattooType(
			"lines",
			"lines",
			"A series of flowing, swirling lines.",
			ColourListPresets.ALL,
			null,
			null,
			null);

	private static Map<AbstractTattooType, String> tattooToIdMap = new HashMap<>();
	private static Map<String, AbstractTattooType> idToTattooMap = new HashMap<>();

	public static AbstractTattooType getTattooTypeFromId(String id) {
//		System.out.print("ID: "+id);
		id = Util.getClosestStringMatch(id, idToTattooMap.keySet());
//		System.out.println("  set to: "+id);
		return idToTattooMap.get(id);
	}

	public static String getIdFromTattooType(AbstractTattooType tattooType) {
		return tattooToIdMap.get(tattooType);
	}

	public static List<AbstractTattooType> getAllTattooTypes() {
		List<AbstractTattooType> allTattoos = new ArrayList<>(tattooToIdMap.keySet());

		allTattoos.sort((t1, t2) -> t1.equals(TattooType.NONE)?-1:(t1.getName().compareTo(t2.getName())));

		return allTattoos;
	}

	static {

		File dir = new File("res/mods");

		if (dir.exists() && dir.isDirectory()) {
			File[] modDirectoryListing = dir.listFiles();
			if (modDirectoryListing != null) {
				for (File modAuthorDirectory : modDirectoryListing) {
					File modAuthorClothingDirectory = new File(modAuthorDirectory.getAbsolutePath()+"/items/tattoos");

					File[] clothingDirectoriesListing = modAuthorClothingDirectory.listFiles();
					if (clothingDirectoriesListing != null) {
						for (File clothingDirectory : clothingDirectoriesListing) {
							if (clothingDirectory.isDirectory()){
								File[] innerDirectoryListing = clothingDirectory.listFiles((path, filename) -> filename.endsWith(".xml"));
								if (innerDirectoryListing != null) {
									for (File innerChild : innerDirectoryListing) {
										try {
											AbstractTattooType ct = new AbstractTattooType(innerChild) {};
											String id = modAuthorDirectory.getName()+"_"+innerChild.getParentFile().getName()+"_"+innerChild.getName().split("\\.")[0];
	//										System.out.println(id);
											tattooToIdMap.put(ct, id);
											idToTattooMap.put(id, ct);
										} catch(Exception ex) {
											System.err.println("Loading modded tattoo failed in 'mod folder tattoos'. File path: "+innerChild.getAbsolutePath());
										}
									}
								}
							}
						}
					}
				}
			}
		}

		// Add in external res tattoos:

		dir = new File("res/tattoos");

		if (dir.exists() && dir.isDirectory()) {
			File[] authorDirectoriesListing = dir.listFiles();
			if (authorDirectoriesListing != null) {
				for (File authorDirectory : authorDirectoriesListing) {
					if (authorDirectory.isDirectory()){
						for (File clothingDirectory : authorDirectory.listFiles()) {
							if (clothingDirectory.isDirectory()){
								File[] innerDirectoryListing = clothingDirectory.listFiles((path, filename) -> filename.endsWith(".xml"));
								if (innerDirectoryListing != null) {
									for (File innerChild : innerDirectoryListing) {
										try {
											AbstractTattooType ct = new AbstractTattooType(innerChild) {};
											String id = authorDirectory.getName()+"_"+innerChild.getParentFile().getName()+"_"+innerChild.getName().split("\\.")[0];
											tattooToIdMap.put(ct, id);
											idToTattooMap.put(id, ct);
										} catch(Exception ex) {
											System.err.println("Loading modded tattoo failed in 'external res tattoos'. File path: "+innerChild.getAbsolutePath());
										}
									}
								}
							}
						}
					}
				}
			}
		}

		Field[] fields = TattooType.class.getFields();

		for(Field f : fields){

			if (AbstractTattooType.class.isAssignableFrom(f.getType())) {

				AbstractTattooType ct;
				try {
					ct = ((AbstractTattooType) f.get(null));

					// I feel like this is stupid :thinking:
					tattooToIdMap.put(ct, f.getName());
					idToTattooMap.put(f.getName(), ct);

				} catch (IllegalArgumentException | IllegalAccessException e) {
					e.printStackTrace();
				}

			}
		}
	}

}


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

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

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

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

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

	private String text;
	private Colour colour;
	private boolean glow;
	private List<TattooWritingStyle> styles;

	public TattooWriting(String text, Colour colour, boolean glow, TattooWritingStyle... styles) {
		this.text = text;
		this.colour = colour;
		this.glow = glow;
		this.styles = new ArrayList<>();
		Collections.addAll(this.styles, styles);
	}

	public static List<Colour> getAvailableColours() {
		return ColourListPresets.ALL;
	}

	@Override
	public boolean equals(Object o) {
		if(super.equals(o)) {
			return (o instanceof TattooWriting)
					&& ((TattooWriting)o).getText().equals(this.getText())
					&& ((TattooWriting)o).getColour().equals(this.getColour())
					&& ((TattooWriting)o).isGlow()==glow
					&& ((TattooWriting)o).getStyles().equals(this.getStyles());
		} else {
			return false;
		}
	}

	@Override
	public int hashCode() {
		int result = 17;
		result = 31 * result + getText().hashCode();
		result = 31 * result + getColour().hashCode();
		result = 31 * result + (isGlow() ? 1 : 0);
		result = 31 * result + getStyles().hashCode();
		return result;
	}

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

		CharacterUtils.addAttribute(doc, element, "colour", this.getColour().toString());
		CharacterUtils.addAttribute(doc, element, "glow", String.valueOf(this.isGlow()));

		element.appendChild(doc.createCDATASection(this.getText().trim()));

		Element innerElement = doc.createElement("styles");
		element.appendChild(innerElement);
		for(TattooWritingStyle style : this.getStyles()) {
			Element styleElement = doc.createElement("style");
			innerElement.appendChild(styleElement);
			CharacterUtils.addAttribute(doc, styleElement, "value", style.toString());
		}

		return element;
	}

	public static TattooWriting loadFromXML(Element parentElement, Document doc) {
		try {
			List<TattooWritingStyle> importedStyles = new ArrayList<>();
			try {
				NodeList stylesList = ((Element) parentElement.getElementsByTagName("styles").item(0)).getElementsByTagName("style");
				for(int i=0; i<stylesList.getLength(); i++){
					Element e = ((Element)stylesList.item(i));

					TattooWritingStyle style = TattooWritingStyle.valueOf(e.getAttribute("value"));
					importedStyles.add(style);
				}
			} catch(Exception ex) {
			}

			String text = parentElement.getTextContent();

			TattooWriting tw = new TattooWriting(text.trim(),
					Colour.valueOf(parentElement.getAttribute("colour")),
					Boolean.valueOf(parentElement.getAttribute("glow")));

			tw.styles = importedStyles;

			return tw;

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

	public void setText(String text) {
		this.text = text;
	}

	public String getText() {
		return text;
	}

	public Colour getColour() {
		return colour;
	}

	public boolean isGlow() {
		return glow;
	}

	public List<TattooWritingStyle> getStyles() {
		return styles;
	}

	public void addStyle(TattooWritingStyle style) {
		this.styles.add(style);
	}

	public void removeStyle(TattooWritingStyle style) {
		this.styles.remove(style);
	}

	public void setColour(Colour colour) {
		this.colour = colour;
	}

	public void setGlow(boolean glow) {
		this.glow = glow;
	}

}


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

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

	ITALICISED("italicised"),

	BOLD("bold");

	private String name;

	private TattooWritingStyle(String name) {
		this.name = name;
	}

	public String getName() {
		return name;
	}
}