code:
package com.lilithsthrone.game.character;

import java.time.LocalDateTime;
import java.time.Month;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

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

import com.lilithsthrone.game.character.gender.PronounType;
import com.lilithsthrone.game.character.npc.NPC;
import com.lilithsthrone.game.character.npc.misc.GenericFemaleNPC;
import com.lilithsthrone.game.character.persona.Relationship;
import com.lilithsthrone.game.character.race.Subspecies;
import com.lilithsthrone.main.Main;
import com.lilithsthrone.utils.Colour;
import com.lilithsthrone.utils.Util;
import com.lilithsthrone.utils.XMLSaving;

/**
 * @since 0.1.62
 * @version 0.3.5.5
 * @author Innoxia, orvail
 */
public class Litter implements XMLSaving {

	private LocalDateTime conceptionDate;
	private LocalDateTime birthDate;

	private String motherId;
	private String fatherId;

	private int sonsMother;
	private int daughtersMother;
	private int sonsFather;
	private int daughtersFather;

	private List<String> offspring;

	private String birthedDescription;

	private Subspecies motherRace;
	private Subspecies fatherRace;

	public Litter(LocalDateTime conceptionDate, LocalDateTime birthDate, GameCharacter mother, GameCharacter father, List<NPC> offspring) {
		this.conceptionDate = LocalDateTime.of(conceptionDate.getYear(), conceptionDate.getMonth(), conceptionDate.getDayOfMonth(), 12, 0);
		this.birthDate = LocalDateTime.of(birthDate.getYear(), birthDate.getMonth(), birthDate.getDayOfMonth(), 12, 0);

		motherId = mother.getId();
		motherRace = mother.getSubspecies();
		if(father!=null) {
			fatherId = father.getId();
			fatherRace = father.getSubspecies();
		} else {
			fatherId = "";
			fatherRace = null;
		}

		sonsMother = 0;
		daughtersMother = 0;
		sonsFather = 0;
		daughtersFather = 0;
		this.offspring = new ArrayList<>();

		for(NPC npc : offspring) {
			this.offspring.add(npc.getId());
			if(npc.isFeminine()) {
				if(npc.isTakesAfterMother()) {
					daughtersMother++;
				} else {
					daughtersFather++;
				}
			} else {
				if(npc.isTakesAfterMother()) {
					sonsMother++;
				} else {
					sonsFather++;
				}
			}
		}

		generateBirthedDescription();
	}

	public Litter(LocalDateTime conceptionDate, LocalDateTime birthDate,
			String motherId, String fatherId,
			int sonsMother, int daughtersMother, int sonsFather, int daughtersFather,
			List<String> offspring,
			Subspecies motherRace, Subspecies fatherRace, String birthedDescription) {

		this.conceptionDate = LocalDateTime.of(conceptionDate.getYear(), conceptionDate.getMonth(), conceptionDate.getDayOfMonth(), 12, 0);
		this.birthDate = LocalDateTime.of(birthDate.getYear(), birthDate.getMonth(), birthDate.getDayOfMonth(), 12, 0);

		this.motherId = motherId;
		this.fatherId = fatherId;

		this.sonsMother = sonsMother;
		this.daughtersMother = daughtersMother;
		this.sonsFather = sonsFather;
		this.daughtersFather = daughtersFather;

		this.offspring = offspring;
		this.motherRace = motherRace;
		this.fatherRace = fatherRace;

		this.birthedDescription = birthedDescription;
	}

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

//		CharacterUtils.addAttribute(doc, element, "dayOfConception", String.valueOf(this.getDayOfConception()));
//		CharacterUtils.addAttribute(doc, element, "dayOfBirth", String.valueOf(this.getDayOfBirth()));

		CharacterUtils.createXMLElementWithValue(doc, element, "yearOfBirth", String.valueOf(this.getBirthDate().getYear()));
		CharacterUtils.createXMLElementWithValue(doc, element, "monthOfBirth", this.getBirthDate().getMonth().toString());
		CharacterUtils.createXMLElementWithValue(doc, element, "dayOfBirth", String.valueOf(this.getBirthDate().getDayOfMonth()));

		CharacterUtils.createXMLElementWithValue(doc, element, "yearOfConception", String.valueOf(this.getConceptionDate().getYear()));
		CharacterUtils.createXMLElementWithValue(doc, element, "monthOfConception", this.getConceptionDate().getMonth().toString());
		CharacterUtils.createXMLElementWithValue(doc, element, "dayOfConception", String.valueOf(this.getConceptionDate().getDayOfMonth()));

		CharacterUtils.addAttribute(doc, element, "motherId", this.getMotherId());
		CharacterUtils.addAttribute(doc, element, "fatherId", this.getFatherId());

		CharacterUtils.addAttribute(doc, element, "sonsMother", String.valueOf(this.getSonsFromMother()));
		CharacterUtils.addAttribute(doc, element, "daughtersMother", String.valueOf(this.getDaughtersFromMother()));
		CharacterUtils.addAttribute(doc, element, "sonsFather", String.valueOf(this.getSonsFromFather()));
		CharacterUtils.addAttribute(doc, element, "daughtersFather", String.valueOf(this.getDaughtersFromFather()));

		CharacterUtils.addAttribute(doc, element, "motherRace", String.valueOf(this.getMotherRace()));
		CharacterUtils.addAttribute(doc, element, "fatherRace", String.valueOf(this.getFatherRace()));

		CharacterUtils.addAttribute(doc, element, "birthedDescription", this.getBirthedDescription());

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

		for(String offspring : this.getOffspring()) {

			element = doc.createElement("offspring");
			innerElement.appendChild(element);

			CharacterUtils.addAttribute(doc, element, "id", offspring);
		}

