code:
package com.lilithsthrone.game.sex;

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

import com.lilithsthrone.utils.Util;

/**
 * @since 0.2.0
 * @version 0.3.3.10
 * @author Innoxia
 */
public class SexActionInteractions {

	private Map<SexAreaInterface, List<SexAreaInterface>> interactions;

	private List<OrgasmCumTarget> availableCumTargets;
	private List<OrgasmCumTarget> providedCumTargets;

//	@Deprecated
	public SexActionInteractions(
			Map<SexAreaInterface, List<SexAreaInterface>> interactions,
			List<OrgasmCumTarget> availableCumTargets) {
		this(interactions, availableCumTargets, null);
	}

	/**
	 * A container for sex-interaction data.
	 *
	 * @param interactions A map of performer sex areas to the target's available sex areas.
	 * @param availableCumTargets A list of cum targets that the performer can use when orgasming.
	 * @param providedCumTargets A list of cum targets that this performer makes available to the target when the target is orgasming.
	 */
	public SexActionInteractions(
			Map<SexAreaInterface, List<SexAreaInterface>> interactions,
			List<OrgasmCumTarget> availableCumTargets,
			List<OrgasmCumTarget> providedCumTargets) {

		if(interactions==null) {
			this.interactions = new HashMap<>();
		} else {
			this.interactions = interactions;
		}

		if(availableCumTargets==null) {
			this.availableCumTargets = Util.newArrayListOfValues(OrgasmCumTarget.FLOOR);
		} else {
			this.availableCumTargets = availableCumTargets;
		}

		if(providedCumTargets==null) {
			this.providedCumTargets = Util.newArrayListOfValues(OrgasmCumTarget.FLOOR);
		} else {
			this.providedCumTargets = providedCumTargets;
		}
	}

	/**
	 * @return interactions map. The keys correspond to the performing characters area, while the values are lists of areas (owned by the targeted character) that can be targeted by the key.
	 */
	public Map<SexAreaInterface, List<SexAreaInterface>> getInteractions() {
		return interactions;
	}

	public void setInteractions(Map<SexAreaInterface, List<SexAreaInterface>> interactions) {
		this.interactions = interactions;
	}

	public List<OrgasmCumTarget> getAvailableCumTargets() {
		return availableCumTargets;
	}

	public void setAvailableCumTargets(List<OrgasmCumTarget> availableCumTargets) {
		this.availableCumTargets = availableCumTargets;
	}

	public List<OrgasmCumTarget> getProvidedCumTargets() {
		return providedCumTargets;
	}
}

repo_name: klorpa/Pedo

license: unknown


code:
package com.lilithsthrone.game.sex;

import com.lilithsthrone.game.character.GameCharacter;
import com.lilithsthrone.game.character.body.CoverableArea;

/**
 * @since 0.2.7
 * @version 0.2.7
 * @author Innoxia
 */
public interface SexAreaInterface {

	public boolean isOrifice();

	public CoverableArea getRelatedCoverableArea();

	public String getName(GameCharacter owner);

	public boolean isFree(GameCharacter owner);

	public default boolean isPenetration() {
		return !isOrifice();
	}

	public default boolean isPlural() {
		return false;
	}

}


code:
package com.lilithsthrone.game.sex;

import com.lilithsthrone.game.character.GameCharacter;
import com.lilithsthrone.game.character.body.CoverableArea;

/**
 * @since 0.1.78
 * @version 0.3.1
 * @author Innoxia
 */
public enum SexAreaOrifice implements SexAreaInterface {

	MOUTH(2,
			-0.5f, -0.5f, -1f,
			0.5f, -0.5f ,-1f,
			2/60f, 15/60f,
			true) {
		@Override
		public String getName(GameCharacter owner) {
			return "mouth";
		}
		@Override
		public boolean isFree(GameCharacter owner) {
			return Sex.isOrificeFree(owner, this) && Sex.isPenetrationTypeFree(owner, SexAreaPenetration.TONGUE);
		}
		@Override
		public CoverableArea getRelatedCoverableArea() {
			return CoverableArea.MOUTH;
		}
	},

	NIPPLE(2,
			-0.5f, -0.5f, -1f,
			0.5f, -0.5f ,-1f,
			4/60f, 2/60f,
			true) {
		@Override
		public String getName(GameCharacter owner) {
			return owner.getNippleName();
		}
		@Override
		public boolean isPlural() {
			return true;
		}
		@Override
		public boolean isFree(GameCharacter owner) {
			return Sex.isOrificeFree(owner, this);
		}
		@Override
		public CoverableArea getRelatedCoverableArea() {
			return CoverableArea.NIPPLES;
		}
	},

	BREAST(1,
			-0.5f, -0.5f, -1f,
			0.5f, -0.5f ,-1f,
			25/60f, 0,
			false) {
		@Override
		public String getName(GameCharacter owner) {
			return owner.getBreastName();
		}
		@Override
		public boolean isPlural() {
			return true;
		}
		@Override
		public boolean isFree(GameCharacter owner) {
			return Sex.isOrificeFree(owner, this);
		}
		@Override
		public CoverableArea getRelatedCoverableArea() {
			return CoverableArea.BREASTS;
		}
	},

	NIPPLE_CROTCH(2,
			-0.5f, -0.5f, -1f,
			0.5f, -0.5f ,-1f,
			4/60f, 2/60f,
			true) {
		@Override
		public String getName(GameCharacter owner) {
			return owner.getNippleCrotchName();
		}
		@Override
		public boolean isPlural() {
			return true;
		}
		@Override
		public boolean isFree(GameCharacter owner) {
			return Sex.isOrificeFree(owner, this);
		}
		@Override
		public CoverableArea getRelatedCoverableArea() {
			return CoverableArea.NIPPLES_CROTCH;
		}
	},

	BREAST_CROTCH(1,
			-0.5f, -0.5f, -1f,
			0.5f, -0.5f ,-1f,
			25/60f, 0,
			false) {
		@Override
		public String getName(GameCharacter owner) {
			return owner.getBreastCrotchName();
		}
		@Override
		public boolean isPlural() {
			return true;
		}
		@Override
		public boolean isFree(GameCharacter owner) {
			return Sex.isOrificeFree(owner, this);
		}
		@Override
		public CoverableArea getRelatedCoverableArea() {
			return CoverableArea.BREASTS_CROTCH;
		}
	},

	ASS(1,
			-0.5f, -0.5f, -1f,
			0.5f, -0.5f ,-1f,
			25/60f, 0,
			false) {
		@Override
		public String getName(GameCharacter owner) {
			return "ass cheeks";
//			return owner.getAssName(false);
		}
		@Override
		public boolean isFree(GameCharacter owner) {
			return Sex.isOrificeFree(owner, this);
		}
		@Override
		public CoverableArea getRelatedCoverableArea() {
			return CoverableArea.ASS;
		}
	},

	ANUS(2,
			-0.5f, -0.5f, -1f,
			0.5f, -0.5f ,-1f,
			4/60f, 4/60f,
			true) {
		@Override
		public String getName(GameCharacter owner) {
			return owner.getAnusName(false);
		}
		@Override
		public boolean isFree(GameCharacter owner) {
			return Sex.isOrificeFree(owner, this);
		}
		@Override
		public CoverableArea getRelatedCoverableArea() {
			return CoverableArea.ANUS;
		}
	},

	VAGINA(4,
			-0.5f, -0.5f, -1f,
			0.5f, -0.5f ,-1f,
			4/60f, 2/60f,
			true) {
		@Override
		public String getName(GameCharacter owner) {
			return owner.getVaginaName(false);
		}
		@Override
		public boolean isFree(GameCharacter owner) {
			return Sex.isOrificeFree(owner, this);
		}
		@Override
		public CoverableArea getRelatedCoverableArea() {
			return CoverableArea.VAGINA;
		}
	},

	THIGHS(1,
			-0.5f, -0.5f, -1f,
			0.5f, -0.5f ,-1f,
			25/60f, 0,
			false) {
		@Override
		public String getName(GameCharacter owner) {
			return "thighs";
		}
		@Override
		public boolean isFree(GameCharacter owner) {
			return Sex.isOrificeFree(owner, this);
		}
		@Override
		public boolean isPlural() {
			return true;
		}
		@Override
		public CoverableArea getRelatedCoverableArea() {
			return CoverableArea.THIGHS;
		}
	},

	URETHRA_VAGINA(1,
			-0.5f, -0.5f, -1f,
			0.5f, -0.5f ,-1f,
			4/60f, 2/60f,
			true) {
		@Override
		public String getName(GameCharacter owner) {
			return "urethra";
		}
		@Override
		public boolean isFree(GameCharacter owner) {
			return Sex.isOrificeFree(owner, this);
		}
		@Override
		public CoverableArea getRelatedCoverableArea() {
			return CoverableArea.VAGINA;
		}
	},

	URETHRA_PENIS(1,
			-0.5f, -0.5f, -1f,
			0.5f, -0.5f ,-1f,
			4/60f, 2/60f,
			true) {
		@Override
		public String getName(GameCharacter owner) {
			return "urethra";
		}
		@Override
		public boolean isFree(GameCharacter owner) {
			return Sex.isOrificeFree(owner, this) && Sex.isPenetrationTypeFree(owner, SexAreaPenetration.PENIS);
		}
		@Override
		public CoverableArea getRelatedCoverableArea() {
			return CoverableArea.PENIS;
		}
	};

	private float baseArousalWhenPenetrated;
	private float arousalChangePenetratedStretching;
	private float arousalChangePenetratedTooLoose;
	private float arousalChangePenetratedDry;
	private float arousalChangePenetratingStretching;
	private float arousalChangePenetratingTooLoose;
	private float arousalChangePenetratingDry;
	private float cumLossPerSecond;
	private float cumAbsorptionPerSecond;
	private boolean takesPenisVirginity;

	/**
	 * @param baseArousalWhenPenetrated
	 * @param arousalChangePenetratedStretching
	 * @param arousalChangePenetratedTooLoose
	 * @param arousalChangePenetratedDry
	 * @param arousalChangePenetratingStretching
	 * @param arousalChangePenetratingTooLoose
	 * @param arousalChangePenetratingDry
	 * @param cumLossPerSecond The amount of cum or other fluids that leak out of this orifice every second.
	 * @param cumAbsorptionPerSecond The amount of cum or other fluids that are absorbed into the character's body through this orifice every second.
	 * @param takesPenisVirginity
	 */
	private SexAreaOrifice(float baseArousalWhenPenetrated,
			float arousalChangePenetratedStretching,
			float arousalChangePenetratedTooLoose,
			float arousalChangePenetratedDry,
			float arousalChangePenetratingStretching,
			float arousalChangePenetratingTooLoose,
			float arousalChangePenetratingDry,
			float cumLossPerSecond,
			float cumAbsorptionPerSecond,
			boolean takesPenisVirginity) {
		this.baseArousalWhenPenetrated = baseArousalWhenPenetrated;
		this.arousalChangePenetratedStretching = arousalChangePenetratedStretching;
		this.arousalChangePenetratedTooLoose = arousalChangePenetratedTooLoose;
		this.arousalChangePenetratedDry = arousalChangePenetratedDry;
		this.arousalChangePenetratingStretching = arousalChangePenetratingStretching;
		this.arousalChangePenetratingTooLoose = arousalChangePenetratingTooLoose;
		this.arousalChangePenetratingDry = arousalChangePenetratingDry;
		this.cumLossPerSecond = cumLossPerSecond;
		this.cumAbsorptionPerSecond = cumAbsorptionPerSecond;
		this.takesPenisVirginity = takesPenisVirginity;
	}

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

	public float getBaseArousalWhenPenetrated() {
		return baseArousalWhenPenetrated;
	}

	public float getArousalChangePenetratedStretching() {
		return arousalChangePenetratedStretching;
	}