		return element;
	}

	public static Litter loadFromXML(Element parentElement, Document doc) {
		List<String> offspring = new ArrayList<>();

		Element element = (Element) parentElement.getElementsByTagName("offspringList").item(0);
		NodeList offSpringList = element.getElementsByTagName("offspring");
		for(int i = 0; i < offSpringList.getLength(); i++){
			Element e = ((Element)offSpringList.item(i));

			offspring.add(e.getAttribute("id"));
		}

		Subspecies motherRace = Subspecies.HUMAN;
		Subspecies fatherRace = Subspecies.HUMAN;
		try {
			motherRace = Subspecies.valueOf(parentElement.getAttribute("motherRace"));
			fatherRace = Subspecies.valueOf(parentElement.getAttribute("fatherRace"));
		} catch(Exception ex) {
		}

		LocalDateTime loadedConceptionDate;
		LocalDateTime loadedBirthDate;

		if(parentElement.getElementsByTagName("dayOfConception").getLength()>0) {

			int day = Integer.valueOf(((Element)parentElement.getElementsByTagName("dayOfConception").item(0)).getAttribute("value"));
			Month month = Month.valueOf(((Element)parentElement.getElementsByTagName("monthOfConception").item(0)).getAttribute("value"));
			int year = Integer.valueOf(((Element)parentElement.getElementsByTagName("yearOfConception").item(0)).getAttribute("value"));

			loadedConceptionDate = LocalDateTime.of(year, month, day, 12, 0);

			day = Integer.valueOf(((Element)parentElement.getElementsByTagName("dayOfBirth").item(0)).getAttribute("value"));
			month = Month.valueOf(((Element)parentElement.getElementsByTagName("monthOfBirth").item(0)).getAttribute("value"));
			year = Integer.valueOf(((Element)parentElement.getElementsByTagName("yearOfBirth").item(0)).getAttribute("value"));

			loadedBirthDate = LocalDateTime.of(year, month, day, 12, 0);

		} else {
			int conceptionDay = Integer.valueOf(parentElement.getAttribute("dayOfConception"));
			loadedConceptionDate = Main.game.getStartingDate().plusDays(conceptionDay);

			int birthDay = Integer.valueOf(parentElement.getAttribute("dayOfBirth"));
			loadedBirthDate = Main.game.getStartingDate().plusDays(birthDay);
		}

		String birthedDescription = "";
		try {
			birthedDescription = parentElement.getAttribute("birthedDescription");
		} catch(Exception ex) {
		}

		Litter litter = new Litter(
				loadedConceptionDate,
				loadedBirthDate,
				parentElement.getAttribute("motherId"),
				parentElement.getAttribute("fatherId"),
				Integer.valueOf(parentElement.getAttribute("sonsMother")),
				Integer.valueOf(parentElement.getAttribute("daughtersMother")),
				Integer.valueOf(parentElement.getAttribute("sonsFather")),
				Integer.valueOf(parentElement.getAttribute("daughtersFather")),
				offspring,
				motherRace,
				fatherRace,
				birthedDescription);

//		if(birthedDescription.isEmpty()) {
//			litter.generateBirthedDescription();
//		}

		return litter;
	}

	public boolean isSelfImpregnation() {
		return this.getMotherId().equals(this.getFatherId());
	}

	public LocalDateTime getConceptionDate() {
		return conceptionDate;
	}

	public LocalDateTime getBirthDate() {
		return birthDate;
	}

	public void setBirthDate(LocalDateTime birthDate) {
		this.birthDate = birthDate;
	}

	public String getMotherId() {
		return motherId;
	}

	public String getFatherId() {
		return fatherId;
	}

	public GameCharacter getMother() {
		try {
			return Main.game.getNPCById(motherId);
		} catch (Exception e) {
			if(!motherId.equals("NOT_SET")) {
				Util.logGetNpcByIdError("Litter.getMother()", motherId);
			}
			return Main.game.getNpc(GenericFemaleNPC.class);
		}
	}

	public boolean isMotherId(String motherId) {
		return this.motherId.equals(motherId);
	}

	public GameCharacter getFather() {
		try {
			return Main.game.getNPCById(fatherId);
		} catch (Exception e) {
			return null;
		}
	}

	public String getFatherName() {
		if(getFather()==null) {
			return "someone";
		}
		return getFather().getName();
	}

	public boolean isFatherId(String fatherId) {
		return this.fatherId.equals(fatherId);
	}

	public Set<GameCharacter> getOffspringCharacters() {
		HashSet<GameCharacter> result = new HashSet<>();
		offspring.stream().map(x -> {
			try {
				return Main.game.getNPCById(x);
			} catch (Exception e) {
				return null;
			}
		}).filter(Objects::nonNull).forEach(result::add);
		return result;
	}

	public List<String> getOffspring() {
		return offspring;
	}

	public int getSonsFromMother() {
		return sonsMother;
	}

	public int getDaughtersFromMother() {
		return daughtersMother;
	}

	public int getSonsFromFather() {
		return sonsFather;
	}

	public int getDaughtersFromFather() {
		return daughtersFather;
	}

	public int getTotalLitterCount() {
		return sonsMother + daughtersMother + sonsFather + daughtersFather;
	}

	public Subspecies getMotherRace() {
		return motherRace;
	}

	public Subspecies getFatherRace() {
		return fatherRace;
	}

	public void setFatherRace(Subspecies fatherSubspecies) {
		fatherRace = fatherSubspecies;
	}

	public void generateBirthedDescription() {
		Map<String, Integer> sons = new HashMap<>();
		Map<String, Integer> daughters = new HashMap<>();

		for(String id : this.getOffspring()) {
			try {
				GameCharacter character = Main.game.getNPCById(id);
				Subspecies subspecies = character.getSubspecies();
				if(Main.game.getNPCById(id).isFeminine()) {
					String nameId = subspecies.getSingularFemaleName(character)+"|"+subspecies.getPluralFemaleName(character);
					daughters.putIfAbsent(nameId, 0);
					daughters.put(nameId, daughters.get(nameId)+1);
				} else {
					String nameId = subspecies.getSingularMaleName(character)+"|"+subspecies.getPluralMaleName(character);
					sons.putIfAbsent(nameId, 0);
					sons.put(nameId, sons.get(nameId)+1);
				}
			} catch (Exception e) {
			}
		}

		List<String> entries = new ArrayList<>();

		for(Entry<String, Integer> entry : sons.entrySet()) {
			entries.add(""+Util.intToString(entry.getValue())+" "
						+(entry.getValue() > 1
							? entry.getKey().split("\\|")[1]
							: entry.getKey().split("\\|")[0])
						+"");
		}
		for(Entry<String, Integer> entry : daughters.entrySet()) {
			entries.add(""+Util.intToString(entry.getValue())+" "
						+(entry.getValue() > 1
							? entry.getKey().split("\\|")[1]
							: entry.getKey().split("\\|")[0])
						+"");
		}

		if(!entries.isEmpty()) {
			birthedDescription = Util.stringsToStringList(entries, false);
		}
	}

	//TODO Add this to the generateBirthedDescription() method above.
	@SuppressWarnings("unused")
	private String getRelationshipInformation() {
		StringBuilder descriptionSB = new StringBuilder();

		// FIXME: this won't work when there are multiple children, even if they all have the same gender
		Set<PronounType> childrenPronouns = getOffspringCharacters().stream().map(x -> x.getGender().getType()).collect(Collectors.toSet());
		PronounType childrenPronoun = PronounType.NEUTRAL;
		if(childrenPronouns.size() == 1) {
		    childrenPronoun = childrenPronouns.iterator().next();
		}

		Set<Relationship> relFather = Collections.emptySet();
		if(getFather() != null) {
			relFather = getOffspringCharacters().stream()
					.flatMap(x -> x.getRelationshipsTo(getFather()).stream())
					.collect(Collectors.toSet());
		}

		Set<Relationship> relMother = Collections.emptySet();
		if(getFather() != null) {
			relMother = getOffspringCharacters().stream()
					.flatMap(x -> x.getRelationshipsTo(getMother()).stream())
					.collect(Collectors.toSet());
		}

		Set<Relationship> relPlayer = Collections.emptySet();
		if(getFather() != null && !getFather().isPlayer() && getMother() != null && !getMother().isPlayer()) {
			relPlayer = getOffspringCharacters().stream()
					.flatMap(x -> x.getRelationshipsTo(Main.game.getPlayer()).stream())
					.collect(Collectors.toSet());
		}

		if(!relMother.isEmpty() || !relFather.isEmpty()) {
			descriptionSB.append(" (");
			if(!relFather.isEmpty()) {
				if(getFather().isPlayer()) {
					descriptionSB.append("your ");
				} else {
					descriptionSB.append(getFather().getName(true) + "'s ");
				}
				descriptionSB.append(GameCharacter.getRelationshipStr(relFather, childrenPronoun));
				if(!relMother.isEmpty() || !relPlayer.isEmpty()) {
					descriptionSB.append(", ");
				}
			}

			if(!relMother.isEmpty()) {
				if(getMother().isPlayer()) {
					descriptionSB.append("your ");
				} else {
					descriptionSB.append(getMother().getName(true) + "'s ");
				}
				descriptionSB.append(GameCharacter.getRelationshipStr(relMother, childrenPronoun));
				if(!relPlayer.isEmpty())
					descriptionSB.append(", ");
			}

			if(!relPlayer.isEmpty()) {
				descriptionSB.append("your ");
				descriptionSB.append(GameCharacter.getRelationshipStr(relPlayer, childrenPronoun));
			}
			descriptionSB.append(")");
		}
		return descriptionSB.toString();
	}

	public String getBirthedDescription() {
		if(birthedDescription==null || birthedDescription.isEmpty()) {
			generateBirthedDescription();
		}
		return birthedDescription;
	}

}

repo_name: klorpa/Pedo

license: unknown


code:
package com.lilithsthrone.game.character;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
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.attributes.Attribute;
import com.lilithsthrone.game.character.body.types.ArmType;
import com.lilithsthrone.game.character.body.types.AssType;
import com.lilithsthrone.game.character.body.types.BreastType;
import com.lilithsthrone.game.character.body.types.EarType;
import com.lilithsthrone.game.character.body.types.EyeType;
import com.lilithsthrone.game.character.body.types.FaceType;
import com.lilithsthrone.game.character.body.types.HairType;
import com.lilithsthrone.game.character.body.types.HornType;
import com.lilithsthrone.game.character.body.types.LegType;
import com.lilithsthrone.game.character.body.types.PenisType;
import com.lilithsthrone.game.character.body.types.SkinType;
import com.lilithsthrone.game.character.body.types.TailType;
import com.lilithsthrone.game.character.body.types.VaginaType;
import com.lilithsthrone.game.character.body.types.WingType;
import com.lilithsthrone.game.character.effects.StatusEffect;
import com.lilithsthrone.game.character.fetishes.Fetish;
import com.lilithsthrone.game.character.gender.Gender;
import com.lilithsthrone.game.character.npc.NPC;
import com.lilithsthrone.game.character.npc.dominion.DominionClubNPC;
import com.lilithsthrone.game.character.npc.dominion.Lilaya;
import com.lilithsthrone.game.character.npc.misc.NPCOffspring;
import com.lilithsthrone.game.character.npc.submission.DarkSiren;
import com.lilithsthrone.game.character.npc.submission.Elizabeth;
import com.lilithsthrone.game.character.npc.submission.Lyssieth;
import com.lilithsthrone.game.character.persona.NameTriplet;
import com.lilithsthrone.game.character.persona.Relationship;
import com.lilithsthrone.game.character.persona.SexualOrientation;
import com.lilithsthrone.game.character.quests.Quest;
import com.lilithsthrone.game.character.quests.QuestLine;
import com.lilithsthrone.game.character.quests.QuestType;
import com.lilithsthrone.game.character.race.Race;
import com.lilithsthrone.game.character.race.RaceStage;
import com.lilithsthrone.game.character.race.Subspecies;
import com.lilithsthrone.game.dialogue.DialogueFlagValue;
import com.lilithsthrone.game.dialogue.eventLog.EventLogEntry;
import com.lilithsthrone.game.dialogue.utils.UtilText;
import com.lilithsthrone.game.inventory.CharacterInventory;
import com.lilithsthrone.game.inventory.ShopTransaction;
import com.lilithsthrone.game.inventory.weapon.AbstractWeaponType;
import com.lilithsthrone.game.sex.CondomFailure;
import com.lilithsthrone.game.sex.OrgasmCumTarget;
import com.lilithsthrone.game.sex.Sex;
import com.lilithsthrone.game.sex.SexAreaOrifice;
import com.lilithsthrone.game.sex.SexAreaPenetration;
import com.lilithsthrone.game.sex.managers.submission.SMLyssiethDemonTF;
import com.lilithsthrone.game.sex.positions.slots.SexSlotLyingDown;
import com.lilithsthrone.game.sex.sexActions.SexActionInterface;
import com.lilithsthrone.game.sex.sexActions.SexActionOrgasmOverride;
import com.lilithsthrone.game.sex.sexActions.SexActionType;
import com.lilithsthrone.game.sex.sexActions.baseActionsMisc.GenericOrgasms;
import com.lilithsthrone.main.Main;
import com.lilithsthrone.utils.Colour;
import com.lilithsthrone.utils.SizedStack;
import com.lilithsthrone.utils.TreeNode;
import com.lilithsthrone.utils.Util;
import com.lilithsthrone.utils.Vector2i;
import com.lilithsthrone.utils.XMLSaving;
import com.lilithsthrone.world.WorldType;
import com.lilithsthrone.world.places.AbstractPlaceType;
import com.lilithsthrone.world.places.PlaceType;

/**
 * @since 0.1.0
 * @version 0.3.5
 * @author Innoxia
 */
public class PlayerCharacter extends GameCharacter implements XMLSaving {

	private String title;

	private int karma;

	private Map<QuestLine, List<Quest>> quests;

	private boolean mainQuestUpdated, sideQuestUpdated, relationshipQuestUpdated;

	private Set<Subspecies> racesDiscoveredFromBook;

	protected List<String> friendlyOccupants;

	// Trader buy-back:
	private SizedStack<ShopTransaction> buybackStack;

	private List<String> charactersEncountered;

	private Set<WorldType> worldsVisited;

	public PlayerCharacter(NameTriplet nameTriplet, int level, LocalDateTime birthday, Gender gender, Subspecies startingSubspecies, RaceStage stage, WorldType startingWorld, AbstractPlaceType startingPlace) {
		super(nameTriplet, "", "", level, Main.game.getDateNow().minusYears(22), gender, startingSubspecies, stage, new CharacterInventory(0), startingWorld, startingPlace);

		this.setSexualOrientation(SexualOrientation.AMBIPHILIC);

		title = "The Human";

		karma = 0;

		this.setMaxCompanions(1);

		quests = new HashMap<>();

		mainQuestUpdated = false;
		sideQuestUpdated = false;
		relationshipQuestUpdated = false;

		racesDiscoveredFromBook = new HashSet<>();

		buybackStack = new SizedStack<>(24);

		charactersEncountered = new ArrayList<>();

		friendlyOccupants = new ArrayList<>();

		worldsVisited = new HashSet<>();

		this.setAttribute(Attribute.MAJOR_PHYSIQUE, 0f, false);
		this.setAttribute(Attribute.MAJOR_ARCANE, 0f, false);
		this.setAttribute(Attribute.MAJOR_CORRUPTION, 0f, false);
	}