	public float getArousalChangePenetratedTooLoose() {
		return arousalChangePenetratedTooLoose;
	}

	public float getArousalChangePenetratedDry() {
		return arousalChangePenetratedDry;
	}

	public float getArousalChangePenetratingStretching() {
		return arousalChangePenetratingStretching;
	}

	public float getArousalChangePenetratingTooLoose() {
		return arousalChangePenetratingTooLoose;
	}

	public float getArousalChangePenetratingDry() {
		return arousalChangePenetratingDry;
	}

	public float getCumLossPerSecond() {
		return cumLossPerSecond;
	}

	public float getCumAbsorptionPerSecond() {
		return cumAbsorptionPerSecond/60f;
	}

	/**
	 * @return true If this orifice is a fully internal orifice, capable of taking penile virginity.

	 * Mouth, vagina, anus, urethras, and nipple are considered internal orifices.

	 * Ass, breasts, and thighs are not.
	 */
	public boolean isInternalOrifice() {
		return takesPenisVirginity;
	}

	public float getCharactersCumLossPerSecond(GameCharacter target) {
		float cumLost = this.getCumAbsorptionPerSecond();
		float fluidInArea = target.getTotalFluidInArea(this);
		// The rate obviously decreases as the fluid drains out, but assuming if the drain was applied all at once, it would take about 5.5 hours to all drain out (not factoring in absorption or natural loss):
		float secondPercentageLoss = fluidInArea/20_000;

		if(!target.isOrificePlugged(this)) {
			cumLost += this.getCumLossPerSecond() + secondPercentageLoss;
		}
		return cumLost;
	}

}


code:
package com.lilithsthrone.game.sex;

import com.lilithsthrone.game.character.GameCharacter;
import com.lilithsthrone.game.character.body.CoverableArea;

/**
 * @since 0.1.0
 * @version 0.2.7
 * @author Innoxia
 */
public enum SexAreaPenetration implements SexAreaInterface {

	PENIS(4, -2f, true) {
		@Override
		public String getName(GameCharacter owner) {
			return owner.getPenisName();
		}

		@Override
		public boolean isFree(GameCharacter owner) {
			return Sex.isOrificeFree(owner, SexAreaOrifice.URETHRA_PENIS) && Sex.isPenetrationTypeFree(owner, this);
		}

		@Override
		public CoverableArea getRelatedCoverableArea() {
			return CoverableArea.PENIS;
		}
	},

	CLIT(4, -2f, true) {
		@Override
		public String getName(GameCharacter owner) {
			return "clit";
		}

		@Override
		public boolean isFree(GameCharacter owner) {
			return Sex.isPenetrationTypeFree(owner, this);
		}

		@Override
		public CoverableArea getRelatedCoverableArea() {
			return CoverableArea.VAGINA;
		}
	},

	TONGUE(2, 0, false) {
		@Override
		public String getName(GameCharacter owner) {
			return owner.getTongueName();
		}

		@Override
		public boolean isFree(GameCharacter owner) {
			return Sex.isOrificeFree(owner, SexAreaOrifice.MOUTH) && Sex.isPenetrationTypeFree(owner, this);
		}

		@Override
		public CoverableArea getRelatedCoverableArea() {
			return CoverableArea.MOUTH;
		}
	},

	FINGER(1, 0, false) {
		@Override
		public String getName(GameCharacter owner) {
			return owner.getArmType().getFingersNamePlural(owner);
		}
		@Override
		public boolean isPlural() {
			return true;
		}
		@Override
		public boolean isFree(GameCharacter owner) {
			return Sex.isPenetrationTypeFree(owner, this);
		}
		@Override
		public CoverableArea getRelatedCoverableArea() {
			return CoverableArea.NONE;
		}
	},

	FOOT(1, 0, false) {
		@Override
		public String getName(GameCharacter owner) {
			return owner.getLegType().getFootNameSingular(owner);
		}
		@Override
		public boolean isPlural() {
			return true;
		}
		@Override
		public boolean isFree(GameCharacter owner) {
			return Sex.isPenetrationTypeFree(owner, this);
		}
		@Override
		public CoverableArea getRelatedCoverableArea() {
			return CoverableArea.FEET;
		}
	},

	TAIL(2, -1f, true) {
		@Override
		public String getName(GameCharacter owner) {
			return owner.getTailName();
		}

		@Override
		public boolean isFree(GameCharacter owner) {
			return Sex.isPenetrationTypeFree(owner, this);
		}

		@Override
		public CoverableArea getRelatedCoverableArea() {
			return CoverableArea.NONE;
		}
	},

	TENTACLE(3, -1.5f, true) {
		@Override
		public String getName(GameCharacter owner) {
			return "tentacle";
		}

		@Override
		public boolean isFree(GameCharacter owner) {
			// TODO
			return false;
		}

		@Override
		public CoverableArea getRelatedCoverableArea() {
			return CoverableArea.NONE;
		}
	};

	private float baseArousalWhenPenetrating;
	private float arousalChangePenetratingDry;
	private boolean takesVirginity;

	private SexAreaPenetration(float baseArousalWhenPenetrating, float arousalChangePenetratingDry, boolean takesVirginity) {
		this.baseArousalWhenPenetrating = baseArousalWhenPenetrating;
		this.arousalChangePenetratingDry = arousalChangePenetratingDry;
		this.takesVirginity = takesVirginity;
	}

	@Override
	public boolean isOrifice() {
		return false;
	}

	public float getBaseArousalWhenPenetrating() {
		return baseArousalWhenPenetrating;
	}

	public float getArousalChangePenetratingDry() {
		return arousalChangePenetratingDry;
	}

	public boolean isTakesVirginity() {
		return takesVirginity;
	}

}


code:
package com.lilithsthrone.game.sex;

/**
 * @since 0.3.1
 * @version 0.3.1
 * @author Innoxia
 */
public enum SexControl {

	/**The character cannot do anything in sex.*/
	NONE(0),

	/**The character can only perform self actions in sex.*/
	SELF(1),

	/**The character can only use ongoing actions in sex. Default for submissive NPCs in non-equal sex.*/
	ONGOING_ONLY(2),

	/**The character can use ongoing actions in sex, as well as penetrations that are not related to taking virginity (can still start actions that take their own virginity though). Default for the player when submissive in non-equal sex.*/
	ONGOING_PLUS_LIMITED_PENETRATIONS(3),

	/**The character can perform any action in sex. Only this level of control can end sex.*/
	FULL(4);

	private int value;

	private SexControl(int value) {
		this.value = value;
	}

	public int getValue() {
		return value;
	}

}


code:
package com.lilithsthrone.game.sex;

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

import com.lilithsthrone.game.character.GameCharacter;

/**
 * @since 0.1.69.1
 * @version 0.3.5.5
 * @author Innoxia
 */
public class SexFlags {

	public static boolean selfActionsBlockedPlayer;
	public static boolean selfActionsBlockedPartner;

	// Generic:
	public static boolean mutualOrgasmsAllowed;
	public static List<GameCharacter> playerPreparedForCharactersOrgasm;
	/** Typically use the key as a flag, and the value for any integer you want associated with the flag. */
	public static Map<String, Integer> genericFlags;

	// Brax: TODO Temporary awaiting Brax rewrite
	public static boolean braxCumOnChest;

	// Ralph
	public static boolean customerAtCounter;
	public static boolean alertedCustomer;
	public static boolean askedForBigDiscount;
	public static int ralphDiscount;

	//Pix
	public static boolean pixDemandedPromise;
	public static boolean pixPlayerPromised;

	// Claire:
	public static boolean claireSexInterrupted;
	public static int claireSexInterruptedTurn;

	public SexFlags() {
		reset();
	}

	public static void reset() {
		genericFlags = new HashMap<>();

		selfActionsBlockedPlayer = false;
		selfActionsBlockedPartner = false;

		mutualOrgasmsAllowed = true;
		playerPreparedForCharactersOrgasm = new ArrayList<>();

		// Brax:
		braxCumOnChest = false;

		// Ralph:
		customerAtCounter = false;
		alertedCustomer = false;
		askedForBigDiscount = false;
		ralphDiscount = 25;

		// Pix:
		pixDemandedPromise=false;
		pixPlayerPromised = false;

		// Claire:
		claireSexInterrupted = false;
		claireSexInterruptedTurn = -1;
	}

}


code:
package com.lilithsthrone.game.sex;

import com.lilithsthrone.utils.BaseColour;

/**
 * @since 0.1.69.9
 * @version 0.3.1
 * @author Innoxia
 */
public enum SexPace {

	SUB_RESISTING(false, "resisting", BaseColour.CRIMSON),
	SUB_NORMAL(false, "normal", BaseColour.PINK),
	SUB_EAGER(false, "eager", BaseColour.PINK_DEEP),

	DOM_GENTLE(true, "gentle", BaseColour.PINK_LIGHT),
	DOM_NORMAL(true, "normal", BaseColour.PINK),
	DOM_ROUGH(true, "rough", BaseColour.CRIMSON);

	private boolean isDom;
	private String name;
	private BaseColour colour;

	private SexPace(boolean isDom, String name, BaseColour colour) {
		this.isDom = isDom;
		this.name = name;
		this.colour = colour;
	}

	public SexPace getOppositeDomEquivalent() {
		switch(this) {
			case DOM_GENTLE:
				return SUB_NORMAL;
			case DOM_NORMAL:
				return SUB_NORMAL;
			case DOM_ROUGH:
				return SUB_EAGER;
			case SUB_EAGER:
				return DOM_ROUGH;
			case SUB_NORMAL:
				return DOM_NORMAL;
			case SUB_RESISTING:
				return DOM_GENTLE;
		}
		return SUB_NORMAL;
	}

	public boolean isDom() {
		return isDom;
	}

	public String getName() {
		return name;
	}

	public BaseColour getColour() {
		return colour;
	}
}


code:
package com.lilithsthrone.game.sex;

/**
 * @since 0.1.98
 * @version 0.2.8
 * @author Innoxia
 */
public enum SexParticipantType {

//	MISC,

	/**Partner-interaction actions which can only occur in non-masturbation scenes. */
	NORMAL,

//	/**Self-actions which can only occur in non-masturbation scenes. They act like NORMAL in all ways except they are found under the 'self' tab. */
//	SELF_WITH_PARTNER,

	/**Self-actions which can occur in any scene. */
	SELF;
}


code:
package com.lilithsthrone.game.sex;

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

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

import com.lilithsthrone.game.character.CharacterUtils;
import com.lilithsthrone.game.character.GameCharacter;
import com.lilithsthrone.game.character.fetishes.Fetish;
import com.lilithsthrone.game.dialogue.utils.UtilText;
import com.lilithsthrone.utils.XMLSaving;

/**
 * @since 0.1.53
 * @version 0.3.1
 * @author Innoxia
 */
public class SexType implements XMLSaving {

	private SexParticipantType asParticipant;
	private SexAreaInterface performingSexArea;
	private SexAreaInterface targetedSexArea;

	public SexType(SexParticipantType asParticipant, SexAreaInterface performingSexArea, SexAreaInterface targetedSexArea) {
		this.asParticipant = asParticipant;
		this.performingSexArea = performingSexArea;
		this.targetedSexArea = targetedSexArea;
	}

	@Override
	public boolean equals(Object o) {
		if(o instanceof SexType){
			if(((SexType)o).getAsParticipant() == getAsParticipant()
				&& ((SexType)o).getPerformingSexArea() == getPerformingSexArea()
				&& ((SexType)o).getTargetedSexArea() == getTargetedSexArea()){
					return true;
			}
		}
		return false;
	}

	@Override
	public int hashCode() {
		int result = 17;
		result = 31 * result + getPerformingSexArea().hashCode();
		result = 31 * result + getTargetedSexArea().hashCode();
		return result;
	}