	@Override
	public boolean isUnique() {
		return true;
	}

	@Override
	public Element saveAsXML(Element parentElement, Document doc) {
		Element properties = super.saveAsXML(parentElement, doc);

		Element playerSpecific = doc.createElement("playerSpecific");
		properties.appendChild(playerSpecific);

		CharacterUtils.createXMLElementWithValue(doc, playerSpecific, "title", this.getTitle());
		CharacterUtils.createXMLElementWithValue(doc, playerSpecific, "karma", String.valueOf(this.getKarma()));

		Element questUpdatesElement = doc.createElement("questUpdates");
		playerSpecific.appendChild(questUpdatesElement);
		CharacterUtils.createXMLElementWithValue(doc, playerSpecific, "mainQuestUpdated", String.valueOf(this.mainQuestUpdated));
		CharacterUtils.createXMLElementWithValue(doc, playerSpecific, "sideQuestUpdated", String.valueOf(this.sideQuestUpdated));
		CharacterUtils.createXMLElementWithValue(doc, playerSpecific, "relationshipQuestUpdated", String.valueOf(this.relationshipQuestUpdated));

		Element innerElement = doc.createElement("racesDiscovered");
		playerSpecific.appendChild(innerElement);
		for(Subspecies subspecies : racesDiscoveredFromBook) {
			if(subspecies != null) {
				CharacterUtils.createXMLElementWithValue(doc, innerElement, "race", subspecies.toString());
			}
		}

		Element charactersEncounteredElement = doc.createElement("charactersEncountered");
		playerSpecific.appendChild(charactersEncounteredElement);
		for(String id : charactersEncountered) {
			CharacterUtils.createXMLElementWithValue(doc, charactersEncounteredElement, "id", id);
		}

		innerElement = doc.createElement("questMap");
		playerSpecific.appendChild(innerElement);
		for(Entry<QuestLine, List<Quest>> entry : quests.entrySet()) {
			Element e = doc.createElement("entry");
			innerElement.appendChild(e);
			CharacterUtils.addAttribute(doc, e, "questLine", entry.getKey().toString());
			for(int i=0; i<entry.getValue().size(); i++) {
				CharacterUtils.addAttribute(doc, e, "q"+i, String.valueOf(entry.getValue().get(i)));
			}
		}

		Element friendlyOccupants = doc.createElement("friendlyOccupants");
		playerSpecific.appendChild(friendlyOccupants);
		for(String occupant : this.getFriendlyOccupants()) {
			Element element = doc.createElement("occupant");
			friendlyOccupants.appendChild(element);

			CharacterUtils.addAttribute(doc, element, "id", occupant);
		}

		Element worldsVisitedElement = doc.createElement("worldsVisited");
		playerSpecific.appendChild(worldsVisitedElement);
		for(WorldType world : this.getWorldsVisited()) {
			Element element = doc.createElement("world");
			worldsVisitedElement.appendChild(element);

			CharacterUtils.addAttribute(doc, element, "id", world.toString());
		}

//		private SizedStack<ShopTransaction> buybackStack; TODO

//		Element slavesOwned = doc.createElement("slavesExported");
//		properties.appendChild(slavesOwned);
//		for(String id : this.getSlavesOwned()) {
//			Main.game.getNPCById(id).saveAsXML(slavesOwned, doc);
//		}

		return properties;
	}