	@Override
	public String toString() {
		return "Participant: "+asParticipant.toString()+"  Performing/Target: "+(performingSexArea==null?"null":performingSexArea.toString())+"/"+(targetedSexArea==null?"null":targetedSexArea.toString());
	}

	@Override
	public Element saveAsXML(Element parentElement, Document doc) {
		Element effect = doc.createElement("sexType");
		parentElement.appendChild(effect);

		CharacterUtils.addAttribute(doc, effect, "SexParticipantType", asParticipant.toString());
		CharacterUtils.addAttribute(doc, effect, "penetrationType", performingSexArea.toString());
		CharacterUtils.addAttribute(doc, effect, "orificeType", targetedSexArea.toString());

		return effect;
	}

	public static SexType loadFromXML(Element parentElement, Document doc) {
		return new SexType(
				SexParticipantType.valueOf(parentElement.getAttribute("SexParticipantType")),
				SexAreaPenetration.valueOf(parentElement.getAttribute("penetrationType")),
				SexAreaOrifice.valueOf(parentElement.getAttribute("orificeType")));
	}

	public String getName() {
		return "";
	}

	/**
	 * @return A crude description of the sex that took place while using this SexType.
	 */
	public String getPerformanceDescription(boolean isDom, GameCharacter performer, GameCharacter target) {
		if(isDom) {
			if(this.getPerformingSexArea().isPenetration()) {
				if(this.getTargetedSexArea().isPenetration()) {
					return UtilText.parse(performer, target, "[npc.Name] rubbed [npc.her] "+this.getPerformingSexArea().getName(performer)+" against [npc2.namePos] "+this.getTargetedSexArea().getName(target)+".");
				} else {
					return UtilText.parse(performer, target, "[npc.Name] used [npc.her] "+this.getPerformingSexArea().getName(performer)+" to fuck [npc2.namePos] "+this.getTargetedSexArea().getName(target)+".");
				}

			} else {
				if(this.getTargetedSexArea().isPenetration()) {
					return UtilText.parse(performer, target, "[npc.Name] had [npc.her] "+this.getPerformingSexArea().getName(performer)+" fucked by [npc2.namePos] "+this.getTargetedSexArea().getName(target)+".");
				} else {
					return UtilText.parse(performer, target, "[npc.Name] rubbed [npc.her] "+this.getPerformingSexArea().getName(performer)+" against [npc2.namePos] "+this.getTargetedSexArea().getName(target)+".");
				}
			}
		} else {
			if(this.getPerformingSexArea().isPenetration()) {
				if(this.getTargetedSexArea().isPenetration()) {
					return UtilText.parse(target, performer, "[npc.Name] rubbed [npc.her] "+this.getPerformingSexArea().getName(target)+" against [npc2.namePos] "+this.getTargetedSexArea().getName(performer)+".");
				} else {
					return UtilText.parse(target, performer, "[npc.Name] used [npc.her] "+this.getPerformingSexArea().getName(target)+" to fuck [npc2.namePos] "+this.getTargetedSexArea().getName(performer)+".");
				}

			} else {
				if(this.getTargetedSexArea().isPenetration()) {
					return UtilText.parse(target, performer, "[npc.Name] had [npc.her] "+this.getPerformingSexArea().getName(target)+" fucked by [npc2.namePos] "+this.getTargetedSexArea().getName(performer)+".");
				} else {
					return UtilText.parse(target, performer, "[npc.Name] rubbed [npc.her] "+this.getPerformingSexArea().getName(target)+" against [npc2.namePos] "+this.getTargetedSexArea().getName(performer)+".");
				}
			}
		}
	}

	public SexParticipantType getAsParticipant() {
		return asParticipant;
	}

	public SexAreaInterface getPerformingSexArea() {
		return performingSexArea;
	}

	public SexAreaInterface getTargetedSexArea() {
		return targetedSexArea;
	}

	/**
	 * @return true if this SexType has the potential to take virginity of an internal orifice.
	 */
	public boolean isTakesVirginity() {
		if(performingSexArea!=null && performingSexArea.isPenetration() && ((SexAreaPenetration)performingSexArea).isTakesVirginity()) {
			return targetedSexArea!=null && targetedSexArea.isOrifice() && ((SexAreaOrifice)targetedSexArea).isInternalOrifice();
		}
		if(targetedSexArea!=null && targetedSexArea.isPenetration() && ((SexAreaPenetration)targetedSexArea).isTakesVirginity()) {
			return performingSexArea!=null && performingSexArea.isOrifice() && ((SexAreaOrifice)performingSexArea).isInternalOrifice();
		}
		return false;
	}

	public boolean isPenetrating() {
		return getPerformingSexArea().isPenetration() && getTargetedSexArea().isOrifice();
	}

	public boolean isBeingPenetrated() {
		return getPerformingSexArea().isOrifice() && getTargetedSexArea().isPenetration();
	}

	public SexType getReversedSexType() {
		return new SexType(getAsParticipant(), getTargetedSexArea(), getPerformingSexArea());
	}

	public List<Fetish> getRelatedFetishes(GameCharacter characterPerforming, GameCharacter characterTargeted, boolean isPenetration, boolean isOrgasm) {
		List<Fetish> fetishes = new ArrayList<>();

		// Self areas:

		if(getPerformingSexArea()!=null && getPerformingSexArea().isPenetration()) {
			switch((SexAreaPenetration)getPerformingSexArea()) {
				case CLIT:
					fetishes.add(Fetish.FETISH_VAGINAL_RECEIVING);
					break;
				case FINGER:
					break;
				case PENIS:
					fetishes.add(Fetish.FETISH_PENIS_GIVING);
					break;
				case TAIL:
					break;
				case TENTACLE:
					break;
				case FOOT:
					fetishes.add(Fetish.FETISH_FOOT_GIVING);
					break;
				case TONGUE:
					fetishes.add(Fetish.FETISH_ORAL_GIVING);
					break;
			}
		}
		if(getPerformingSexArea()!=null && getPerformingSexArea().isOrifice()) {
			switch((SexAreaOrifice)getPerformingSexArea()) {
				case ANUS:
					fetishes.add(Fetish.FETISH_ANAL_RECEIVING);
					break;
				case ASS:
					fetishes.add(Fetish.FETISH_ANAL_RECEIVING);
					break;
				case BREAST: case BREAST_CROTCH:
					fetishes.add(Fetish.FETISH_BREASTS_SELF);
					break;
				case MOUTH:
					fetishes.add(Fetish.FETISH_ORAL_GIVING);
					break;
				case NIPPLE:
					if(characterPerforming.getBreastRawStoredMilkValue()>0) {
						fetishes.add(Fetish.FETISH_LACTATION_SELF);
					}
					fetishes.add(Fetish.FETISH_BREASTS_SELF);
					break;
				case NIPPLE_CROTCH:
					if(characterPerforming.getBreastCrotchRawStoredMilkValue()>0) {
						fetishes.add(Fetish.FETISH_LACTATION_SELF);
					}
					fetishes.add(Fetish.FETISH_BREASTS_SELF);
					break;
				case THIGHS:
					fetishes.add(Fetish.FETISH_STRUTTER);
					break;
				case URETHRA_PENIS:
					fetishes.add(Fetish.FETISH_PENIS_GIVING);
					break;
				case URETHRA_VAGINA:
					fetishes.add(Fetish.FETISH_VAGINAL_RECEIVING);
					break;
				case VAGINA:
					if(characterTargeted.hasPenisIgnoreDildo()
							&& characterTargeted.getPenisRawStoredCumValue()>0
							&& getTargetedSexArea()==SexAreaPenetration.PENIS
							&& isOrgasm) {
						fetishes.add(Fetish.FETISH_PREGNANCY);
					}
					fetishes.add(Fetish.FETISH_VAGINAL_RECEIVING);
					break;
			}
		}

		// Targeted areas:

		if(getTargetedSexArea()!=null && getTargetedSexArea().isPenetration()) {
			switch((SexAreaPenetration)getTargetedSexArea()) {
				case CLIT:
					fetishes.add(Fetish.FETISH_VAGINAL_GIVING);
					break;
				case FINGER:
					break;
				case PENIS:
					fetishes.add(Fetish.FETISH_PENIS_RECEIVING);
					break;
				case TAIL:
					break;
				case TENTACLE:
					break;
				case FOOT:
					fetishes.add(Fetish.FETISH_FOOT_RECEIVING);
					break;
				case TONGUE:
					fetishes.add(Fetish.FETISH_ORAL_RECEIVING);
					break;
			}
		}
		if(getTargetedSexArea()!=null && getTargetedSexArea().isOrifice()) {
			switch((SexAreaOrifice)getTargetedSexArea()) {
				case ANUS:
					fetishes.add(Fetish.FETISH_ANAL_GIVING);
					if(getPerformingSexArea()!=null && isPenetration && getPerformingSexArea().isPenetration() && ((SexAreaPenetration)getPerformingSexArea()).isTakesVirginity() &&  characterTargeted.isAssVirgin()) {
						fetishes.add(Fetish.FETISH_DEFLOWERING);
					}
					break;
				case ASS:
					fetishes.add(Fetish.FETISH_ANAL_GIVING);
					break;
				case BREAST:
					fetishes.add(Fetish.FETISH_BREASTS_OTHERS);
					break;
				case BREAST_CROTCH:
					fetishes.add(Fetish.FETISH_BREASTS_OTHERS);
					break;
				case MOUTH:
					fetishes.add(Fetish.FETISH_ORAL_RECEIVING);
					if(getPerformingSexArea()!=null && isPenetration && getPerformingSexArea().isPenetration() && ((SexAreaPenetration)getPerformingSexArea()).isTakesVirginity() &&  characterTargeted.isFaceVirgin()) {
						fetishes.add(Fetish.FETISH_DEFLOWERING);
					}
					break;
				case NIPPLE:
					if(characterTargeted.getBreastRawStoredMilkValue()>0) {
						fetishes.add(Fetish.FETISH_LACTATION_OTHERS);
					}
					fetishes.add(Fetish.FETISH_BREASTS_OTHERS);
					if(getPerformingSexArea()!=null && isPenetration && getPerformingSexArea().isPenetration() && ((SexAreaPenetration)getPerformingSexArea()).isTakesVirginity() &&  characterTargeted.isNippleVirgin()) {
						fetishes.add(Fetish.FETISH_DEFLOWERING);
					}
					break;
				case NIPPLE_CROTCH:
					if(characterTargeted.getBreastCrotchRawStoredMilkValue()>0) {
						fetishes.add(Fetish.FETISH_LACTATION_OTHERS);
					}
					fetishes.add(Fetish.FETISH_BREASTS_OTHERS);
					if(getPerformingSexArea()!=null && isPenetration && getPerformingSexArea().isPenetration() && ((SexAreaPenetration)getPerformingSexArea()).isTakesVirginity() &&  characterTargeted.isNippleCrotchVirgin()) {
						fetishes.add(Fetish.FETISH_DEFLOWERING);
					}
					break;
				case THIGHS:
					fetishes.add(Fetish.FETISH_LEG_LOVER);
					break;
				case URETHRA_PENIS:
					fetishes.add(Fetish.FETISH_PENIS_RECEIVING);
					if(getPerformingSexArea()!=null && isPenetration && getPerformingSexArea().isPenetration() && ((SexAreaPenetration)getPerformingSexArea()).isTakesVirginity() &&  characterTargeted.isUrethraVirgin()) {
						fetishes.add(Fetish.FETISH_DEFLOWERING);
					}
					break;
				case URETHRA_VAGINA:
					fetishes.add(Fetish.FETISH_VAGINAL_GIVING);
					if(getPerformingSexArea()!=null && isPenetration && getPerformingSexArea().isPenetration() && ((SexAreaPenetration)getPerformingSexArea()).isTakesVirginity() &&  characterTargeted.isVaginaUrethraVirgin()) {
						fetishes.add(Fetish.FETISH_DEFLOWERING);
					}
					break;
				case VAGINA:
					if(characterPerforming.hasPenisIgnoreDildo()
							&& characterPerforming.getPenisRawStoredCumValue()>0
							&& getPerformingSexArea()!=null
							&& getPerformingSexArea()==SexAreaPenetration.PENIS
							&& isOrgasm) {
						fetishes.add(Fetish.FETISH_IMPREGNATION);
					}
					fetishes.add(Fetish.FETISH_VAGINAL_GIVING);
					if(getPerformingSexArea()!=null && isPenetration && getPerformingSexArea().isPenetration() && ((SexAreaPenetration)getPerformingSexArea()).isTakesVirginity() &&  characterTargeted.isVaginaVirgin()) {
						fetishes.add(Fetish.FETISH_DEFLOWERING);
					}
					break;
			}
		}

		// Check for masturbation:
		if(!fetishes.contains(Fetish.FETISH_MASTURBATION) && (characterPerforming.equals(characterTargeted) || this.asParticipant==SexParticipantType.SELF)) {
			fetishes.add(Fetish.FETISH_MASTURBATION);
		}

		return fetishes;
	}

	public List<Fetish> getOppositeFetishes(GameCharacter characterPerforming, GameCharacter characterTargeted, boolean isPenetration, boolean isOrgasm) {
		List<Fetish> oppositeFetishes = new ArrayList<>();
		for(Fetish f : getRelatedFetishes(characterPerforming, characterTargeted, isPenetration, isOrgasm)) {
			switch(f) {
				case FETISH_ANAL_GIVING:
					oppositeFetishes.add(Fetish.FETISH_ANAL_RECEIVING);
					break;
				case FETISH_ANAL_RECEIVING:
					oppositeFetishes.add(Fetish.FETISH_ANAL_GIVING);
					break;
				case FETISH_BIMBO:
					break;
				case FETISH_BREASTS_OTHERS:
					oppositeFetishes.add(Fetish.FETISH_BREASTS_SELF);
					break;
				case FETISH_BREASTS_SELF:
					oppositeFetishes.add(Fetish.FETISH_BREASTS_OTHERS);
					break;
				case FETISH_LACTATION_OTHERS:
					oppositeFetishes.add(Fetish.FETISH_LACTATION_SELF);
					break;
				case FETISH_LACTATION_SELF:
					oppositeFetishes.add(Fetish.FETISH_LACTATION_OTHERS);
					break;
				case FETISH_BREEDER:
					break;
				case FETISH_CROSS_DRESSER:
					break;
				case FETISH_CUM_ADDICT:
					oppositeFetishes.add(Fetish.FETISH_CUM_STUD);
					break;
				case FETISH_CUM_STUD:
					oppositeFetishes.add(Fetish.FETISH_CUM_ADDICT);
					break;
				case FETISH_DEFLOWERING:
					break;
				case FETISH_DENIAL:
					oppositeFetishes.add(Fetish.FETISH_DENIAL_SELF);
					break;
				case FETISH_DENIAL_SELF:
					oppositeFetishes.add(Fetish.FETISH_DENIAL);
					break;
				case FETISH_DOMINANT:
					oppositeFetishes.add(Fetish.FETISH_SUBMISSIVE);
					break;
				case FETISH_EXHIBITIONIST:
					break;
				case FETISH_IMPREGNATION:
					oppositeFetishes.add(Fetish.FETISH_PREGNANCY);
					break;
				case FETISH_INCEST:
					oppositeFetishes.add(Fetish.FETISH_INCEST);
					break;
				case FETISH_LEG_LOVER:
					oppositeFetishes.add(Fetish.FETISH_STRUTTER);
					break;
				case FETISH_LUSTY_MAIDEN:
					break;
				case FETISH_MASOCHIST:
					oppositeFetishes.add(Fetish.FETISH_SADIST);
					break;
				case FETISH_MASTURBATION:
					oppositeFetishes.add(Fetish.FETISH_MASTURBATION);
					break;
				case FETISH_NON_CON_DOM:
					oppositeFetishes.add(Fetish.FETISH_NON_CON_SUB);
					break;
				case FETISH_NON_CON_SUB:
					oppositeFetishes.add(Fetish.FETISH_NON_CON_DOM);
					break;
				case FETISH_ORAL_GIVING:
					oppositeFetishes.add(Fetish.FETISH_ORAL_RECEIVING);
					break;
				case FETISH_ORAL_RECEIVING:
					oppositeFetishes.add(Fetish.FETISH_ORAL_GIVING);
					break;
				case FETISH_PREGNANCY:
					oppositeFetishes.add(Fetish.FETISH_IMPREGNATION);
					break;
				case FETISH_PURE_VIRGIN:
					break;
				case FETISH_SADIST:
					oppositeFetishes.add(Fetish.FETISH_MASOCHIST);
					break;
				case FETISH_SADOMASOCHIST:
					break;
				case FETISH_STRUTTER:
					oppositeFetishes.add(Fetish.FETISH_LEG_LOVER);
					break;
				case FETISH_SUBMISSIVE:
					oppositeFetishes.add(Fetish.FETISH_DOMINANT);
					break;
				case FETISH_SWITCH:
					break;
				case FETISH_TRANSFORMATION_GIVING:
					oppositeFetishes.add(Fetish.FETISH_TRANSFORMATION_RECEIVING);
					break;
				case FETISH_TRANSFORMATION_RECEIVING:
					oppositeFetishes.add(Fetish.FETISH_TRANSFORMATION_GIVING);
					break;
				case FETISH_VAGINAL_GIVING:
					oppositeFetishes.add(Fetish.FETISH_VAGINAL_RECEIVING);
					break;
				case FETISH_VAGINAL_RECEIVING:
					oppositeFetishes.add(Fetish.FETISH_VAGINAL_GIVING);
					break;
				case FETISH_VOYEURIST:
					break;
				case FETISH_KINK_GIVING:
					break;
				case FETISH_KINK_RECEIVING:
					break;
				case FETISH_PENIS_GIVING:
					oppositeFetishes.add(Fetish.FETISH_PENIS_RECEIVING);
					break;
				case FETISH_PENIS_RECEIVING:
					oppositeFetishes.add(Fetish.FETISH_PENIS_GIVING);
					break;
				case FETISH_FOOT_GIVING:
					oppositeFetishes.add(Fetish.FETISH_FOOT_RECEIVING);
					break;
				case FETISH_FOOT_RECEIVING:
					oppositeFetishes.add(Fetish.FETISH_FOOT_GIVING);
					break;
			}
		}
		return oppositeFetishes;
	}
}


code:
package com.lilithsthrone.game.sex.managers;

/**
 * @since 0.3.4
 * @version 0.3.4
 * @author Innoxia
 */
public enum OrgasmBehaviour {

	PULL_OUT,

	DEFAULT,

	CREAMPIE;
}


code:
package com.lilithsthrone.game.sex.managers;

import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.lilithsthrone.game.character.GameCharacter;
import com.lilithsthrone.game.character.attributes.ArousalLevel;
import com.lilithsthrone.game.character.attributes.LustLevel;
import com.lilithsthrone.game.character.body.CoverableArea;
import com.lilithsthrone.game.character.effects.StatusEffect;
import com.lilithsthrone.game.character.fetishes.Fetish;
import com.lilithsthrone.game.character.npc.NPC;
import com.lilithsthrone.game.inventory.InventorySlot;
import com.lilithsthrone.game.inventory.clothing.AbstractClothing;
import com.lilithsthrone.game.inventory.clothing.DisplacementType;
import com.lilithsthrone.game.sex.OrgasmCumTarget;
import com.lilithsthrone.game.sex.Sex;
import com.lilithsthrone.game.sex.SexAreaInterface;
import com.lilithsthrone.game.sex.SexAreaOrifice;
import com.lilithsthrone.game.sex.SexAreaPenetration;
import com.lilithsthrone.game.sex.SexFlags;
import com.lilithsthrone.game.sex.SexPace;
import com.lilithsthrone.game.sex.SexParticipantType;
import com.lilithsthrone.game.sex.SexType;
import com.lilithsthrone.game.sex.positions.AbstractSexPosition;
import com.lilithsthrone.game.sex.positions.slots.SexSlot;
import com.lilithsthrone.game.sex.positions.slots.SexSlotGeneric;
import com.lilithsthrone.game.sex.sexActions.SexAction;
import com.lilithsthrone.game.sex.sexActions.SexActionInterface;
import com.lilithsthrone.game.sex.sexActions.SexActionPriority;
import com.lilithsthrone.game.sex.sexActions.SexActionType;
import com.lilithsthrone.game.sex.sexActions.SexActionUtility;
import com.lilithsthrone.game.sex.sexActions.baseActions.PenisFeet;
import com.lilithsthrone.game.sex.sexActions.baseActions.PenisFoot;
import com.lilithsthrone.game.sex.sexActions.baseActionsMisc.GenericActions;
import com.lilithsthrone.game.sex.sexActions.baseActionsMisc.GenericOrgasms;
import com.lilithsthrone.game.sex.sexActions.baseActionsSelfPartner.PartnerSelfFingerMouth;
import com.lilithsthrone.utils.Util;

/**
 * @since 0.1.0
 * @version 0.3.1
 * @author Innoxia
 */
public abstract class SexManagerDefault implements SexManagerInterface {

	protected AbstractSexPosition position;
	protected Map<GameCharacter, SexSlot> dominants;
	protected Map<GameCharacter, SexSlot> submissives;
	protected Map<GameCharacter, List<SexAreaInterface>> areasBannedMap;

	public SexManagerDefault(AbstractSexPosition position, Map<GameCharacter, SexSlot> dominants, Map<GameCharacter, SexSlot> submissives) {

		this.position = position;
		this.dominants = dominants==null?new HashMap<>():dominants;
		this.submissives = submissives==null?new HashMap<>():submissives;

		if(position!=null) {
			int totalParticipants = this.dominants.size() + this.submissives.size();

			if(totalParticipants > position.getMaximumSlots()) {
				throw new IllegalArgumentException("Too many characters("+totalParticipants+") for Sex Manager("+(position.getMaximumSlots())+")!");
			}

			try {
				for(SexSlot slot : position.getAllAvailableSexPositions()) {
					int count = 0;
					if(this.dominants.values().contains(slot)) {
						count++;
					}
					if(this.submissives.values().contains(slot)) {
						count++;
					}
					if(count>1) {
						throw new IllegalArgumentException("Multiple partners assigned to a single slot!");
					}
				}
			} catch(Exception ex) {
//				System.err.println("Error in sex position '"+position.getName()+"': Cannot check if partners are assigned to the same slot.");
			}
		}

		areasBannedMap = new HashMap<>();
	}

	@Override
	public AbstractSexPosition getPosition() {
		return position;
	}

	@Override
	public Map<GameCharacter, SexSlot> getDominants() {
		return dominants;
	}

	@Override
	public Map<GameCharacter, SexSlot> getSubmissives() {
		return submissives;
	}

	public Map<GameCharacter, List<SexAreaInterface>> getAreasBannedMap() {
		return areasBannedMap;
	}

	private static List<SexActionInterface> possibleActions = new ArrayList<>();
	private static List<SexActionInterface> bannedActions = new ArrayList<>();