	public static PlayerCharacter loadFromXML(StringBuilder log, Element parentElement, Document doc, CharacterImportSetting... settings) {
		PlayerCharacter character = new PlayerCharacter(new NameTriplet(""), 0, null, Gender.F_V_B_FEMALE, Subspecies.HUMAN, RaceStage.HUMAN, WorldType.DOMINION, PlaceType.DOMINION_AUNTS_HOME);

		GameCharacter.loadGameCharacterVariablesFromXML(character, log, parentElement, doc, settings);

		character.sortInventory();

		boolean newGameImport = Arrays.asList(settings).contains(CharacterImportSetting.NEW_GAME_IMPORT);

		NodeList nodes = parentElement.getElementsByTagName("core");
		Element element = (Element) nodes.item(0);
		String version = "";
		if(element.getElementsByTagName("version").item(0)!=null) {
			version = ((Element) element.getElementsByTagName("version").item(0)).getAttribute("value");
		}

		Element playerSpecificElement = (Element) parentElement.getElementsByTagName("playerSpecific").item(0);

		if(newGameImport) {
			character.setLocation(WorldType.MUSEUM_LOST, PlaceType.MUSEUM_MIRROR);
		}

		if(!newGameImport) {
			if(playerSpecificElement!=null) {
				if(playerSpecificElement.getElementsByTagName("title").getLength()!=0) {
					character.setTitle(((Element)playerSpecificElement.getElementsByTagName("title").item(0)).getAttribute("value"));
				}

				if(playerSpecificElement.getElementsByTagName("karma").getLength()!=0) {
					character.setKarma(Integer.valueOf(((Element)playerSpecificElement.getElementsByTagName("karma").item(0)).getAttribute("value")));
				}

				if(playerSpecificElement.getElementsByTagName("mainQuestUpdated").getLength()!=0) {
					character.setMainQuestUpdated(Boolean.valueOf(((Element)playerSpecificElement.getElementsByTagName("mainQuestUpdated").item(0)).getAttribute("value")));
				}
				if(playerSpecificElement.getElementsByTagName("sideQuestUpdated").getLength()!=0) {
					character.setSideQuestUpdated(Boolean.valueOf(((Element)playerSpecificElement.getElementsByTagName("sideQuestUpdated").item(0)).getAttribute("value")));
				}
				if(playerSpecificElement.getElementsByTagName("relationshipQuestUpdated").getLength()!=0) {
					character.setRelationshipQuestUpdated(Boolean.valueOf(((Element)playerSpecificElement.getElementsByTagName("relationshipQuestUpdated").item(0)).getAttribute("value")));
				}

				try {
					Element racesDiscoveredElement = (Element) playerSpecificElement.getElementsByTagName("racesDiscovered").item(0);
					if(racesDiscoveredElement != null) {

						NodeList races = racesDiscoveredElement.getElementsByTagName("race");
						for(int i=0; i < races.getLength(); i++){
							Element e = (Element) races.item(i);
							try {
								character.addRaceDiscoveredFromBook(Subspecies.valueOf(e.getAttribute("value")));
							} catch(Exception ex) {
							}
						}
					}
				} catch(Exception ex) {
				}

				Element charactersEncounteredElement = (Element) playerSpecificElement.getElementsByTagName("charactersEncountered").item(0);
				if(charactersEncounteredElement != null) {
					NodeList charactersEncounteredIds = charactersEncounteredElement.getElementsByTagName("id");
					for(int i=0; i<charactersEncounteredIds.getLength(); i++){
						Element e = (Element) charactersEncounteredIds.item(i);
						character.addCharacterEncountered(e.getAttribute("value"));
					}
				}

				Element questMapElement = (Element) playerSpecificElement.getElementsByTagName("questMap").item(0);
				if(questMapElement!=null) {
					NodeList questMapEntries = questMapElement.getElementsByTagName("entry");
					if(Main.isVersionOlderThan(version, "0.1.99.5")) {
						for(int i=0; i< questMapEntries.getLength(); i++){
							Element e = (Element) questMapEntries.item(i);

							try {
								int progress = Integer.valueOf(e.getAttribute("progress"));
								QuestLine questLine = QuestLine.valueOf(e.getAttribute("questLine"));
								TreeNode<Quest> q = questLine.getQuestTree();

								for(int it=0;it<progress;it++) {
									if(!q.getChildren().isEmpty()) {
										q = q.getChildren().get(0);
									}
								}

								Quest quest = q.getData();
								List<Quest> questList = new ArrayList<>();
								TreeNode<Quest> node = questLine.getQuestTree().getFirstNodeWithData(quest);

								while(node!=null) {
									questList.add(node.getData());
									node = node.getParent();
								}
								Collections.reverse(questList);

								character.quests.put(
										questLine,
										questList);

							} catch(Exception ex) {
								System.err.println("ERR Quest!");
							}
						}

					} else if(Main.isVersionOlderThan(version, "0.3.5.3")) {
						for(int i=0; i<questMapEntries.getLength(); i++){
							Element e = (Element) questMapEntries.item(i);
							try {
								String questLineString = e.getAttribute("questLine");
								if(questLineString.contains("SIDE_NYAN")) {
									questLineString = questLineString.replace("SIDE_NYAN", "RELATIONSHIP_NYAN");
								}

								String questString = e.getAttribute("quest");
								if(questString.contains("SIDE_NYAN")) {
									questString = questString.replace("SIDE_NYAN", "RELATIONSHIP_NYAN");
								}

								QuestLine questLine = QuestLine.valueOf(questLineString);
								Quest quest = Quest.valueOf(questString);

								List<Quest> questList = new ArrayList<>();
								TreeNode<Quest> node = questLine.getQuestTree().getFirstNodeWithData(quest);

								while(node!=null) {
									questList.add(node.getData());
									node = node.getParent();
								}
								Collections.reverse(questList);

								character.quests.put(
										questLine,
										questList);
							} catch(Exception ex) {
							}
						}

					} else {
						for(int i=0; i<questMapEntries.getLength(); i++){
							Element e = (Element) questMapEntries.item(i);
							String questLineString = e.getAttribute("questLine");
							QuestLine questLine = QuestLine.valueOf(questLineString);
							String questString = e.getAttribute("q"+0);
							Quest quest = Quest.valueOf(questString);
							List<Quest> questList = new ArrayList<>();

							int questIncrement=0;
							while(!questString.isEmpty()) {
								quest = Quest.valueOf(questString);

								questList.add(quest);

								questIncrement++;
								questString = e.getAttribute("q"+questIncrement);
							}

							character.quests.put(
									questLine,
									questList);

						}
					}
				}
			}

			try {
				for(int i=0; i<((Element) playerSpecificElement.getElementsByTagName("friendlyOccupants").item(0)).getElementsByTagName("occupant").getLength(); i++){
					Element e = ((Element)playerSpecificElement.getElementsByTagName("occupant").item(i));

					if(!e.getAttribute("id").equals("NOT_SET")) {
						character.getFriendlyOccupants().add(e.getAttribute("id"));
						CharacterUtils.appendToImportLog(log, "
Added occupant: "+e.getAttribute("id"));
					}
				}
			} catch(Exception ex) {
			}

			try {
				for(int i=0; i<((Element) playerSpecificElement.getElementsByTagName("worldsVisited").item(0)).getElementsByTagName("world").getLength(); i++){
					Element e = ((Element)playerSpecificElement.getElementsByTagName("world").item(i));

					character.getWorldsVisited().add(WorldType.valueOf(e.getAttribute("id")));
					CharacterUtils.appendToImportLog(log, "
Added world visited: "+e.getAttribute("id"));
				}
			} catch(Exception ex) {
			}
		}

		if(Main.isVersionOlderThan(Game.loadingVersion, "0.3.0.5")) {
			// Reset player's demon parts to human if prior to 0.3.0.5:
			if(character.getArmType().getRace()==Race.DEMON) {
				character.setArmType(ArmType.HUMAN);
			}
			if(character.getAssType().getRace()==Race.DEMON) {
				character.setAssType(AssType.HUMAN);
			}
			if(character.getBreastType().getRace()==Race.DEMON) {
				character.setBreastType(BreastType.HUMAN);
			}
			if(character.getEarType().getRace()==Race.DEMON) {
				character.setEarType(EarType.HUMAN);
			}
			if(character.getEyeType().getRace()==Race.DEMON) {
				character.setEyeType(EyeType.HUMAN);
			}
			if(character.getFaceType().getRace()==Race.DEMON) {
				character.setFaceType(FaceType.HUMAN);
			}
			if(character.getHairType().getRace()==Race.DEMON) {
				character.setHairType(HairType.HUMAN);
			}
			if(character.getHornType().getRace()==Race.DEMON) {
				character.setHornType(HornType.NONE);
			}
			if(character.getLegType().getRace()==Race.DEMON) {
				character.setLegType(LegType.HUMAN);
			}
			if(character.getPenisType().getRace()==Race.DEMON) {
				character.setPenisType(PenisType.HUMAN);
			}
			if(character.getSkinType().getRace()==Race.DEMON) {
				character.setSkinType(SkinType.HUMAN);
			}
			if(character.getTailType().getRace()==Race.DEMON) {
				character.setTailType(TailType.NONE);
			}
			if(character.getVaginaType().getRace()==Race.DEMON) {
				character.setVaginaType(VaginaType.HUMAN);
			}
			if(character.getWingType().getRace()==Race.DEMON) {
				character.setWingType(WingType.NONE);
			}
			character.setSubspeciesOverride(null);
			character.getBody().calculateRace(character);
		}

		if(Main.isVersionOlderThan(Game.loadingVersion, "0.3.3.5")) {
			character.equipBasicCombatMoves();
		}

		if(Main.isVersionOlderThan(Game.loadingVersion, "0.3.4")) {
			character.ageAppearanceDifference = -Game.TIME_SKIP_YEARS;
		}

		return character;
	}

	@Override
	protected void updateAttributeListeners() {
		if (playerAttributeChangeEventListeners != null)
			for (CharacterChangeEventListener eventListener : playerAttributeChangeEventListeners)
				eventListener.onChange();
	}

	@Override
	protected void updateLocationListeners() {
		if (playerLocationChangeEventListeners != null)
			for (CharacterChangeEventListener eventListener : playerLocationChangeEventListeners)
				eventListener.onChange();
	}

	@Override
	public void updateInventoryListeners() {
		if (playerInventoryChangeEventListeners != null)
			for (CharacterChangeEventListener eventListener : playerInventoryChangeEventListeners)
				eventListener.onChange();
	}

	@Override
	public String getId() {
		return "PlayerCharacter";//-"+Main.game.getNpcTally();
	}

	@Override
	public boolean isPlayer() {
		return true;
	}

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

	@Override
	public String getDescription() {
		if(!Main.game.isInNewWorld()) {
			return ""; // This isn't displayed anywhere before the game starts for real.
		} else {
			return "Having been pulled into an enchanted mirror in your aunt Lily's museum, you woke up to find yourself in another world."
					+ " By a stroke of good fortune, one of the first people you met was Lilaya; this world's version of your aunt."
					+ " Having convinced her that your story is true, you're now working towards finding a way to get back to your old world.";
		}
	}

	@Override
	public void setLocation(WorldType worldLocation, Vector2i location, boolean setAsHomeLocation) {
		if(this.getWorldsVisited()!=null && !this.getWorldsVisited().contains(worldLocation)) {
			this.getWorldsVisited().add(worldLocation);
			if(Main.game.isStarted()) {
				Main.game.addEvent(new EventLogEntry("[style.colourExcellent(Discovered)]", Util.capitaliseSentence(worldLocation.getName())), false);
			}
		}

		if(Main.game.isStarted() && worldLocation!=this.getWorldLocation()) {
			Main.game.addEvent(new EventLogEntry("[style.colourMinorGood(Entered)]", Util.capitaliseSentence(worldLocation.getName())), false);
		}

		if(this.getWorldLocation()==WorldType.NIGHTLIFE_CLUB) {
			List<GameCharacter> clubbers = new ArrayList<>(Main.game.getNonCompanionCharactersPresent());
			clubbers.removeIf((npc) -> !(npc instanceof DominionClubNPC));

			WorldType worldLocationInitial = this.getWorldLocation();
			Vector2i locationInitial = this.getLocation();

			super.setLocation(worldLocation, location, setAsHomeLocation);

			for(GameCharacter clubber : clubbers) {
				clubber.setLocation(this, false);
				// TODO Why is this needed? I can't figure out why IDs are not being removed without this line:
				if(worldLocation!=worldLocationInitial || !location.equals(locationInitial)) {
					Main.game.getWorlds().get(worldLocationInitial).getCell(locationInitial).removeCharacterPresentId(clubber.getId());
				}
			}

		} else if(this.getWorldLocation()==WorldType.SUBMISSION) {
			super.setLocation(worldLocation, location, setAsHomeLocation);

			AbstractPlaceType place = Main.game.getWorlds().get(WorldType.SUBMISSION).getCell(location).getPlace().getPlaceType();
			if(place.equals(PlaceType.SUBMISSION_LILIN_PALACE_GATE) || place.equals(PlaceType.SUBMISSION_LILIN_PALACE)) {
				Main.game.getNpc(Elizabeth.class).setLocation(this, false);
			}

		} else {
			super.setLocation(worldLocation, location, setAsHomeLocation);
		}
	}

	public String getTitle() {
		return title;
	}

	public void setTitle(String title) {
		this.title = title;
	}

	public String getPetName(GameCharacter target) {
		if(target instanceof Lyssieth && this.getRace()==Race.DEMON) {
			if(this.hasFetish(Fetish.FETISH_INCEST)) {
				return "mommy";
			} else {
				return "mother";
			}
		}
		return super.getPetName(target);
	}

	public int getKarma() {
		return karma;
	}

	public void setKarma(int karma) {
		this.karma = karma;
	}

	/**
	 * This is just an internal stat that isn't used for anything, other than to help me feel better about writing horrible scenes.

	 *
	 * -100 would be for something huge, like attacking and enslaving one of your children.

	 * -10 would be for something large, like stealing from someone.

	 * -1 would be for something small, like insulting someone who doesn't deserve it.

	 * 0 = neutral

	 * +1 would be for something small, like giving a gift.

	 * +10 would be for something large, like sacrificing your own well-being to help another person.

	 * +100 would be for something huge, like buying and then immediately freeing a slave.

	 * @param increment
	 */
	public void incrementKarma(int increment) {
		this.karma += increment;
	}

	@Override
	public Set<Relationship> getRelationshipsTo(GameCharacter character, Relationship... excludedRelationships) {
		if(character instanceof Lilaya) {
			if(this.getRace()==Race.DEMON) {
				Set<Relationship> rSet = new LinkedHashSet<>();
				rSet.add(Relationship.HalfSibling);
				rSet.add(Relationship.Nibling);
				return rSet;
			}
			return Util.newHashSetOfValues(Relationship.Nibling);
		}
		if(character.getRace()==Race.DEMON) {
			if(character instanceof Lyssieth) {
				return Util.newHashSetOfValues(Relationship.Parent);
			}
			if(character instanceof DarkSiren) {
				return Util.newHashSetOfValues(Relationship.HalfSibling);
			}
		}
		return super.getRelationshipsTo(character, excludedRelationships);
	}

	// Quests:

	public void resetAllQuests() {
		quests = new EnumMap<>(QuestLine.class);
	}

	public boolean isMainQuestUpdated() {
		return mainQuestUpdated;
	}

	public void setMainQuestUpdated(boolean mainQuestUpdated) {
		this.mainQuestUpdated = mainQuestUpdated;
	}

	public boolean isSideQuestUpdated() {
		return sideQuestUpdated;
	}

	public void setSideQuestUpdated(boolean sideQuestUpdated) {
		this.sideQuestUpdated = sideQuestUpdated;
	}

	public boolean isRelationshipQuestUpdated() {
		return relationshipQuestUpdated;
	}

	public void setRelationshipQuestUpdated(boolean relationshipQuestUpdated) {
		this.relationshipQuestUpdated = relationshipQuestUpdated;
	}

	public String startQuest(QuestLine questLine) {
		return setQuestProgress(questLine, questLine.getQuestTree().getData());
	}

	public String addOptionalQuestProgress(QuestLine questLine, Quest quest) {
		if(!quests.containsKey(questLine)) {
			System.err.println("Player does not have quest line "+questLine+", so cannot add optional quest: "+quest);
			return "";
		}

		if(questLine.getType() == QuestType.MAIN) {
			setMainQuestUpdated(true);

		} else if(questLine.getType() == QuestType.SIDE) {
			setSideQuestUpdated(true);

		} else {
			setRelationshipQuestUpdated(true);
		}

		String experienceUpdate = incrementExperience(quest.getExperienceReward(), true);

		quests.get(questLine).add(0, quest);

		Main.game.getEventLog().add(new EventLogEntry(Main.game.getMinutesPassed(), "[style.colourGood(Optional Task Complete)]", quest.getName()));
		return "
"
				+ "Quest - " + questLine.getName() + "
"
				+ "Optional Task Completed: " + quest.getName() + "
"
				+ experienceUpdate;
	}

	public String setQuestProgress(QuestLine questLine, Quest quest) {
		if(!questLine.getQuestTree().childrenContainsData(quest)) {
			System.err.println("QuestTree in quest line "+questLine+" does not contain quest: "+quest);
			return "";
		}

		if (questLine.getType() == QuestType.MAIN) {
			setMainQuestUpdated(true);

		} else if (questLine.getType() == QuestType.SIDE) {
			setSideQuestUpdated(true);

		} else {
			setRelationshipQuestUpdated(true);
		}

		if(quests.containsKey(questLine)) {
			Quest currentQuest = questLine.getQuestTree().getFirstNodeWithData(getQuest(questLine)).getData();

			String experienceUpdate = incrementExperience(currentQuest.getExperienceReward(), true);

			quests.get(questLine).add(quest);

			if (questLine.getQuestTree().getFirstNodeWithData(quest).getChildren().isEmpty()) { // QuestLine complete (No more children in the tree)
				Main.game.getEventLog().add(new EventLogEntry(Main.game.getMinutesPassed(), "[style.colourExcellent(Quest Complete)]", questLine.getName()));
				return "
"
						+ "Quest - " + questLine.getName() + "
"
						+ "Task Completed - "+currentQuest.getName()+"
"
						+ "All Tasks Completed!
"
						+ experienceUpdate;

			} else {
				Main.game.getEventLog().add(new EventLogEntry(Main.game.getMinutesPassed(), "[style.colourMinorGood(New Task)]", quest.getName()));
				return "
"
						+ "Quest - " + questLine.getName() + "
"
						+ "Task Completed
"
						+ "New Task - " + quest.getName() + "
"
						+ experienceUpdate;
			}

		} else {
			quests.put(questLine, new ArrayList<>());
			quests.get(questLine).add(quest);

			Main.game.getEventLog().add(new EventLogEntry(Main.game.getMinutesPassed(), "[style.colourGood(Quest Started)]", questLine.getName()));

			return "
"
					+ "New Quest - " + questLine.getName() + "
"
					+ "New Task - " + quest.getName() + "
";
		}

	}

	public Map<QuestLine, List<Quest>> getQuests() {
		return quests;
	}

	public Quest getQuest(QuestLine questLine) {
		return quests.get(questLine).get(quests.get(questLine).size()-1);
	}

	public boolean hasQuest(QuestLine questLine) {
		return quests.containsKey(questLine);
	}

	public boolean isSubQuestCompleted(Quest subQuest, QuestLine questLine) {
		if(quests.containsKey(questLine) && quests.get(questLine).contains(subQuest) && getQuest(questLine)!=subQuest) {
			return true;
		}
		return false;
	}

	public boolean isQuestCompleted(QuestLine questLine) {
		if(!hasQuest(questLine)) {
			return false;
		}
		return questLine.getQuestTree().getFirstNodeWithData(getQuest(questLine)).getChildren().isEmpty();
	}

	public boolean isHasSlaverLicense() {
		return isQuestCompleted(QuestLine.SIDE_SLAVERY) || Main.game.isDebugMode();
	}

	public boolean isAbleToAccessRoomManagement() {
		return isHasSlaverLicense() || isQuestCompleted(QuestLine.SIDE_ACCOMMODATION);
	}

	public boolean isQuestProgressGreaterThan(QuestLine questLine, Quest quest) {
		if(!hasQuest(questLine)) {
			System.err.println("Player does not have Quest: "+questLine.toString()+", "+quest.toString());
			return false;
		}

		if(questLine.getQuestTree().getFirstNodeWithData(quest)==null) {
			System.err.println("Quest "+quest.toString()+" was not in QuestLine!");
			return false;
		}

		// Check to see if the current progress does not have a child with quest data:
		return questLine.getQuestTree().getFirstNodeWithData(getQuest(questLine)).getFirstNodeWithData(quest)==null;
	}

	public boolean isQuestProgressLessThan(QuestLine questLine, Quest quest) {
		if(!hasQuest(questLine)) {
			System.err.println("Player does not have Quest: "+quest.toString());
			return false;
		}

		if(getQuest(questLine)==quest) {
			return false;
		}

		if(questLine.getQuestTree().getFirstNodeWithData(quest)==null) {
			System.err.println("Quest "+quest.toString()+" was not in QuestLine!");
			return false;
		}

		// Check to see if the current progress has a child with quest data:
		return questLine.getQuestTree().getFirstNodeWithData(getQuest(questLine)).getFirstNodeWithData(quest)!=null;
	}

	// Other stuff:

	public List<String> getCharactersEncountered() {
		return charactersEncountered;
	}

	public void addCharacterEncountered(String character) {
		if (!charactersEncountered.contains(character)) {
			charactersEncountered.add(character);
		}
		if(Main.game.isStarted()) {
			sortCharactersEncountered();
		}
	}

	public void addCharacterEncountered(GameCharacter character) {
		if (!charactersEncountered.contains(character.getId())) {
			charactersEncountered.add(character.getId());
		}
		if(Main.game.isStarted()) {
			sortCharactersEncountered();
		}
	}

	/**
	 * @param expansiveSearch True if you want every possible character that the player has have encountered, including ones that are not usually added to the contacts list. (This will include random NPCs the player has had sex with.)
	 * @return
	 */
	public List<GameCharacter> getCharactersEncounteredAsGameCharacters(boolean expansiveSearch) {
		List<GameCharacter> npcsEncountered = new ArrayList<>();
		for(String characterId : charactersEncountered) {
			try {
				GameCharacter npc = Main.game.getNPCById(characterId);
				npcsEncountered.add(npc);
			} catch (Exception e) {
				Util.logGetNpcByIdError("getCharactersEncounteredAsGameCharacters()", characterId);
			}
		}

		if(expansiveSearch) {
			for(String id : this.getSexPartners().keySet()) {
				try {
					GameCharacter npc = Main.game.getNPCById(id);
					npcsEncountered.add(npc);
				} catch(Exception ex) {
				}
			}
		}

		return npcsEncountered;
	}

	public void sortCharactersEncountered() {
		List<GameCharacter> npcsEncountered = new ArrayList<>();
		for(String characterId : charactersEncountered) {
			try {
				GameCharacter npc = Main.game.getNPCById(characterId);
				npcsEncountered.add(npc);
			} catch (Exception e) {
				Util.logGetNpcByIdError("sortCharactersEncountered()", characterId);
			}
		}
		npcsEncountered.sort((npc1, npc2) -> npc1 instanceof NPCOffspring
				?(npc2 instanceof NPCOffspring
					?npc1.getName(true).compareTo(npc2.getName(true))
					:1)
				:(npc2 instanceof NPCOffspring
						?-1
						:npc1.getName(true).compareTo(npc2.getName(true))));
		List<String> sortedIDs = new ArrayList<>();
		for(GameCharacter character : npcsEncountered) {
			sortedIDs.add(character.getId());
		}
		charactersEncountered = sortedIDs;
	}

	public SizedStack<ShopTransaction> getBuybackStack() {
		return buybackStack;
	}

	public boolean addRaceDiscoveredFromBook(Subspecies subspecies) {
		return racesDiscoveredFromBook.add(subspecies);
	}

	public Set<Subspecies> getRacesDiscoveredFromBook() {
		return racesDiscoveredFromBook;
	}

	@Override
	public String getMainAttackDescription(int armRow, GameCharacter target, boolean isHit) {
		if(this.getMainWeapon(armRow)!=null) {
			return this.getMainWeapon(armRow).getWeaponType().getAttackDescription(this, target, isHit);
		} else {
			return AbstractWeaponType.genericMeleeAttackDescription(this, target, isHit);
		}
	}

	@Override
	public String getSpellDescription() {
		return "
"
				+ UtilText.parse(this,
						UtilText.returnStringAtRandom(
						"Concentrating on harnessing the power of your arcane aura, you thrust your [pc.arm] into mid air and cast a spell!"))
			+ "
";
	}

	@Override
	public String getSeductionDescription(GameCharacter target) {
		String description = "";
		if(this.hasStatusEffect(StatusEffect.TELEPATHIC_COMMUNICATION)
				|| this.hasStatusEffect(StatusEffect.TELEPATHIC_COMMUNICATION_POWER_OF_SUGGESTION)
				|| this.hasStatusEffect(StatusEffect.TELEPATHIC_COMMUNICATION_PROJECTED_TOUCH)) {
			if(this.isFeminine()) {
				return UtilText.parse(target,
						UtilText.returnStringAtRandom(
								"You put on a smouldering look, and as your [pc.eyes] meet [npc.namePos], you project an extremely lewd moan into [npc.her] head,"
										+ " [pc.thought(~Aaah!~ "
											+(this.hasVagina()
													?"You're making me so wet!"
													:this.hasPenis()
														?"You're getting me so hard!"
														:"You're turning me on so much!")+")]",
								"You lock your [pc.eyes] with [npc.namePos], and, putting on your most innocent look as you pout at [npc.herHim], you project an echoing moan deep into [npc.her] mind,"
									+ " [pc.thought("+
											(this.hasVagina()
													?"~Mmm!~ Fuck me! ~Aaa!~ My pussy's wet and ready for you!"
													:this.hasPenis()
														?"~Mmm!~ I can't wait to fuck you! ~Aaa!~ You're going to love my cock!"
														:"~Mmm!~ Fuck me! ~Aaa!~ I need you so badly!")+")]",
								(this.hasStatusEffect(StatusEffect.TELEPATHIC_COMMUNICATION_POWER_OF_SUGGESTION)
										|| this.hasStatusEffect(StatusEffect.TELEPATHIC_COMMUNICATION_PROJECTED_TOUCH)
										?"You pout innocently at [npc.name], before blowing [npc.herHim] a wet kiss."
												+ " As you straighten back up, you project the feeling of a ghostly pair of wet lips pressing against [npc.her] cheek."
										:"")));
			} else {
				return UtilText.parse(target,
						UtilText.returnStringAtRandom(
								"You put on a confident look, and as your [pc.eyes] meet [npc.namePos], you project an extremely lewd groan into [npc.her] head,"
									+ " [pc.thought(~Mmm!~ "
											+(this.hasVagina()
													?"You're making me so wet!"
													:this.hasPenis()
														?"You're getting me so hard!"
														:"You're turning me on so much!")+")]",
								"You lock your [pc.eyes] with [npc.namePos], and, throwing [npc.herHim] a charming smile, you project an echoing groan deep into [npc.her] mind,"
									+ " [pc.thought("+
											(this.hasVagina()
													?"~Mmm!~ Fuck me! ~Aaa!~ My pussy's wet and ready for you!"
													:this.hasPenis()
														?"~Mmm!~ I can't wait to fuck you! You're going to love my cock!"
														:"~Mmm!~ I can't wait to have some fun with you!")+")]",
								(this.hasStatusEffect(StatusEffect.TELEPATHIC_COMMUNICATION_POWER_OF_SUGGESTION)
										|| this.hasStatusEffect(StatusEffect.TELEPATHIC_COMMUNICATION_PROJECTED_TOUCH)
										?"You throw [npc.name] a charming smile, before winking at [npc.herHim] and striking a heroic pose."
												+ " As you straighten back up, you project the feeling of a ghostly pair of arms pulling [npc.herHim] into a strong, confident embrace."
										:"")));
			}
		}

		if(this.isFeminine()) {
			description = UtilText.parse(target,
					UtilText.returnStringAtRandom(
					"You blow a kiss at [npc.name] and wink suggestively at [npc.herHim].",
					"Biting your lip and putting on your most smouldering look, you run your hands slowly up your inner thighs.",
					"As you give [npc.name] your most innocent look, you blow [npc.herHim] a little kiss.",
					"Turning around, you let out a playful giggle as you give your [pc.ass+] a slap.",
					"You slowly run your hands up the length of your body, before pouting at [npc.name]."));

		} else {
			description = UtilText.parse(target,
					UtilText.returnStringAtRandom(
					"You blow a kiss at [npc.name] and wink suggestively at [npc.herHim].",
					"Smiling confidently at [npc.name], you slowly run your hands up your inner thighs.",
					"As you give [npc.name] your most seductive look, you blow [npc.herHim] a little kiss.",
					"Turning around, you let out a playful laugh as you give your [pc.ass+] a slap.",
					"You try to look as commanding as possible as you smirk playfully at [npc.name]."));
		}

		return description;
	}

	@Override
	public boolean isAbleToBeImpregnated() {
		return true;
	}

	// This behaviour is overridden for unique scenes in which the player's orgasm requires special dialogue or effects.
	// At the time of this comment (v0.3.1), it's only used for Lilaya's creampie reaction and Lyssieth's demon TF scene.
	@Override
	public SexActionOrgasmOverride getSexActionOrgasmOverride(SexActionInterface sexAction, OrgasmCumTarget target, boolean applyExtraEffects) {
		if(Sex.getAllParticipants().contains(Main.game.getNpc(Lilaya.class))
				&& Main.game.getNpc(Lilaya.class).getFetishDesire(Fetish.FETISH_PREGNANCY).isNegative()
				&& target==OrgasmCumTarget.INSIDE
				&& !Main.game.getNpc(Lilaya.class).isVisiblyPregnant()
				&& this.getCurrentPenisRawCumStorageValue()>0
				&& Sex.getContactingSexAreas(this, SexAreaPenetration.PENIS, Main.game.getNpc(Lilaya.class)).contains(SexAreaOrifice.VAGINA)) {

			StringBuilder sb = new StringBuilder();
			sb.append(GenericOrgasms.getGenericOrgasmDescription(sexAction, this, target));

			if(this.isWearingCondom()) {
				if(sexAction.getCondomFailure(this, Main.game.getNpc(Lilaya.class))!=CondomFailure.NONE) {
					Main.game.getDialogueFlags().setFlag(DialogueFlagValue.lilayaCondomBroke, true);
					sb.append(UtilText.parseFromXMLFile("characters/dominion/lilaya", "ORGASM_REACTION_CREAMPIE_CONDOM_BROKE"));
				} else {
					Main.game.getDialogueFlags().setFlag(DialogueFlagValue.lilayaCondomBroke, false);
					sb.append(UtilText.parseFromXMLFile("characters/dominion/lilaya", "ORGASM_REACTION_CREAMPIE_CONDOM"));
				}
			} else {
				Main.game.getDialogueFlags().setFlag(DialogueFlagValue.lilayaCondomBroke, false);
				sb.append(UtilText.parseFromXMLFile("characters/dominion/lilaya", "ORGASM_REACTION_CREAMPIE"));
			}

			return new SexActionOrgasmOverride(false, sb.toString()) {
				@Override
				public void applyEffects() {
				}
				@Override
				public boolean isEndsSex() {
					return Main.game.getNpc(Lilaya.class).hasStatusEffect(StatusEffect.CREAMPIE_VAGINA)
							&& !Main.game.getNpc(Lilaya.class).isVisiblyPregnant()
							&& Main.game.getNpc(Lilaya.class).getFetishDesire(Fetish.FETISH_PREGNANCY).isNegative();
				}
			};
		}

		if(Sex.getSexManager() instanceof SMLyssiethDemonTF) { // Lyssieth's demon TF scene:
			StringBuilder sb = new StringBuilder();
			sb.append(GenericOrgasms.getGenericOrgasmDescription(sexAction, this, target));

			if(Sex.getLastUsedSexAction(Main.game.getNpc(Lyssieth.class)).getActionType()==SexActionType.ORGASM) { //These specials are only for follow-ups to Lyssieth's orgasms:
				if(Sex.getNumberOfOrgasms(Main.game.getNpc(Lyssieth.class))==1) {
					// Stage 1) Player is sucking Lyssieth's cock:
					if(Sex.getContactingSexAreas(this, SexAreaOrifice.MOUTH, Main.game.getNpc(Lyssieth.class)).contains(SexAreaPenetration.PENIS)) {
						sb.append(UtilText.parseFromXMLFile("characters/submission/lyssieth", "DEMON_TF_STAGE_1_PC_GIVING_LYSSIETH_BLOWJOB_END"));

						return new SexActionOrgasmOverride(false, sb.toString()) {
							@Override
							public void applyEffects() {
							}
						};

					// Stage 1) Lyssieth is sucking player's cock:
					} else if(Sex.getContactingSexAreas(this, SexAreaPenetration.PENIS, Main.game.getNpc(Lyssieth.class)).contains(SexAreaOrifice.MOUTH)) {
						sb.append(UtilText.parseFromXMLFile("characters/submission/lyssieth", "DEMON_TF_STAGE_1_PC_GETTING_BLOWJOB_FROM_LYSSIETH_END"));

						return new SexActionOrgasmOverride(false, sb.toString()) {
							@Override
							public void applyEffects() {
							}
						};

					// Stage 1) Lyssieth is eating the player out:
					} else if(Sex.getContactingSexAreas(this, SexAreaPenetration.TONGUE, Main.game.getNpc(Lyssieth.class)).contains(SexAreaOrifice.VAGINA)) {
						sb.append(UtilText.parseFromXMLFile("characters/submission/lyssieth", "DEMON_TF_STAGE_1_PC_GETTING_CUNNILINGUS_FROM_LYSSIETH_END"));

						return new SexActionOrgasmOverride(false, sb.toString()) {
							@Override
							public void applyEffects() {
							}
						};
					}

				} else if(Sex.getNumberOfOrgasms(Main.game.getNpc(Lyssieth.class))==2) {
					// Stage 2) Lyssieth is fucking the player:
					if(Sex.getContactingSexAreas(this, SexAreaOrifice.VAGINA, Main.game.getNpc(Lyssieth.class)).contains(SexAreaPenetration.PENIS)) {
						sb.append(UtilText.parseFromXMLFile("characters/submission/lyssieth", "DEMON_TF_STAGE_2_PC_PUSSY_FUCKED_BY_LYSSIETH_END"));

						return new SexActionOrgasmOverride(false, sb.toString()) {
							@Override
							public void applyEffects() {
							}
							@Override
							public void applyEndEffects() {
								if(applyExtraEffects) {
									Sex.stopAllOngoingActions(Main.game.getPlayer(), Main.game.getNpc(Lyssieth.class));
								}
							}
						};

					// Stage 2) Lyssieth is fucking the player's ass:
					} else if(Sex.getContactingSexAreas(this, SexAreaOrifice.ANUS, Main.game.getNpc(Lyssieth.class)).contains(SexAreaPenetration.PENIS)) {
						if(Main.game.getPlayer().hasPenis() && Main.game.getPlayer().getPenisType().getRace()==Race.DEMON && Main.game.getPlayer().getPenisRawSizeValue()<=4) {
							sb.append(UtilText.parseFromXMLFile("characters/submission/lyssieth", "DEMON_TF_STAGE_2_PC_ASS_FUCKED_BY_LYSSIETH_END_SISSY"));
						} else {
							sb.append(UtilText.parseFromXMLFile("characters/submission/lyssieth", "DEMON_TF_STAGE_2_PC_ASS_FUCKED_BY_LYSSIETH_END"));
						}

						return new SexActionOrgasmOverride(false, sb.toString()) {
							@Override
							public void applyEffects() {
							}
							@Override
							public void applyEndEffects() {
								if(applyExtraEffects) {
									Sex.stopAllOngoingActions(Main.game.getPlayer(), Main.game.getNpc(Lyssieth.class));
								}
							}
						};

					// Stage 2) The player is fucking Lyssieth:
					} else if(Sex.getContactingSexAreas(this, SexAreaPenetration.PENIS, Main.game.getNpc(Lyssieth.class)).contains(SexAreaOrifice.VAGINA)) {
						sb.append(UtilText.parseFromXMLFile("characters/submission/lyssieth", "DEMON_TF_STAGE_2_PC_FUCKING_LYSSIETH_END"));

						return new SexActionOrgasmOverride(false, sb.toString()) {
							@Override
							public void applyEffects() {
							}
							@Override
							public void applyEndEffects() {
								if(applyExtraEffects) {
									Sex.stopAllOngoingActions(Main.game.getPlayer(), Main.game.getNpc(Lyssieth.class));
								}
							}
						};
					}

				} else if(Sex.getNumberOfOrgasms(Main.game.getNpc(Lyssieth.class))==3) {
					// Stage 3) Player is fucking/breeding Lyssieth:
					if(Sex.getContactingSexAreas(this, SexAreaPenetration.PENIS, Main.game.getNpc(Lyssieth.class)).contains(SexAreaOrifice.VAGINA)) {
						if(Sex.getSexPositionSlot(Main.game.getPlayer())==SexSlotLyingDown.MATING_PRESS) {
							sb.append(UtilText.parseFromXMLFile("characters/submission/lyssieth", "DEMON_TF_FINAL_PC_BREEDING_LYSSIETH_END"));
						} else {
							sb.append(UtilText.parseFromXMLFile("characters/submission/lyssieth", "DEMON_TF_FINAL_PC_FUCKING_LYSSIETH_END"));
						}

						return new SexActionOrgasmOverride(true, sb.toString()) {
							@Override
							public void applyEffects() {
							}
						};

					// Stage 3) Lyssieth is fucking the player:
					} else if(Sex.getContactingSexAreas(this, SexAreaOrifice.VAGINA, Main.game.getNpc(Lyssieth.class)).contains(SexAreaPenetration.PENIS)) {
						sb.append(UtilText.parseFromXMLFile("characters/submission/lyssieth", "DEMON_TF_FINAL_PC_PUSSY_FUCKED_BY_LYSSIETH_END"));

						return new SexActionOrgasmOverride(true, sb.toString()) {
							@Override
							public void applyEffects() {
							}
						};

					// Stage 3) Lyssieth is fucking the player's ass:
					} else if(Sex.getContactingSexAreas(this, SexAreaOrifice.ANUS, Main.game.getNpc(Lyssieth.class)).contains(SexAreaPenetration.PENIS)) {
						sb.append(UtilText.parseFromXMLFile("characters/submission/lyssieth", "DEMON_TF_FINAL_PC_ASS_FUCKED_BY_LYSSIETH_END"));

						return new SexActionOrgasmOverride(true, sb.toString()) {
							@Override
							public void applyEffects() {
							}
						};

					// Stage 3) Lyssieth is sucking player's cock:
					} else if(Sex.getContactingSexAreas(this, SexAreaPenetration.PENIS, Main.game.getNpc(Lyssieth.class)).contains(SexAreaOrifice.MOUTH)) {
						sb.append(UtilText.parseFromXMLFile("characters/submission/lyssieth", "DEMON_TF_FINAL_PC_GETTING_BLOWJOB_FROM_LYSSIETH_END"));

						return new SexActionOrgasmOverride(true, sb.toString()) {
							@Override
							public void applyEffects() {
							}
						};

					// Stage 3) Lyssieth is eating the player out:
					} else if(Sex.getContactingSexAreas(this, SexAreaPenetration.TONGUE, Main.game.getNpc(Lyssieth.class)).contains(SexAreaOrifice.VAGINA)) {
						sb.append(UtilText.parseFromXMLFile("characters/submission/lyssieth", "DEMON_TF_FINAL_PC_GETTING_CUNNILINGUS_FROM_LYSSIETH_END"));

						return new SexActionOrgasmOverride(true, sb.toString()) {
							@Override
							public void applyEffects() {
							}
						};
					}
				}
			}
		}

		return super.getSexActionOrgasmOverride(sexAction, target, applyExtraEffects); // Normal scene
	}

	/**
	 * Returns a list of NPCs either living in Lilaya's house or in an apartment known to the player.
	 */
	public List<String> getFriendlyOccupants() {
		return friendlyOccupants;
	}

	public boolean addFriendlyOccupant(NPC occupant) {
		return friendlyOccupants.add(occupant.getId());
	}

	public boolean removeFriendlyOccupant(GameCharacter occupant) {
		return friendlyOccupants.remove(occupant.getId());
	}

	public Set<WorldType> getWorldsVisited() {
		return worldsVisited;
	}

	public boolean isDiscoveredWorldMap() {
		return this.isQuestProgressGreaterThan(QuestLine.MAIN, Quest.MAIN_2_D_MEETING_A_LILIN);
	}
}


code:
package com.lilithsthrone.game.character;

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

import com.lilithsthrone.game.character.npc.misc.GenericFemaleNPC;
import com.lilithsthrone.main.Main;
import com.lilithsthrone.utils.Util;
import com.lilithsthrone.utils.XMLSaving;

/**
 * @since 0.1.66
 * @version 0.3
 * @author Innoxia
 */
public class PregnancyPossibility implements XMLSaving {

	private String motherId, fatherId;
	private float probability;

	public PregnancyPossibility(String motherId, String fatherId, float probability) {
		this.motherId = motherId;
		this.fatherId = fatherId;
		this.probability = probability;
	}

	@Override
	public boolean equals(Object o) {
		return (o instanceof PregnancyPossibility)
				&& ((PregnancyPossibility)o).getMotherId().equals(motherId)
				&& ((PregnancyPossibility)o).getFatherId().equals(fatherId)
				&& ((PregnancyPossibility)o).getProbability() == probability;
	}

	@Override
	public int hashCode() {
		int result = 17;
		result = 31 * result + motherId.hashCode();
		result = 31 * result + fatherId.hashCode();
		result = 31 * result + (int)probability;
		return result;
	}

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

		CharacterUtils.addAttribute(doc, element, "motherId", this.getMotherId());
		CharacterUtils.addAttribute(doc, element, "fatherId", this.getFatherId());
		CharacterUtils.addAttribute(doc, element, "probability", String.valueOf(this.getProbability()));

		return element;
	}

	public static PregnancyPossibility loadFromXML(Element parentElement, Document doc) {
		return new PregnancyPossibility(
				parentElement.getAttribute("motherId"),
				parentElement.getAttribute("fatherId"),
				Float.valueOf(parentElement.getAttribute("probability")));
	}

	public String getMotherId() {
		return motherId;
	}

	public String getFatherId() {
		return fatherId;
	}

	public GameCharacter getMother() {
		try {
			return Main.game.getNPCById(motherId);
		} catch (Exception e) {
			if(!motherId.equals("NOT_SET")) {
				Util.logGetNpcByIdError("PregnancyPossibility.getMother()", motherId);
			}
			return Main.game.getNpc(GenericFemaleNPC.class);
		}
	}

	public GameCharacter getFather() {
		try {
			return Main.game.getNPCById(fatherId);
		} catch (Exception e) {
			return null;
		}
	}

	public float getProbability() {
		return probability;
	}

	public void setProbability(float probability) {
		this.probability = probability;
	}

}


code:
package com.lilithsthrone.game.character;

/**
 * @since 0.2.9
 * @version 0.2.9
 * @author Innoxia
 */
public class SexCount {

	private int sexConsensualCount;
	private int sexAsSubCount;
	private int sexAsDomCount;

	public SexCount() {
		sexConsensualCount = 0;
		sexAsSubCount = 0;
		sexAsDomCount = 0;
	}

	public int getSexConsensualCount() {
		return sexConsensualCount;
	}
	public void setSexConsensualCount(int sexConsensualCount) {
		this.sexConsensualCount = sexConsensualCount;
	}
	public int getSexAsSubCount() {
		return sexAsSubCount;
	}
	public void setSexAsSubCount(int sexAsSubCount) {
		this.sexAsSubCount = sexAsSubCount;
	}
	public int getSexAsDomCount() {
		return sexAsDomCount;
	}
	public void setSexAsDomCount(int sexAsDomCount) {
		this.sexAsDomCount = sexAsDomCount;
	}
	public int getTotalTimesHadSex() {
		return getSexAsDomCount() + getSexAsSubCount();
	}
}


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

import com.lilithsthrone.game.character.GameCharacter;
import com.lilithsthrone.game.dialogue.utils.UtilText;
import com.lilithsthrone.utils.Colour;

/**
 * @since 0.1.78
 * @version 0.1.86
 * @author Innoxia
 */
public enum AffectionLevel {

	/** -100 to -90*/
	NEGATIVE_FIVE_LOATHE("loathing", "loathes", -100, -90, Colour.AFFECTION_NEGATIVE_FIVE),