	/**
	 * New:

	 * - Get accessible areas

	 * - Choose foreplay & main sex

	 * - Choose [npc.verb(position)] for each

	 * - Clothing for foreplay

	 * - position

	 * - foreplay (self-actions take minimum priority)

	 * - clothing for main

	 * - position

	 * - main (self-actions take minimum priority)

	 * - orgasm
	 */
	@Override
	public SexActionInterface getPartnerSexAction(SexActionInterface sexActionPlayer) {

		possibleActions.clear();
		bannedActions.clear();

		NPC partner = (NPC) Sex.getCharacterPerformingAction();

		List<SexActionInterface> availableActions = Sex.getAvailableSexActionsPartner();

		// --- Priority 1 | If orgasming, bypass everything and use an orgasm option ---

		if (partner.getArousal() >= ArousalLevel.FIVE_ORGASM_IMMINENT.getMaximumValue() && SexFlags.playerPreparedForCharactersOrgasm.contains(partner)) {
			List<SexActionInterface> priorityOrgasms = new ArrayList<>();

			for(SexActionInterface action : availableActions) {
				for(GameCharacter character : Sex.getAllParticipants()) {
					if(action.getAreasCummedIn(partner, character) != null) {
						if((action.getAreasCummedIn(partner, character).contains(SexAreaOrifice.VAGINA) && (partner.hasFetish(Fetish.FETISH_IMPREGNATION))) || Sex.getRequestedPulloutWeighting(partner)<0) {
							priorityOrgasms.add(action);

						}
//						else if(SexFlags.characterRequestedPullOut && (Sex.isConsensual() || Sex.getSexControl(partner)==SexControl.FULL)) {
//							priorityOrgasms.add(action);
//						}
					} else {
						if(Sex.getRequestedPulloutWeighting(partner)>0) {
							priorityOrgasms.add(action);
						}
					}
					if(action.getAreasCummedIn(character, partner) != null) {
						if((action.getAreasCummedIn(character, partner).contains(SexAreaOrifice.VAGINA) && (partner.hasFetish(Fetish.FETISH_PREGNANCY)))) {
							priorityOrgasms.add(action);

						}
					}
				}
			}

			if(!priorityOrgasms.isEmpty()) {
				return priorityOrgasms.get(Util.random.nextInt(priorityOrgasms.size()));

			} else {
				if(availableActions.isEmpty()) {
					System.err.println(partner.getName(true)+" has no orgasm actions!!!");
					return GenericOrgasms.PARTNER_GENERIC_ORGASM;
				}
				return availableActions.get(Util.random.nextInt(availableActions.size()));
			}
		}

		// --- Priority 2 | Resisting ---

		// If the partner is resisting, they will not want to remove any clothing, and will instead simply use an available option. (Which will be a SUB_RESIST or neutral pace one.)
		if(Sex.getSexPace(partner)==SexPace.SUB_RESISTING) {
			possibleActions.addAll(Sex.getAvailableSexActionsPartner());

			if (!possibleActions.isEmpty()) {
				return possibleActions.get(Util.random.nextInt(possibleActions.size()));
			} else {
				return SexActionUtility.PARTNER_NONE;
			}
		}

		// --- Priority 3 | Move into one of the partner's preferred positions ---

		if(Sex.isSexLeader(partner)) { // Only the leader should be able to switch positions:
			boolean suitablePosition = false;

			List<SexActionInterface> highPriorityActions = new ArrayList<>();
			for(SexActionInterface action : availableActions) {
				if(action.getActionType()==SexActionType.POSITIONING) {
					if(action.getPriority()==SexActionPriority.HIGH) {
						highPriorityActions.add(action);
					} else {
						possibleActions.add(action);
					}
				}
			}

			for(GameCharacter character : Sex.getAllParticipants()) {
				if(!character.equals(partner)
						&& Sex.getSexPositionSlot(character)!=SexSlotGeneric.MISC_WATCHING
						&& partner.isHappyToBeInSlot(Sex.getPosition(), Sex.getSexPositionSlot(partner), Sex.getSexPositionSlot(character), character)
//						&& highPriorityActions.isEmpty()
						) {
//					System.out.println("Happy in slot");
					suitablePosition = true;
					break;
				} else {
//					System.out.println("Not happy in slot");
				}
			}

			if(!suitablePosition && Sex.getLastUsedPlayerAction().getActionType()!=SexActionType.POSITIONING) {
				// Choose a random position:
				if (!highPriorityActions.isEmpty()) {
					return Util.randomItemFrom(highPriorityActions);
				}
				if (!possibleActions.isEmpty()) {
					return Util.randomItemFrom(possibleActions);
				}

			} else {
				for(SexActionInterface action : availableActions) {
					if(action.getActionType()==SexActionType.POSITIONING) {
						bannedActions.add(action);
					}
				}
			}
		}

		// --- Priority 4 | Removing clothing ---

		GameCharacter targetedCharacter = Sex.getTargetedPartner(partner);

		// Skip over remove clothing if action is of HIGH or MAX priority
		if(!availableActions.isEmpty()
				&& availableActions.get(0).getPriority()!=SexActionPriority.HIGH
				&& availableActions.get(0).getPriority()!=SexActionPriority.UNIQUE_MAX) {

			List<CoverableArea> targetAreasToBeExposed = new ArrayList<>();
			List<CoverableArea> partnerAreasToBeExposed = new ArrayList<>();

			partnerAreasToBeExposed.addAll(Sex.getInitialSexManager().getAdditionalAreasToExposeDuringSex(partner, partner));
			targetAreasToBeExposed.addAll(Sex.getInitialSexManager().getAdditionalAreasToExposeDuringSex(partner, targetedCharacter));

			if(Sex.isInForeplay(partner)) {
				if(Sex.getForeplayPreference(partner, targetedCharacter)!=null) {
					SexParticipantType participantType = Sex.getForeplayPreference(partner, targetedCharacter).getAsParticipant();

					partnerAreasToBeExposed.add(Sex.getForeplayPreference(partner, targetedCharacter).getPerformingSexArea().getRelatedCoverableArea());
					if(participantType==SexParticipantType.SELF) {
						partnerAreasToBeExposed.add(Sex.getForeplayPreference(partner, targetedCharacter).getTargetedSexArea().getRelatedCoverableArea());
					} else {
						targetAreasToBeExposed.add(Sex.getForeplayPreference(partner, targetedCharacter).getTargetedSexArea().getRelatedCoverableArea());
					}

				} else {
					partnerAreasToBeExposed.add(CoverableArea.MOUTH);

					targetAreasToBeExposed.add(CoverableArea.MOUTH);

					if(Math.random()<0.4) {
						partnerAreasToBeExposed.add(CoverableArea.PENIS);
						partnerAreasToBeExposed.add(CoverableArea.VAGINA);

						targetAreasToBeExposed.add(CoverableArea.PENIS);
						targetAreasToBeExposed.add(CoverableArea.VAGINA);
					}
				}

			} else {
				if(partner.getMainSexPreference(targetedCharacter)!=null) {
					SexParticipantType participantType = partner.getMainSexPreference(targetedCharacter).getAsParticipant();

					partnerAreasToBeExposed.add(partner.getMainSexPreference(targetedCharacter).getPerformingSexArea().getRelatedCoverableArea());
					if(participantType==SexParticipantType.SELF) {
						partnerAreasToBeExposed.add(partner.getMainSexPreference(targetedCharacter).getTargetedSexArea().getRelatedCoverableArea());
					} else {
						targetAreasToBeExposed.add(partner.getMainSexPreference(targetedCharacter).getTargetedSexArea().getRelatedCoverableArea());
					}

				} else {
					partnerAreasToBeExposed.add(CoverableArea.PENIS);
					partnerAreasToBeExposed.add(CoverableArea.VAGINA);

					targetAreasToBeExposed.add(CoverableArea.PENIS);
					targetAreasToBeExposed.add(CoverableArea.VAGINA);
				}
			}

			if((Sex.getActionsAvailablePartner(partner, targetedCharacter).contains(PenisFoot.FOOT_JOB_SINGLE_GIVING_START)
							&& partner.calculateSexTypeWeighting(PenisFoot.FOOT_JOB_SINGLE_GIVING_START.getAsSexType(), targetedCharacter, null)>0)
					|| (Sex.getActionsAvailablePartner(partner, targetedCharacter).contains(PenisFeet.FOOT_JOB_DOUBLE_GIVING_START)
							&& partner.calculateSexTypeWeighting(PenisFeet.FOOT_JOB_DOUBLE_GIVING_START.getAsSexType(), targetedCharacter, null)>0)) {
				partnerAreasToBeExposed.add(CoverableArea.FEET);
			}

			if((Sex.getActionsAvailablePartner(partner, targetedCharacter).contains(PenisFoot.FOOT_JOB_SINGLE_RECEIVING_START)
						&& partner.calculateSexTypeWeighting(PenisFoot.FOOT_JOB_SINGLE_RECEIVING_START.getAsSexType(), targetedCharacter, null)>0)
				|| (Sex.getActionsAvailablePartner(partner, targetedCharacter).contains(PenisFeet.FOOT_JOB_DOUBLE_RECEIVING_START)
						&& partner.calculateSexTypeWeighting(PenisFeet.FOOT_JOB_DOUBLE_RECEIVING_START.getAsSexType(), targetedCharacter, null)>0)) {
				targetAreasToBeExposed.add(CoverableArea.FEET);
			}

			partnerAreasToBeExposed.removeIf((area) -> (partner.isCoverableAreaExposed(area) || !partner.isAbleToAccessCoverableArea(area, true))
					|| (area==CoverableArea.PENIS && !partner.hasPenis())
					|| (area==CoverableArea.VAGINA && !partner.hasVagina()));

			partnerAreasToBeExposed.removeIf((area) -> (area==CoverableArea.FEET && (partner.getFetishDesire(Fetish.FETISH_SADIST).isPositive() || partner.getClothingInSlot(InventorySlot.FOOT)==null)));
			targetAreasToBeExposed.removeIf((area) -> (area==CoverableArea.FEET && (partner.getFetishDesire(Fetish.FETISH_MASOCHIST).isPositive() || targetedCharacter.getClothingInSlot(InventorySlot.FOOT)==null)));

			targetAreasToBeExposed.removeIf((area) -> (targetedCharacter.isCoverableAreaExposed(area) || !targetedCharacter.isAbleToAccessCoverableArea(area, true))
					|| (area==CoverableArea.PENIS && !targetedCharacter.hasPenis())
					|| (area==CoverableArea.VAGINA && !targetedCharacter.hasVagina()));

			if(!partnerAreasToBeExposed.isEmpty() && Sex.isCanRemoveSelfClothing(partner)) {
				Collections.shuffle(partnerAreasToBeExposed);
				CoverableArea exposeArea = partnerAreasToBeExposed.get(0);
				if(exposeArea==CoverableArea.MOUND) {
					exposeArea = CoverableArea.VAGINA;
				}
				SexType preference = partner.getCurrentSexPreference(targetedCharacter);
				// Only displace clothing if its the desired area, or if the clothing to be displaced is not a sex toy:
				SimpleEntry<AbstractClothing, DisplacementType> clothingToRemove = partner.getNextClothingToRemoveForCoverableAreaAccess(exposeArea);
				if((preference!=null && preference.getPerformingSexArea().getRelatedCoverableArea()==exposeArea) //TODO
						|| (clothingToRemove!=null && !clothingToRemove.getKey().getClothingType().isSexToy(clothingToRemove.getKey().getSlotEquippedTo()))) {
					return Sex.manageClothingToAccessCoverableArea(partner, partner, exposeArea);
				}
			}

			if(!Sex.isSpectator(partner) && !targetAreasToBeExposed.isEmpty() && Sex.isCanRemoveOthersClothing(partner, null)) {
				Collections.shuffle(targetAreasToBeExposed);
				List<CoverableArea> areas = new ArrayList<>(targetAreasToBeExposed);
				for(CoverableArea area : areas) {
					SimpleEntry<AbstractClothing, DisplacementType> clothingRemoval = targetedCharacter.getNextClothingToRemoveForCoverableAreaAccess(area);
					if(clothingRemoval==null || !Sex.isCanRemoveOthersClothing(partner, clothingRemoval.getKey())) {
						targetAreasToBeExposed.remove(area);
					}
				}

				if(!targetAreasToBeExposed.isEmpty()) {
					CoverableArea exposeArea = targetAreasToBeExposed.get(0);
					if(exposeArea==CoverableArea.MOUND) {
						exposeArea = CoverableArea.VAGINA;
					}
					SexType preference = partner.getCurrentSexPreference(targetedCharacter);
					// Only displace clothing if its the desired area, or if the clothing to be displaced is not a sex toy:
					SimpleEntry<AbstractClothing, DisplacementType> clothingToRemove = targetedCharacter.getNextClothingToRemoveForCoverableAreaAccess(exposeArea);
					if((preference!=null && preference.getTargetedSexArea().getRelatedCoverableArea()==exposeArea)
							|| (clothingToRemove!=null && !clothingToRemove.getKey().getClothingType().isSexToy(clothingToRemove.getKey().getSlotEquippedTo()))) {
						return Sex.manageClothingToAccessCoverableArea(partner, targetedCharacter, exposeArea);
					}
				}
			}
		}

		// --- Priority 5 | Ban actions that make no sense for the partner to perform ---

		// Ban all penetrations if the partner is a virgin in the associated orifice:
		for(SexActionInterface action : availableActions) {
			if(action.getActionType()==SexActionType.START_ONGOING && action.isTakesVirginity(true)) {
				for(SexAreaOrifice sArea : action.getPerformingCharacterOrifices()) {
					switch(sArea) {
						case ANUS:
							if(Sex.getCharacterPerformingAction().isAssVirgin() && (Sex.getCharacterPerformingAction().getLust()<LustLevel.FOUR_IMPASSIONED.getMinimumValue() || action.getParticipantType()==SexParticipantType.SELF)) {
								bannedActions.add(action);
							}
							break;
						case MOUTH:
							// NPCs don't care about losing oral virginity.
							break;
						case NIPPLE:
							if(Sex.getCharacterPerformingAction().isNippleVirgin() && (Sex.getCharacterPerformingAction().getLust()<LustLevel.FOUR_IMPASSIONED.getMinimumValue() || action.getParticipantType()==SexParticipantType.SELF)) {
								bannedActions.add(action);
							}
							break;
						case NIPPLE_CROTCH:
							if(Sex.getCharacterPerformingAction().isNippleCrotchVirgin() && (Sex.getCharacterPerformingAction().getLust()<LustLevel.FOUR_IMPASSIONED.getMinimumValue() || action.getParticipantType()==SexParticipantType.SELF)) {
								bannedActions.add(action);
							}
							break;
						case URETHRA_PENIS:
							if(Sex.getCharacterPerformingAction().isUrethraVirgin() && (Sex.getCharacterPerformingAction().getLust()<LustLevel.FOUR_IMPASSIONED.getMinimumValue() || action.getParticipantType()==SexParticipantType.SELF)) {
								bannedActions.add(action);
							}
							break;
						case URETHRA_VAGINA:
							if(Sex.getCharacterPerformingAction().isVaginaUrethraVirgin() && (Sex.getCharacterPerformingAction().getLust()<LustLevel.FOUR_IMPASSIONED.getMinimumValue() || action.getParticipantType()==SexParticipantType.SELF)) {
								bannedActions.add(action);
							}
							break;
						case VAGINA:
							if(Sex.getCharacterPerformingAction().hasStatusEffect(StatusEffect.FETISH_PURE_VIRGIN)
									|| Sex.getCharacterPerformingAction().hasStatusEffect(StatusEffect.FETISH_PURE_VIRGIN_LUSTY_MAIDEN)
									|| (Sex.getCharacterPerformingAction().isVaginaVirgin() && (Sex.getCharacterPerformingAction().getLust()<LustLevel.FOUR_IMPASSIONED.getMinimumValue() || action.getParticipantType()==SexParticipantType.SELF))) {
								bannedActions.add(action);
							}
							break;
						// No virginity to lose:
						case ASS:
						case BREAST:
						case BREAST_CROTCH:
						case THIGHS:
							break;
					}
				}
				if(action.getParticipantType()==SexParticipantType.SELF) {
					for(SexAreaOrifice sArea : action.getTargetedCharacterOrifices()) {
						switch(sArea) {
							case ANUS:
								if(Sex.getCharacterPerformingAction().isAssVirgin()) {// && Sex.getCharacterPerformingAction().getLust()<LustLevel.FOUR_IMPASSIONED.getMinimumValue()) {
									bannedActions.add(action);
								}
								break;
							case MOUTH:
								// NPCs don't care about losing oral virginity.
								break;
							case NIPPLE:
								if(Sex.getCharacterPerformingAction().isNippleVirgin()) {// && Sex.getCharacterPerformingAction().getLust()<LustLevel.FOUR_IMPASSIONED.getMinimumValue()) {
									bannedActions.add(action);
								}
								break;
							case NIPPLE_CROTCH:
								if(Sex.getCharacterPerformingAction().isNippleCrotchVirgin()) {// && Sex.getCharacterPerformingAction().getLust()<LustLevel.FOUR_IMPASSIONED.getMinimumValue()) {
									bannedActions.add(action);
								}
								break;
							case URETHRA_PENIS:
								if(Sex.getCharacterPerformingAction().isUrethraVirgin()) {// && Sex.getCharacterPerformingAction().getLust()<LustLevel.FOUR_IMPASSIONED.getMinimumValue()) {
									bannedActions.add(action);
								}
								break;
							case URETHRA_VAGINA:
								if(Sex.getCharacterPerformingAction().isVaginaUrethraVirgin()) {// && Sex.getCharacterPerformingAction().getLust()<LustLevel.FOUR_IMPASSIONED.getMinimumValue()) {
									bannedActions.add(action);
								}
								break;
							case VAGINA:
								if(Sex.getCharacterPerformingAction().hasStatusEffect(StatusEffect.FETISH_PURE_VIRGIN)
										|| Sex.getCharacterPerformingAction().hasStatusEffect(StatusEffect.FETISH_PURE_VIRGIN_LUSTY_MAIDEN)
										|| (Sex.getCharacterPerformingAction().isVaginaVirgin())) {// && Sex.getCharacterPerformingAction().getLust()<LustLevel.FOUR_IMPASSIONED.getMinimumValue())) {
									bannedActions.add(action);
								}
								break;
							// No virginity to lose:
							case ASS:
							case BREAST:
							case BREAST_CROTCH:
							case THIGHS:
								break;
						}
					}
				}
			}
		}

		// --- Priority 6 | Perform actions based on foreplay or sex ---

		// Perform foreplay action if arousal is < 25 and haven't orgasmed yet:
		SexAction actionToPerform = null;
		if(Sex.isInForeplay(Sex.getCharacterPerformingAction())) {
			actionToPerform = performForeplayAction(sexActionPlayer);
			if(actionToPerform!=null) {
				return actionToPerform;
			}

		} else {
			actionToPerform = performSexAction(sexActionPlayer);
			if(actionToPerform!=null) {
				return actionToPerform;
			}
		}

		// --- Priority 7 using other options at random ---
		possibleActions.addAll(availableActions);

		possibleActions.removeAll(bannedActions);

		if (!possibleActions.isEmpty()) {
			return possibleActions.get(Util.random.nextInt(possibleActions.size()));
		}

		// Priority 9 (last resort):
		return SexActionUtility.PARTNER_NONE;
	}