	/** -90 to -70*/
	NEGATIVE_FOUR_HATE("hatred", "hates", -90, -70, Colour.AFFECTION_NEGATIVE_FOUR),

	/** -70 to -50*/
	NEGATIVE_THREE_STRONG_DISLIKE("strong dislike", "strongly dislikes", -70, -50, Colour.AFFECTION_NEGATIVE_THREE),

	/** -50 to -30*/
	NEGATIVE_TWO_DISLIKE("dislike", "dislikes", -50, -30, Colour.AFFECTION_NEGATIVE_TWO),

	/** -30 to -10*/
	NEGATIVE_ONE_ANNOYED("annoyed", "is annoyed with", -30, -10, Colour.AFFECTION_NEGATIVE_ONE),

	/** -10 to 10*/
	ZERO_NEUTRAL("neutral", "neither likes nor dislikes", -10, 10, Colour.AFFECTION_NEUTRAL),

	/** 10 to 30*/
	POSITIVE_ONE_FRIENDLY("friendly", "is friendly towards", 10, 30, Colour.AFFECTION_POSITIVE_ONE),

	/** 30 to 50*/
	POSITIVE_TWO_LIKE("likes", "likes", 30, 50, Colour.AFFECTION_POSITIVE_TWO),

	/** 50 to 70*/
	POSITIVE_THREE_CARING("caring", "cares for", 50, 70, Colour.AFFECTION_POSITIVE_THREE),

	/** 70 to 90*/
	POSITIVE_FOUR_LOVE("love", "loves", 70, 90, Colour.AFFECTION_POSITIVE_FOUR),

	/** 90 to 100*/
	POSITIVE_FIVE_WORSHIP("worshipping", "worships", 90, 100, Colour.AFFECTION_POSITIVE_FIVE);

	private String name;
	private String descriptor;
	private int minimumValue, maximumValue;
	private Colour colour;

	private AffectionLevel(String name, String descriptor, int minimumValue, int maximumValue, Colour colour) {
		this.name = name;
		this.descriptor = descriptor;
		this.minimumValue = minimumValue;
		this.maximumValue = maximumValue;
		this.colour = colour;
	}

	private static StringBuilder sb = new StringBuilder();
	public static String getDescription(GameCharacter character, GameCharacter target, AffectionLevel affectionLevel, boolean withColour) {
		sb.setLength(0);

		switch(affectionLevel) {
			case NEGATIVE_FIVE_LOATHE:
				if(withColour) {
					sb.append(UtilText.parse(character, target, "[npc.Name] [npc.verb(loathe)] [npc2.name]."));
				} else {
					sb.append(UtilText.parse(character, target, "[npc.Name] [npc.verb(loathe)] [npc2.name]."));
				}
				break;
			case NEGATIVE_FOUR_HATE:
				if(withColour) {
					sb.append(UtilText.parse(character, target, "[npc.Name] [npc.verb(hate)] [npc2.name]."));
				} else {
					sb.append(UtilText.parse(character, target, "[npc.Name] [npc.verb(hate)] [npc2.name]."));
				}
				break;
			case NEGATIVE_THREE_STRONG_DISLIKE:
				if(withColour) {
					sb.append(UtilText.parse(character, target, "[npc.Name] strongly [npc.verb(dislike)] [npc2.name]."));
				} else {
					sb.append(UtilText.parse(character, target, "[npc.Name] strongly [npc.verb(dislike)] [npc2.name]."));
				}
				break;
			case NEGATIVE_TWO_DISLIKE:
				if(withColour) {
					sb.append(UtilText.parse(character, target, "[npc.Name] [npc.verb(dislike)] [npc2.name]."));
				} else {
					sb.append(UtilText.parse(character, target, "[npc.Name] [npc.verb(dislike)] [npc2.name]."));
				}
				break;
			case NEGATIVE_ONE_ANNOYED:
				if(withColour) {
					sb.append(UtilText.parse(character, target, "[npc.Name] [npc.is] annoyed with [npc2.name]."));
				} else {
					sb.append(UtilText.parse(character, target, "[npc.Name] [npc.is] annoyed with [npc2.name]."));
				}
				break;
			case ZERO_NEUTRAL:
				if(withColour) {
					sb.append(UtilText.parse(character, target, "[npc.Name] [npc.is] indifferent towards [npc2.name]."));
				} else {
					sb.append(UtilText.parse(character, target, "[npc.Name] [npc.is] indifferent towards [npc2.name]."));
				}
				break;
			case POSITIVE_ONE_FRIENDLY:
				if(withColour) {
					sb.append(UtilText.parse(character, target, "[npc.Name] [npc.is] friendly towards [npc2.name]."));
				} else {
					sb.append(UtilText.parse(character, target, "[npc.Name] [npc.is] friendly towards [npc2.name]."));
				}
				break;
			case POSITIVE_TWO_LIKE:
				if(withColour) {
					sb.append(UtilText.parse(character, target, "[npc.Name] [npc.verb(like)] [npc2.name]."));
				} else {
					sb.append(UtilText.parse(character, target, "[npc.Name] [npc.verb(like)] [npc2.name]."));
				}
				break;
			case POSITIVE_THREE_CARING:
				if(withColour) {
					sb.append(UtilText.parse(character, target, "[npc.Name] [npc.verb(care)] about [npc2.name]."));
				} else {
					sb.append(UtilText.parse(character, target, "[npc.Name] [npc.verb(care)] about [npc2.name]."));
				}
				break;
			case POSITIVE_FOUR_LOVE:
				if(withColour) {
					sb.append(UtilText.parse(character, target, "[npc.Name] [npc.verb(love)] [npc2.name]."));
				} else {
					sb.append(UtilText.parse(character, target, "[npc.Name] [npc.verb(love)] [npc2.name]."));
				}
				break;
			case POSITIVE_FIVE_WORSHIP:
				if(withColour) {
					sb.append(UtilText.parse(character, target, "[npc.Name] [npc.verb(worship)] [npc2.name]."));
				} else {
					sb.append(UtilText.parse(character, target, "[npc.Name] [npc.verb(worship)] [npc2.name]."));
				}
				break;
		}

		return sb.toString();
	}