	/**
	 * Finger and tongue actions are considered foreplay.
	 */
	private SexAction performForeplayAction(SexActionInterface sexActionPlayer) {
		boolean debug = false;

		NPC performingCharacter = (NPC)Sex.getCharacterPerformingAction();
		GameCharacter targetedCharacter = Sex.getTargetedPartner(Sex.getCharacterPerformingAction());

		List<SexActionInterface> availableActions = new ArrayList<>(Sex.getAvailableSexActionsPartner());
		availableActions.removeIf(si -> !si.isAddedToAvailableSexActions() && !si.isAbleToAccessParts(performingCharacter));

		bannedActions.add(PartnerSelfFingerMouth.PARTNER_SELF_FINGER_MOUTH_PENETRATION);

		if(sexActionPlayer.getActionType()==SexActionType.STOP_ONGOING
				|| sexActionPlayer.equals(GenericActions.PLAYER_FORBID_PARTNER_SELF)
				|| sexActionPlayer.equals(GenericActions.PLAYER_STOP_PARTNER_SELF)
				|| sexActionPlayer.equals(GenericActions.PLAYER_STOP_ALL_PENETRATIONS)
				|| sexActionPlayer.equals(GenericActions.PLAYER_STOP_ALL_PENETRATIONS_SELF)) {
			availableActions.removeIf(sexAction -> sexAction.getActionType()==SexActionType.START_ONGOING || sexAction.getActionType()==SexActionType.START_ADDITIONAL_ONGOING);
//			System.out.println("hmm");
		}

		availableActions.removeAll(bannedActions);

		List<SexActionInterface> highPriorityList = new ArrayList<>();
		List<SexActionInterface> mediumPriorityList = new ArrayList<>();

		// If the NPC has a preference, they are more likely to choose actions related to that:
		SexType foreplayPreference = Sex.getForeplayPreference(performingCharacter, targetedCharacter);
		if(!Sex.isInForeplay(performingCharacter)) { // Use main sex preference if in main sex (as this method can be called as a fallthrough from performSexAction())
			foreplayPreference = Sex.getMainSexPreference(performingCharacter, targetedCharacter);
		}
		if(debug) {
			System.out.println(performingCharacter.getName(true)+" wants to use: "+foreplayPreference.toString());
		}
		if(foreplayPreference!=null) {
			for(SexActionInterface action : availableActions) {
				if((action.getPerformingCharacterAreas().contains(foreplayPreference.getPerformingSexArea()) && action.getTargetedCharacterAreas().contains(foreplayPreference.getTargetedSexArea()))
//						((action.getPerformingCharacterPenetrations().contains(foreplayPreference.getPerformingSexArea()) && action.getTargetedCharacterOrifices().contains(foreplayPreference.getTargetedSexArea()))
//						|| (action.getPerformingCharacterPenetrations().contains(foreplayPreference.getTargetedSexArea()) && action.getTargetedCharacterOrifices().contains(foreplayPreference.getPerformingSexArea()))
//						|| (action.getTargetedCharacterPenetrations().contains(foreplayPreference.getPerformingSexArea()) && action.getPerformingCharacterOrifices().contains(foreplayPreference.getTargetedSexArea()))
//						|| (action.getTargetedCharacterPenetrations().contains(foreplayPreference.getTargetedSexArea()) && action.getPerformingCharacterOrifices().contains(foreplayPreference.getPerformingSexArea())))
						&& action.getActionType() != SexActionType.STOP_ONGOING
						&& action.getParticipantType() != SexParticipantType.SELF) {
					highPriorityList.add(action);
					if(action.getActionType() == SexActionType.START_ONGOING
							|| action.getActionType()==SexActionType.START_ADDITIONAL_ONGOING) { // If a penetrative action is in the list, always return that first.
						if(debug) {
							System.out.println(performingCharacter.getName(true)+" performs "+action.getActionTitle()+" on "+targetedCharacter.getName(true));
						}
						return (SexAction) action;
					}
				}
			}

			if(!highPriorityList.isEmpty() && Math.random()<0.66f) {
				return (SexAction) highPriorityList.get(Util.random.nextInt(highPriorityList.size()));
			}
		}

		highPriorityList.clear();
		for (SexActionInterface action : availableActions) {
			if((action.getActionType() == SexActionType.START_ONGOING || action.getActionType()==SexActionType.START_ADDITIONAL_ONGOING || Sex.isCharacterEngagedInOngoingAction(performingCharacter, targetedCharacter))
					&& action.getActionType() != SexActionType.STOP_ONGOING
					&& action.getParticipantType()!=SexParticipantType.SELF) {
				if(!action.isTakesVirginity(false)) { // Do not want to take any virginity in foreplay TODO?
					highPriorityList.add(action);
				}
				mediumPriorityList.add(action);
			}
		}

		if(!highPriorityList.isEmpty()) {
			return (SexAction) highPriorityList.get(Util.random.nextInt(highPriorityList.size()));
		}

		if(!mediumPriorityList.isEmpty()) {
			return (SexAction) mediumPriorityList.get(Util.random.nextInt(mediumPriorityList.size()));
		}

		// --- Ban stop penetration actions ---

		for(SexActionInterface action : availableActions) {
			if(action.getActionType() == SexActionType.STOP_ONGOING) {
				bannedActions.add(action);
			}
		}

		return null;
	}

//	private boolean removedAllPenetrationAfterForeplay = false;