	public String getName() {
		return name;
	}

	/**
	 * To fit into a sentence such as:

	 * "Due to the fact that Kate "+getDescriptor()+" you..."
	 * @return
	 */
	public String getDescriptor() {
		return descriptor;
	}

	public int getMinimumValue() {
		return minimumValue;
	}

	public int getMaximumValue() {
		return maximumValue;
	}

	public int getMedianValue() {
		return (minimumValue + maximumValue) / 2;
	}

	public Colour getColour() {
		return colour;
	}

	public static AffectionLevel getAffectionLevelFromValue(float value){
		for(AffectionLevel al : AffectionLevel.values()) {
			if(value>=al.getMinimumValue() && value<al.getMaximumValue())
				return al;
		}
		return POSITIVE_FIVE_WORSHIP;
	}
}


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

import com.lilithsthrone.utils.Colour;

/**
 * @since 0.1.95
 * @version 0.1.95
 * @author Innoxia
 */
public enum AffectionLevelBasic {

	/** -100 to -30*/
	DISLIKE("dislikes", -100, -30, Colour.AFFECTION_NEGATIVE_TWO),

	/** -30 to 30*/
	NEUTRAL("neutral", -30, 30, Colour.AFFECTION_POSITIVE_ONE),

	/** -30 to 100*/
	LIKE("likes", 30, 100, Colour.AFFECTION_POSITIVE_FIVE);

	private String name;
	private int minimumValue, maximumValue;
	private Colour colour;

	private AffectionLevelBasic(String name, int minimumValue, int maximumValue, Colour colour) {
		this.name = name;
		this.minimumValue = minimumValue;
		this.maximumValue = maximumValue;
		this.colour = colour;
	}

	public String getName() {
		return name;
	}

	public int getMinimumValue() {
		return minimumValue;
	}

	public int getMaximumValue() {
		return maximumValue;
	}

	public int getMedianValue() {
		return (minimumValue + maximumValue) / 2;
	}

	public Colour getColour() {
		return colour;
	}

	public static AffectionLevelBasic getAffectionLevelFromValue(float value){
		for(AffectionLevelBasic al : AffectionLevelBasic.values()) {
			if(value>=al.getMinimumValue() && value<al.getMaximumValue())
				return al;
		}
		return LIKE;
	}
}


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

import com.lilithsthrone.utils.Colour;

/**
 * @since 0.2.8
 * @version 0.2.8
 * @author Innoxia
 */
public enum AlcoholLevel {

	ZERO_SOBER("sober", 0, 0, 0.01f, Colour.ALCOHOL_LEVEL_ZERO),

	ONE_TIPSY("tipsy", 0, 0.01f, 0.2f, Colour.ALCOHOL_LEVEL_ONE),

	TWO_MERRY("merry", 25, 0.2f, 0.4f, Colour.ALCOHOL_LEVEL_TWO),

	THREE_DRUNK("drunk", 20, 0.4f, 0.6f, Colour.ALCOHOL_LEVEL_THREE),

	FOUR_HAMMERED("hammered", 15, 0.6f, 0.8f, Colour.ALCOHOL_LEVEL_FOUR),

	FIVE_WASTED("wasted", 10, 0.8f, 1, Colour.ALCOHOL_LEVEL_FIVE);

	private String name;
	private float minimumValue;
	private float maximumValue;
	private Colour colour;
	private int slurredSpeechFrequency;

	private AlcoholLevel(String name, int slurredSpeechFrequency, float minimumValue, float maximumValue, Colour colour) {
		this.name = name;
		this.slurredSpeechFrequency = slurredSpeechFrequency;
		this.minimumValue = minimumValue;
		this.maximumValue = maximumValue;
		this.colour = colour;
	}

	public String getName() {
		return name;
	}
	public int getSlurredSpeechFrequency() {
		return slurredSpeechFrequency;
	}

	public float getMinimumValue() {
		return minimumValue;
	}

	public float getMaximumValue() {
		return maximumValue;
	}

	public Colour getColour() {
		return colour;
	}

	public static AlcoholLevel getAlcoholLevelFromValue(float value){
		for(AlcoholLevel al : AlcoholLevel.values()) {
			if(value>=al.getMinimumValue() && value<al.getMaximumValue())
				return al;
		}
		return FIVE_WASTED;
	}
}


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

import com.lilithsthrone.game.character.effects.StatusEffect;
import com.lilithsthrone.utils.Colour;

/**
 * @since 0.1.65
 * @version 0.1.97
 * @author Innoxia
 */
public enum ArousalLevel {

	ZERO_NONE("none", 0, 10, Colour.AROUSAL_STAGE_ZERO) {
		@Override
		public StatusEffect getRelatedStatusEffect() {
			return StatusEffect.AROUSAL_PERK_0;
		}
	},

	ONE_TURNED_ON("turned on", 10, 25, Colour.AROUSAL_STAGE_ONE) {
		@Override
		public StatusEffect getRelatedStatusEffect() {
			return StatusEffect.AROUSAL_PERK_1;
		}
	},

	TWO_EXCITED("excited", 25, 50, Colour.AROUSAL_STAGE_TWO) {
		@Override
		public StatusEffect getRelatedStatusEffect() {
			return StatusEffect.AROUSAL_PERK_2;
		}
	},

	THREE_HEATED("heated", 50, 80, Colour.AROUSAL_STAGE_THREE) {
		@Override
		public StatusEffect getRelatedStatusEffect() {
			return StatusEffect.AROUSAL_PERK_3;
		}
	},

	FOUR_PASSIONATE("passionate", 80, 95, Colour.AROUSAL_STAGE_FOUR) {
		@Override
		public StatusEffect getRelatedStatusEffect() {
			return StatusEffect.AROUSAL_PERK_4;
		}
		@Override
		public boolean isMutualOrgasm() {
			return true;
		}
	},

	FIVE_ORGASM_IMMINENT("imminent orgasm", 95, 100, Colour.AROUSAL_STAGE_FIVE) {
		@Override
		public StatusEffect getRelatedStatusEffect() {
			return StatusEffect.AROUSAL_PERK_5;
		}
		@Override
		public boolean isMutualOrgasm() {
			return true;
		}
	};

	private String name;
	private int minimumValue, maximumValue;
	private Colour colour;

	private ArousalLevel(String name, int minimumValue, int maximumValue, Colour colour) {
		this.name = name;
		this.minimumValue = minimumValue;
		this.maximumValue = maximumValue;
		this.colour = colour;
	}

	public abstract StatusEffect getRelatedStatusEffect();

	public String getName() {
		return name;
	}

	public int getMinimumValue() {
		return minimumValue;
	}

	public int getMaximumValue() {
		return maximumValue;
	}

	public int getMedianValue() {
		return (minimumValue + maximumValue) / 2;
	}

	public Colour getColour() {
		return colour;
	}

	public static ArousalLevel getArousalLevelFromValue(float value){
		for(ArousalLevel al : ArousalLevel.values()) {
			if(value>=al.getMinimumValue() && value<al.getMaximumValue()) {
				return al;
			}
		}
		return FIVE_ORGASM_IMMINENT;
	}

	public boolean isMutualOrgasm() {
		return false;
	}
}