	private SexAction performSexAction(SexActionInterface sexActionPlayer) {

		NPC performingCharacter = (NPC)Sex.getCharacterPerformingAction();
		GameCharacter targetedCharacter = Sex.getTargetedPartner(Sex.getCharacterPerformingAction());

		boolean debug = false;
		boolean debugFullActionList = false;

		if(debug) {
			System.out.println();
			System.out.println("Character: "+performingCharacter.getName(true));
			System.out.println("Target: "+targetedCharacter.getName(true));
		}

		List<SexActionInterface> availableActions = new ArrayList<>(Sex.getAvailableSexActionsPartner()); //TODO need to check this
//		List<SexActionInterface> availableActions = new ArrayList<>(Sex.getActionsAvailablePartner(performingCharacter, targetedCharacter));
		availableActions.removeIf(si -> !si.isAddedToAvailableSexActions() && !si.isAbleToAccessParts(performingCharacter));

		if(sexActionPlayer.getActionType()==SexActionType.STOP_ONGOING
				|| sexActionPlayer.equals(GenericActions.PLAYER_FORBID_PARTNER_SELF)
				|| sexActionPlayer.equals(GenericActions.PLAYER_STOP_PARTNER_SELF)
				|| sexActionPlayer.equals(GenericActions.PLAYER_STOP_ALL_PENETRATIONS)
				|| sexActionPlayer.equals(GenericActions.PLAYER_STOP_ALL_PENETRATIONS_SELF)) {
			availableActions.removeIf(sexAction -> sexAction.getActionType()==SexActionType.START_ONGOING || sexAction.getActionType()==SexActionType.START_ADDITIONAL_ONGOING);
		}

		bannedActions.add(PartnerSelfFingerMouth.PARTNER_SELF_FINGER_MOUTH_PENETRATION);
		availableActions.removeAll(bannedActions);
		List<SexActionInterface> returnableActions = new ArrayList<>();

		boolean isSexPenetrationPossible = false;
		actionLoop:
		for(SexActionInterface action : Sex.getActionsAvailablePartner(performingCharacter, targetedCharacter)) {
//		for(SexActionInterface action : availableActions) { //TODO need to check this
			boolean penetrationAction = false;
			boolean sexOrifice = false;
			if(action.getParticipantType()!=SexParticipantType.SELF
					&& (action.getActionType()==SexActionType.START_ONGOING || action.getActionType()==SexActionType.START_ADDITIONAL_ONGOING)
					&& performingCharacter.calculateSexTypeWeighting(action.getAsSexType(), targetedCharacter, null)>0
					&& (action.isAddedToAvailableSexActions() || action.isAbleToAccessParts(performingCharacter))) {
				if(debugFullActionList) {
					System.out.println("A ");
				}
				for(SexAreaPenetration pen : action.getPerformingCharacterPenetrations()) {
					if(pen.isTakesVirginity()) {
						penetrationAction = true;
					}
				}
				for(SexAreaPenetration pen : action.getTargetedCharacterPenetrations()) {
					if(pen.isTakesVirginity()) {
						penetrationAction = true;
					}
				}
				if(penetrationAction) {
					for(SexAreaOrifice orifice : action.getPerformingCharacterOrifices()) {
						if(orifice.isInternalOrifice()) {
							sexOrifice = true;
						}
					}
					for(SexAreaOrifice orifice : action.getTargetedCharacterOrifices()) {
						if(orifice.isInternalOrifice()) {
							sexOrifice = true;
						}
					}
					if(sexOrifice) {
						isSexPenetrationPossible = true;
						break actionLoop;
					}
				}
			} else {
				if(debugFullActionList) {
					System.out.print("U ");
				}
			}
			if(debugFullActionList) {
				System.out.print("action: "+action.getActionTitle()+"\n");
			}
		}
		if(isSexPenetrationPossible) {
			isSexPenetrationPossible =
					((Sex.getMainSexPreference(performingCharacter, targetedCharacter)==null || Sex.getMainSexPreference(performingCharacter, targetedCharacter).getPerformingSexArea()==SexAreaPenetration.PENIS)
							&& performingCharacter.hasPenis() && performingCharacter.isAbleToAccessCoverableArea(CoverableArea.PENIS, true))
					|| ((Sex.getMainSexPreference(performingCharacter, targetedCharacter)==null || Sex.getMainSexPreference(performingCharacter, targetedCharacter).getPerformingSexArea()==SexAreaPenetration.TAIL)
							&& performingCharacter.getTailType().isSuitableForPenetration())
					|| ((Sex.getMainSexPreference(performingCharacter, targetedCharacter)==null || Sex.getMainSexPreference(performingCharacter, targetedCharacter).getTargetedSexArea()==SexAreaPenetration.PENIS)
							&& targetedCharacter.hasPenis() && targetedCharacter.isAbleToAccessCoverableArea(CoverableArea.PENIS, true))
					|| ((Sex.getMainSexPreference(performingCharacter, targetedCharacter)==null || Sex.getMainSexPreference(performingCharacter, targetedCharacter).getTargetedSexArea()==SexAreaPenetration.TAIL)
							&& targetedCharacter.getTailType().isSuitableForPenetration());
		}

		boolean isOngoingSexPenetration = false;
		// Is any sexual penetration happening, or is the NPC's preferred penetration happening:
		outerloop:
		for(SexAreaPenetration pen : SexAreaPenetration.values()) {
			if((pen.isTakesVirginity() || (Sex.getMainSexPreference(performingCharacter, targetedCharacter)!=null && Sex.getMainSexPreference(performingCharacter, targetedCharacter).getPerformingSexArea()==pen))
					&& Sex.getOngoingActionsMap(performingCharacter).get(pen).containsKey(targetedCharacter)) {
				for(SexAreaInterface sai : Sex.getOngoingActionsMap(performingCharacter).get(pen).get(targetedCharacter)) {
					if((sai.isOrifice() && ((SexAreaOrifice) sai).isInternalOrifice())
							|| (Sex.getMainSexPreference(performingCharacter, targetedCharacter)!=null && Sex.getMainSexPreference(performingCharacter, targetedCharacter).getTargetedSexArea()==sai
									&& (sai!=SexAreaOrifice.ASS || !targetedCharacter.isAbleToAccessCoverableArea(CoverableArea.ANUS, true)))) {
						isOngoingSexPenetration = true;
						if(debug) {
							System.out.println(performingCharacter.getName(true)+" isOngoingSexPenetration found 1");
						}
						break outerloop;
					}
				}
			}
			if((pen.isTakesVirginity() || (Sex.getMainSexPreference(performingCharacter, targetedCharacter)!=null && Sex.getMainSexPreference(performingCharacter, targetedCharacter).getTargetedSexArea()==pen))
					&& Sex.getOngoingActionsMap(targetedCharacter).get(pen).containsKey(performingCharacter)) {
				for(SexAreaInterface sai : Sex.getOngoingActionsMap(targetedCharacter).get(pen).get(performingCharacter)) {
					if((sai.isOrifice() && ((SexAreaOrifice) sai).isInternalOrifice())
							|| (Sex.getMainSexPreference(performingCharacter, targetedCharacter)!=null && Sex.getMainSexPreference(performingCharacter, targetedCharacter).getPerformingSexArea()==sai
									&& (sai!=SexAreaOrifice.ASS || !targetedCharacter.isAbleToAccessCoverableArea(CoverableArea.ANUS, true)))) {
						isOngoingSexPenetration = true;
						if(debug) {
							System.out.println(performingCharacter.getName(true)+" isOngoingSexPenetration found 2");
						}
						break outerloop;
					}
				}
			}
		}

		// If there is no real penetration going on:
		if(!isOngoingSexPenetration) {
			if(debug) {
				System.out.println("!isOngoingSexPenetration");
			}
			if(isSexPenetrationPossible) {
				if(debug) {
					System.out.println("isSexPenetrationPossible");
				}
				// --- Stop foreplay actions: ---
				for(SexActionInterface action : availableActions) {
					if(action.getActionType() == SexActionType.STOP_ONGOING) {
						// Don't stop kissing or fetishised oral actions:
						if(Sex.getMainSexPreference(performingCharacter, targetedCharacter)==null) { //TODO More testing
							if(!((action.getPerformingCharacterPenetrations().contains(SexAreaPenetration.TONGUE)
									|| action.getPerformingCharacterOrifices().contains(SexAreaOrifice.MOUTH))
									&& (action.getTargetedCharacterPenetrations().contains(SexAreaPenetration.TONGUE)
											|| action.getTargetedCharacterOrifices().contains(SexAreaOrifice.MOUTH)))
									&& !(performingCharacter.hasFetish(Fetish.FETISH_ORAL_RECEIVING)
											&& (action.getTargetedCharacterOrifices().contains(SexAreaOrifice.MOUTH) || action.getTargetedCharacterPenetrations().contains(SexAreaPenetration.TONGUE)))
									&& !(performingCharacter.hasFetish(Fetish.FETISH_ORAL_GIVING)
											&& (action.getPerformingCharacterOrifices().contains(SexAreaOrifice.MOUTH) || action.getPerformingCharacterPenetrations().contains(SexAreaPenetration.TONGUE)))) {
								returnableActions.add(action);
							}
						} else {
							returnableActions.add(action);
						}
					}
				}
				if(returnableActions.size()<=1) {
					Sex.removeCharacterBannedFromPositioning(performingCharacter);
	//				SexFlags.positioningBlockedPartner = false;
				}
				if(!returnableActions.isEmpty()) {
					return (SexAction) returnableActions.get(Util.random.nextInt(returnableActions.size()));
				}

				SexType mainSexPreference = Sex.getMainSexPreference(performingCharacter, targetedCharacter);

				// If the NPC has a preference, they are more likely to choose actions related to that:
				List<SexActionInterface> penetrativeActionList = new ArrayList<>();
				if(mainSexPreference!=null) {
					List<SexActionInterface> highPriorityList = new ArrayList<>();
					for(SexActionInterface action : availableActions) {
						if(
//								((action.getPerformingCharacterPenetrations().contains(Sex.getMainSexPreference(performingCharacter, targetedCharacter).getPerformingSexArea())
//								&& action.getTargetedCharacterOrifices().contains(Sex.getMainSexPreference(performingCharacter, targetedCharacter).getTargetedSexArea()))
//								|| (action.getTargetedCharacterPenetrations().contains(Sex.getMainSexPreference(performingCharacter, targetedCharacter).getTargetedSexArea())
//										&& action.getPerformingCharacterOrifices().contains(Sex.getMainSexPreference(performingCharacter, targetedCharacter).getPerformingSexArea())))
							(action.getPerformingCharacterAreas().contains(mainSexPreference.getPerformingSexArea()) && action.getTargetedCharacterAreas().contains(mainSexPreference.getTargetedSexArea()))
								&& action.getParticipantType()!=SexParticipantType.SELF
								&& action.getActionType() != SexActionType.STOP_ONGOING) {
							highPriorityList.add(action);
							if(action.getActionType() == SexActionType.START_ONGOING
									|| action.getActionType()==SexActionType.START_ADDITIONAL_ONGOING) { // If a penetrative action is in the list, always return that first.
								penetrativeActionList.add(action);
							}
						}
					}

					if(!penetrativeActionList.isEmpty()) {
						return (SexAction) penetrativeActionList.get(Util.random.nextInt(penetrativeActionList.size()));
					}

					if(penetrativeActionList.isEmpty() && !highPriorityList.isEmpty() && Math.random()<0.66f) { // 2/3 chance, so that there is some chance of using other actions as well:
						return (SexAction) highPriorityList.get(Util.random.nextInt(highPriorityList.size()));
					}
				}

				// --- Start penetrating: ---
				if(debug) {
					System.out.println("Start penetrating");
				}
				for(SexActionInterface action : availableActions) {
					if((action.getActionType()==SexActionType.START_ONGOING || action.getActionType()==SexActionType.START_ADDITIONAL_ONGOING)
							&& action.getParticipantType()!=SexParticipantType.SELF) {

						if(performingCharacter.hasFetish(Fetish.FETISH_IMPREGNATION)
									&& action.getTargetedCharacterOrifices().contains(SexAreaOrifice.VAGINA)
									&& action.getPerformingCharacterPenetrations().contains(SexAreaPenetration.PENIS)) {
							return (SexAction) action; // Instantly return, as this takes priority above everything else
						}

						if(performingCharacter.hasFetish(Fetish.FETISH_PREGNANCY)
								&& action.getPerformingCharacterOrifices().contains(SexAreaOrifice.VAGINA)
								&& action.getTargetedCharacterPenetrations().contains(SexAreaPenetration.PENIS)) {
							return (SexAction) action; // Instantly return, as this takes priority above everything else
						}

						if(action.getPerformingCharacterPenetrations().contains(SexAreaPenetration.PENIS)
								|| action.getPerformingCharacterPenetrations().contains(SexAreaPenetration.TAIL)
								|| action.getTargetedCharacterPenetrations().contains(SexAreaPenetration.PENIS)
								|| action.getTargetedCharacterPenetrations().contains(SexAreaPenetration.TAIL)) {
							if(!Collections.disjoint(performingCharacter.getFetishes(true), action.getFetishes(performingCharacter))) {
								penetrativeActionList.add(action); // Start any penis/tail penetrations that are loved by the performing character
							} else {
								returnableActions.add(action);
							}
						}
					}
				}

				// Perform filtering, preferring to return actions that penetrate an actual orifice:

				// Prefer penetrativeActionList (used as high priority list):
				penetrativeActionList.removeAll(bannedActions);

				List<SexActionInterface> actualOrifices = new ArrayList<>(penetrativeActionList);
				for(SexActionInterface si : penetrativeActionList) {
					boolean orificeFound = false;
					for(SexAreaOrifice orifice : si.getPerformingCharacterOrifices()) {
						if(orifice.isInternalOrifice()) {
							orificeFound = true;
							break;
						}
					}
					for(SexAreaOrifice orifice : si.getTargetedCharacterOrifices()) {
						if(orifice.isInternalOrifice()) {
							orificeFound = true;
							break;
						}
					}
					if(!orificeFound) {
						actualOrifices.remove(si);
					}
				}

				List<SexActionInterface> nonPenetrativeOrifices = new ArrayList<>(penetrativeActionList);
				nonPenetrativeOrifices.removeAll(actualOrifices);

				if(!actualOrifices.isEmpty()) {
					return (SexAction) actualOrifices.get(Util.random.nextInt(actualOrifices.size()));

				} else if(!nonPenetrativeOrifices.isEmpty()) {
					return (SexAction) nonPenetrativeOrifices.get(Util.random.nextInt(nonPenetrativeOrifices.size()));
				}

				// If no entries in penetrativeActionList, use returnableActions:
				returnableActions.removeAll(bannedActions);

				actualOrifices = new ArrayList<>(returnableActions);
				actualOrifices.removeIf((ac) -> (!ac.getPerformingCharacterOrifices().isEmpty() && !ac.getPerformingCharacterOrifices().get(0).isInternalOrifice())
						&& (!ac.getTargetedCharacterOrifices().isEmpty() && !ac.getTargetedCharacterOrifices().get(0).isInternalOrifice()));

				nonPenetrativeOrifices = new ArrayList<>(returnableActions);
				nonPenetrativeOrifices.removeAll(actualOrifices);

				if(!actualOrifices.isEmpty()) {
					return (SexAction) actualOrifices.get(Util.random.nextInt(actualOrifices.size()));

				} else if(!nonPenetrativeOrifices.isEmpty()) {
					return (SexAction) nonPenetrativeOrifices.get(Util.random.nextInt(nonPenetrativeOrifices.size()));
				}

			} else { // If there is no sex penetration available, just use foreplay:
				return performForeplayAction(sexActionPlayer);
			}
		}

		// Ban stop penetration actions:
		for(SexActionInterface action : availableActions) {
			if(action.getActionType() == SexActionType.STOP_ONGOING) {
				if(performingCharacter.getMainSexPreference(targetedCharacter)!=null
						&& (action.getPerformingCharacterAreas().contains(performingCharacter.getMainSexPreference(targetedCharacter).getPerformingSexArea())
								&& action.getTargetedCharacterAreas().contains(performingCharacter.getMainSexPreference(targetedCharacter).getTargetedSexArea()))) {
					if(debug) {
						System.out.println("banned action from preference");
					}
					bannedActions.add(action); // If they don't mind the associated fetishes, stop them from stopping it
				} else {
					int weight = ((NPC)Sex.getCharacterPerformingAction()).calculateSexTypeWeighting(action.getAsSexType(), targetedCharacter, null);

					if(weight<0) {
						return (SexAction) action; // If they don't like the associated fetishes, stop it.
					} else {
						bannedActions.add(action); // If they don't mind the associated fetishes, stop them from stopping it
					}

//					for(Fetish f : action.getFetishes(performingCharacter)) {
//						if(!performingCharacter.getFetishDesire(f).isNegative()) {
//							bannedActions.add(action); // If they don't mind the associated fetishes, stop them from stopping it
//						} else {
//							return (SexAction) action; // IF they don't like the associated fetishes, stop it.
//						}
//					}
				}
			}
		}

		return null;
	}

	@Override
	public void assignNPCTarget(GameCharacter targeter) {

		List<GameCharacter> availableTargets = new ArrayList<>(Sex.getAllParticipants());

		availableTargets.removeIf((character) -> Sex.getSexPositionSlot(character)==SexSlotGeneric.MISC_WATCHING);

		GameCharacter preferredTarget = ((NPC) targeter).getPreferredSexTarget();

		// Always target those who are about to cum:
		if(Sex.isReadyToOrgasm(targeter) && SexFlags.playerPreparedForCharactersOrgasm.contains(targeter)) {
			availableTargets.clear();
			List<GameCharacter> floorTargets = new ArrayList<>();

			for(GameCharacter character : Sex.getAllParticipants()) {
				if(!character.equals(targeter) && Sex.isAbleToTarget(character)) {
					List<OrgasmCumTarget> cumTargets = Sex.getSexManager().getPosition().getSexInteractions(Sex.getSexPositionSlot(targeter), Sex.getSexPositionSlot(character)).getAvailableCumTargets();
					if(cumTargets!=null && !cumTargets.isEmpty()) {
						if(cumTargets.size()>1 || cumTargets.get(0)!=OrgasmCumTarget.FLOOR) {
							availableTargets.add(character);
						} else {
							floorTargets.add(character);
						}
					}
				}
			}

			if(availableTargets.isEmpty()) {
				availableTargets.addAll(floorTargets);
			}

			if(!availableTargets.isEmpty()) {
				if(preferredTarget!=null && availableTargets.contains(preferredTarget)) {
					Sex.setTargetedPartner(targeter, preferredTarget);
					return;
				}
				Sex.setTargetedPartner(targeter, Util.randomItemFrom(availableTargets));
				return;
			}
		}

		if(preferredTarget!=null && availableTargets.contains(preferredTarget)) {
			Sex.setTargetedPartner(targeter, preferredTarget);
			return;
		}

		// If positioning is blocked, then only prioritise characters that actually have actions available that the targeter wants to perform:
		if(!this.isPositionChangingAllowed(targeter)) {
			Map<GameCharacter, SexType> sexPreferences = new HashMap<>();

			for(GameCharacter character : availableTargets) {
				if(Sex.getActionsAvailablePartner(targeter, character)!=null && !Sex.getActionsAvailablePartner(targeter, character).isEmpty()) {
					if(Sex.isInForeplay(targeter)) {
						sexPreferences.put(character, Sex.getForeplayPreference(((NPC)targeter), character));
					} else {
						sexPreferences.put(character, Sex.getMainSexPreference(((NPC)targeter), character));
					}
				}
			}

			List<GameCharacter> priorityTargets = new ArrayList<>();

			characterLoop:
			for(GameCharacter character : availableTargets) {
				SexType preference = sexPreferences.get(character);
				if(preference!=null) {
					for(SexActionInterface action : Sex.isReadyToOrgasm(targeter)?Sex.getOrgasmActionsPartner(targeter, character):Sex.getActionsAvailablePartner(targeter, character)) {
						if(action.getParticipantType()!=SexParticipantType.SELF
								&& (action.getPerformingCharacterOrifices().contains(preference.getPerformingSexArea())
									|| action.getPerformingCharacterPenetrations().contains(preference.getPerformingSexArea()))
								&& (action.getTargetedCharacterOrifices().contains(preference.getTargetedSexArea())
										|| action.getTargetedCharacterPenetrations().contains(preference.getTargetedSexArea()))) {
							priorityTargets.add(character);
//							System.out.println(targeter.getName()+" prioritising "+character.getName());
							continue characterLoop;
						}
					}
				}
			}

			if(!priorityTargets.isEmpty()) {
				availableTargets.clear();
				availableTargets.addAll(priorityTargets);
			}
		}

		// Select a target:

		Map<GameCharacter, Integer> weightedTargets = new HashMap<>();

		for(GameCharacter character : availableTargets) {
			if(!character.equals(targeter) && Sex.isAbleToTarget(character)) {

				if(Sex.getActionsAvailablePartner(targeter, character).isEmpty()) { // If no interactions, skip character.
					continue;
				}

				Set<SexActionInterface> availableActions = Sex.isReadyToOrgasm(targeter)?Sex.getOrgasmActionsPartner(targeter, character):Sex.getActionsAvailablePartner(targeter, character);

				int attractionModifier = 100;

				 // If targeter is not attracted to the character, or if the character is in the same dom/sub type, and is not attracted to the targeter, then they are a hundred times less likely to target them:
				if(!targeter.isAttractedTo(character) || (Sex.isDom(targeter)==Sex.isDom(character) && !character.isAttractedTo(targeter))) {
					attractionModifier/=100;
				}

				if(!availableActions.isEmpty()) {
					if(Sex.isReadyToOrgasm(targeter) && Sex.getCharactersHavingOngoingActionWith(targeter, SexAreaPenetration.PENIS).contains(character)) {
						weightedTargets.put(character, 100*attractionModifier); // Prioritise characters who the targeter is penetrating when orgasming.

					} else if(Sex.isDom(targeter) != Sex.isDom(character)) {
						weightedTargets.put(character, 5*attractionModifier*availableActions.size()); // Prioritise targeting opposite character by a factor of five (i.e subs target doms and vice versa).

					} else {
						if(Sex.getSexPace(targeter)!=SexPace.SUB_RESISTING) { // If resisting, don't target a sub (as resisting actions are targeted towards doms).
							weightedTargets.put(character, 1*attractionModifier*availableActions.size());
						}
					}
				}
			}
		}

		if(weightedTargets.isEmpty()) {
			Sex.setTargetedPartner(targeter, Util.randomItemFrom(availableTargets));
		} else {
			Sex.setTargetedPartner(targeter, Util.getRandomObjectFromWeightedMap(weightedTargets));
		}
	}

}