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

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

import com.lilithsthrone.game.character.GameCharacter;
import com.lilithsthrone.game.character.body.valueEnums.LegConfiguration;
import com.lilithsthrone.game.dialogue.responses.ResponseTag;
import com.lilithsthrone.game.sex.SexPace;
import com.lilithsthrone.game.sex.managers.SexManagerDefault;
import com.lilithsthrone.game.sex.positions.SexPosition;
import com.lilithsthrone.game.sex.positions.slots.SexSlot;
import com.lilithsthrone.game.sex.positions.slots.SexSlotAllFours;
import com.lilithsthrone.game.sex.positions.slots.SexSlotLyingDown;
import com.lilithsthrone.game.sex.positions.slots.SexSlotStanding;

/**
 * A generic sex manager, meant to be used for initialising generic sex scenes. Will typically start in the 'standing' position.
 *
 * @since 0.3.1
 * @version 0.3.4
 * @author Innoxia
 */
public class SMGeneric extends SexManagerDefault {

	List<GameCharacter> dominantSpectators;
	List<GameCharacter> submissiveSpectators;
	List<ResponseTag> tags;

	public SMGeneric(
			List<GameCharacter> dominants,
			List<GameCharacter> submissives,
			List<GameCharacter> dominantSpectators,
			List<GameCharacter> submissiveSpectators,
			ResponseTag... tags) {
		this(dominants, submissives, dominantSpectators, submissiveSpectators, Arrays.asList(tags));
	}

	public SMGeneric(
			List<GameCharacter> dominants,
			List<GameCharacter> submissives,
			List<GameCharacter> dominantSpectators,
			List<GameCharacter> submissiveSpectators,
			List<ResponseTag> tags) {
		super(null, null, null);

		this.dominantSpectators = new ArrayList<>();
		if(dominantSpectators!=null) {
			this.dominantSpectators.addAll(dominantSpectators);
			this.dominantSpectators.removeIf(sp -> sp==null);
		}

		this.submissiveSpectators = new ArrayList<>();
		if(submissiveSpectators!=null) {
			this.submissiveSpectators.addAll(submissiveSpectators);
			this.submissiveSpectators.removeIf(sp -> sp==null);
		}

		boolean nonBiped=false;
		for(GameCharacter character : submissives) {
			if(character.getLegConfiguration()!=LegConfiguration.BIPEDAL) {
				nonBiped=true;
				break;
			}
		}
		if(!nonBiped) {
			for(GameCharacter character : dominants) {
				if(character.getLegConfiguration()!=LegConfiguration.BIPEDAL) {
					nonBiped=true;
					break;
				}
			}
		}

		this.tags = tags;

		SexSlot[] slotsDominant;
		SexSlot[] slotsSubmissive;
		if(nonBiped) { // This scene contains characters who are non-bipedal, so use the SexPositionOther classes:
			if(tags.contains(ResponseTag.PREFER_DOGGY)) {
				this.position = SexPosition.ALL_FOURS;
				if(submissives.size()==1) {
					slotsDominant = new SexSlot[] {SexSlotAllFours.BEHIND, SexSlotAllFours.IN_FRONT};
				} else {
					slotsDominant = new SexSlot[] {SexSlotAllFours.BEHIND, SexSlotAllFours.BEHIND_TWO, SexSlotAllFours.IN_FRONT, SexSlotAllFours.IN_FRONT_TWO};
				}
				slotsSubmissive = new SexSlot[] {SexSlotAllFours.ALL_FOURS, SexSlotAllFours.ALL_FOURS_TWO, SexSlotAllFours.ALL_FOURS_THREE, SexSlotAllFours.ALL_FOURS_FOUR};

			} else if(tags.contains(ResponseTag.PREFER_ORAL)) {
				this.position = SexPosition.STANDING;
				slotsDominant = new SexSlot[] {SexSlotStanding.STANDING_DOMINANT, SexSlotStanding.STANDING_DOMINANT_TWO, SexSlotStanding.STANDING_DOMINANT_THREE, SexSlotStanding.STANDING_DOMINANT_FOUR};
				if(dominants.size()==1) {
					if(dominants.get(0).hasPenis() || !dominants.get(0).isTaur()) {
						slotsSubmissive = new SexSlot[] {SexSlotStanding.PERFORMING_ORAL, SexSlotStanding.PERFORMING_ORAL_BEHIND, SexSlotStanding.PERFORMING_ORAL_TWO, SexSlotStanding.PERFORMING_ORAL_BEHIND_TWO};
					} else {
						slotsSubmissive = new SexSlot[] {SexSlotStanding.PERFORMING_ORAL_BEHIND, SexSlotStanding.PERFORMING_ORAL, SexSlotStanding.PERFORMING_ORAL_BEHIND_TWO, SexSlotStanding.PERFORMING_ORAL_TWO};
					}
				} else {
					slotsSubmissive = new SexSlot[] {SexSlotStanding.PERFORMING_ORAL, SexSlotStanding.PERFORMING_ORAL_TWO, SexSlotStanding.PERFORMING_ORAL_THREE, SexSlotStanding.PERFORMING_ORAL_FOUR};
				}

			} else if(tags.contains(ResponseTag.PREFER_COW_GIRL)) {
				this.position = SexPosition.LYING_DOWN;
				if(submissives.size()==1) {
					slotsDominant = new SexSlot[] {SexSlotLyingDown.COWGIRL, SexSlotLyingDown.FACE_SITTING_REVERSE};
				} else {
					slotsDominant = new SexSlot[] {SexSlotLyingDown.COWGIRL, SexSlotLyingDown.COWGIRL_TWO, SexSlotLyingDown.FACE_SITTING_REVERSE, SexSlotLyingDown.FACE_SITTING_REVERSE_TWO};
				}
				slotsSubmissive = new SexSlot[] {SexSlotLyingDown.LYING_DOWN, SexSlotLyingDown.LYING_DOWN_TWO, SexSlotLyingDown.LYING_DOWN_THREE, SexSlotLyingDown.LYING_DOWN_FOUR};

			} else if(tags.contains(ResponseTag.PREFER_MISSIONARY)) {
				this.position = SexPosition.LYING_DOWN;
				if(submissives.size()==1) {
					if(dominants.size()>=2 &&!dominants.get(0).hasPenis()) {
						slotsDominant = new SexSlot[] {SexSlotLyingDown.FACE_SITTING_REVERSE, SexSlotLyingDown.MISSIONARY, SexSlotLyingDown.BESIDE, SexSlotLyingDown.BESIDE_TWO};

					} else {
						slotsDominant = new SexSlot[] {SexSlotLyingDown.MISSIONARY, SexSlotLyingDown.FACE_SITTING_REVERSE, SexSlotLyingDown.BESIDE, SexSlotLyingDown.BESIDE_TWO};
					}
				} else {
					slotsDominant = new SexSlot[] {SexSlotLyingDown.MISSIONARY, SexSlotLyingDown.MISSIONARY_TWO, SexSlotLyingDown.FACE_SITTING_REVERSE, SexSlotLyingDown.FACE_SITTING_REVERSE_TWO};
				}
				slotsSubmissive = new SexSlot[] {SexSlotLyingDown.LYING_DOWN, SexSlotLyingDown.LYING_DOWN_TWO, SexSlotLyingDown.LYING_DOWN_THREE, SexSlotLyingDown.LYING_DOWN_FOUR};

			} else {
				this.position = SexPosition.STANDING;
				slotsDominant = new SexSlot[] {SexSlotStanding.STANDING_DOMINANT, SexSlotStanding.STANDING_DOMINANT_TWO, SexSlotStanding.STANDING_DOMINANT_THREE, SexSlotStanding.STANDING_DOMINANT_FOUR};
				slotsSubmissive = new SexSlot[] {SexSlotStanding.STANDING_SUBMISSIVE, SexSlotStanding.STANDING_SUBMISSIVE_TWO, SexSlotStanding.STANDING_SUBMISSIVE_THREE, SexSlotStanding.STANDING_SUBMISSIVE_FOUR};
			}

		} else {
			if(tags.contains(ResponseTag.PREFER_DOGGY)) {
				this.position = SexPosition.ALL_FOURS;
				if(submissives.size()==1) {
					slotsDominant = new SexSlot[] {SexSlotAllFours.BEHIND, SexSlotAllFours.IN_FRONT, SexSlotAllFours.IN_FRONT_TWO, SexSlotAllFours.USING_FEET};
				} else {
					slotsDominant = new SexSlot[] {
							SexSlotAllFours.BEHIND, SexSlotAllFours.BEHIND_TWO,
							SexSlotAllFours.IN_FRONT, SexSlotAllFours.IN_FRONT_TWO,
							SexSlotAllFours.IN_FRONT_THREE, SexSlotAllFours.IN_FRONT_FOUR};
				}
				slotsSubmissive = new SexSlot[] {SexSlotAllFours.ALL_FOURS, SexSlotAllFours.ALL_FOURS_TWO, SexSlotAllFours.ALL_FOURS_THREE, SexSlotAllFours.ALL_FOURS_FOUR};

			} else if(tags.contains(ResponseTag.PREFER_ORAL)) {
				this.position = SexPosition.STANDING;
				slotsDominant = new SexSlot[] {
						SexSlotStanding.STANDING_DOMINANT,
						SexSlotStanding.STANDING_DOMINANT_TWO,
						SexSlotStanding.STANDING_DOMINANT_THREE,
						SexSlotStanding.STANDING_DOMINANT_FOUR};
				if(dominants.size()==1) {
					slotsSubmissive = new SexSlot[] {SexSlotStanding.PERFORMING_ORAL, SexSlotStanding.PERFORMING_ORAL_TWO, SexSlotStanding.PERFORMING_ORAL_THREE, SexSlotStanding.PERFORMING_ORAL_BEHIND};
				} else {
					slotsSubmissive = new SexSlot[] {
							SexSlotStanding.PERFORMING_ORAL,
							SexSlotStanding.PERFORMING_ORAL_TWO,
							SexSlotStanding.PERFORMING_ORAL_THREE,
							SexSlotStanding.PERFORMING_ORAL_FOUR};
				}

			} else if(tags.contains(ResponseTag.PREFER_COW_GIRL)) {
				this.position = SexPosition.LYING_DOWN;
				if(submissives.size()==1) {
					slotsDominant = new SexSlot[] {SexSlotLyingDown.COWGIRL, SexSlotLyingDown.FACE_SITTING_REVERSE};
				} else {
					slotsDominant = new SexSlot[] {SexSlotLyingDown.COWGIRL, SexSlotLyingDown.COWGIRL_TWO, SexSlotLyingDown.FACE_SITTING_REVERSE, SexSlotLyingDown.FACE_SITTING_REVERSE_TWO};
				}
				slotsSubmissive = new SexSlot[] {SexSlotLyingDown.LYING_DOWN, SexSlotLyingDown.LYING_DOWN_TWO, SexSlotLyingDown.LYING_DOWN_THREE, SexSlotLyingDown.LYING_DOWN_FOUR};

			} else if(tags.contains(ResponseTag.PREFER_MISSIONARY)) {
				this.position = SexPosition.LYING_DOWN;
				if(submissives.size()==1) {
					if(dominants.size()>=2 &&!dominants.get(0).hasPenis()) {
						slotsDominant = new SexSlot[] {SexSlotLyingDown.FACE_SITTING_REVERSE, SexSlotLyingDown.MISSIONARY, SexSlotLyingDown.BESIDE, SexSlotLyingDown.BESIDE_TWO};

					} else {
						slotsDominant = new SexSlot[] {SexSlotLyingDown.MISSIONARY, SexSlotLyingDown.FACE_SITTING_REVERSE, SexSlotLyingDown.BESIDE, SexSlotLyingDown.BESIDE_TWO};
					}
				} else {
					slotsDominant = new SexSlot[] {SexSlotLyingDown.MISSIONARY, SexSlotLyingDown.MISSIONARY_TWO, SexSlotLyingDown.FACE_SITTING_REVERSE, SexSlotLyingDown.FACE_SITTING_REVERSE_TWO};
				}
				slotsSubmissive = new SexSlot[] {SexSlotLyingDown.LYING_DOWN, SexSlotLyingDown.LYING_DOWN_TWO, SexSlotLyingDown.LYING_DOWN_THREE, SexSlotLyingDown.LYING_DOWN_FOUR};

			} else {
				this.position = SexPosition.STANDING;
				slotsDominant = new SexSlot[] {SexSlotStanding.STANDING_DOMINANT, SexSlotStanding.STANDING_DOMINANT_TWO, SexSlotStanding.STANDING_DOMINANT_THREE, SexSlotStanding.STANDING_DOMINANT_FOUR};
				slotsSubmissive = new SexSlot[] {SexSlotStanding.STANDING_SUBMISSIVE, SexSlotStanding.STANDING_SUBMISSIVE_TWO, SexSlotStanding.STANDING_SUBMISSIVE_THREE, SexSlotStanding.STANDING_SUBMISSIVE_FOUR};
			}
		}
		setUpVariables(dominants, slotsDominant, submissives, slotsSubmissive);
	}

	private void setUpVariables(List<GameCharacter> dominants, SexSlot[] slotsDominant, List<GameCharacter> submissives, SexSlot[] slotsSubmissive) {
		Map<GameCharacter, SexSlot> newPositionDominants = new HashMap<>();
		Map<GameCharacter, SexSlot> newPositionSubmissives = new HashMap<>();

		int i = 0;
		for(GameCharacter character : dominants) { // Assign dominants to slots, and if there aren't enough slots, put dominants as dominant spectators:
			if(i<slotsDominant.length) {
				newPositionDominants.put(character, slotsDominant[i]);
			} else {
				this.dominantSpectators.add(character);
			}
			i++;
		}
		this.dominants = newPositionDominants;

		i = 0;
		for(GameCharacter character : submissives) { // Assign submissives to slots, and if there aren't enough slots, put submissives as submissive spectators:
			if(i<slotsSubmissive.length) {
				newPositionSubmissives.put(character, slotsSubmissive[i]);
			} else {
				this.submissiveSpectators.add(character);
			}
			i++;
		}
		this.submissives = newPositionSubmissives;
	}

	public List<GameCharacter> getDominantSpectators() {
		return dominantSpectators;
	}

	public List<GameCharacter> getSubmissiveSpectators() {
		return submissiveSpectators;
	}

	@Override
	public SexPace getForcedSexPace(GameCharacter character) {
		for(ResponseTag tag : tags) {
			switch(tag) {
				case PREFER_DOGGY:
				case PREFER_MISSIONARY:
				case PREFER_ORAL:
				case PREFER_COW_GIRL:
				case DISABLE_POSITIONING:
					break;
				case START_PACE_PLAYER_DOM_GENTLE:
					if(character.isPlayer()) {
						return SexPace.DOM_GENTLE;
					}
					break;
				case START_PACE_PLAYER_DOM_ROUGH:
					if(character.isPlayer()) {
						return SexPace.DOM_ROUGH;
					}
					break;
				case START_PACE_PLAYER_SUB_EAGER:
					if(character.isPlayer()) {
						return SexPace.SUB_EAGER;
					}
					break;
				case START_PACE_PLAYER_SUB_RESISTING:
					if(character.isPlayer()) {
						return SexPace.SUB_RESISTING;
					}
					break;
			}
		}
		return super.getForcedSexPace(character);
	}

}


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

import java.util.Map;

import com.lilithsthrone.game.character.GameCharacter;
import com.lilithsthrone.game.sex.managers.SexManagerDefault;
import com.lilithsthrone.game.sex.positions.SexPosition;
import com.lilithsthrone.game.sex.positions.slots.SexSlot;

/**
 * @since 0.1.98
 * @version 0.3.4
 * @author Innoxia
 */
public class SMLyingDown extends SexManagerDefault {

	public SMLyingDown(Map<GameCharacter, SexSlot> dominants, Map<GameCharacter, SexSlot> submissives) {
		super(SexPosition.LYING_DOWN,
				dominants,
				submissives);
	}
}

repo_name: klorpa/Pedo

license: unknown


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

import java.util.Map;

import com.lilithsthrone.game.character.GameCharacter;
import com.lilithsthrone.game.sex.managers.SexManagerDefault;
import com.lilithsthrone.game.sex.positions.SexPosition;
import com.lilithsthrone.game.sex.positions.slots.SexSlot;

/**
 * @since 0.1.69.9
 * @version 0.3.3.10
 * @author Innoxia
 */
public class SMSitting extends SexManagerDefault {

	public SMSitting(Map<GameCharacter, SexSlot> dominants, Map<GameCharacter, SexSlot> submissives) {
		super(SexPosition.SITTING,
				dominants,
				submissives);
	}

}


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

import java.util.Map;

import com.lilithsthrone.game.character.GameCharacter;
import com.lilithsthrone.game.sex.managers.SexManagerDefault;
import com.lilithsthrone.game.sex.positions.SexSlot;
import com.lilithsthrone.game.sex.positions.SexPositionBipeds;

/**
 * @since 0.1.69
 * @version 0.1.97
 * @author Innoxia
 */
public class SMSixtyNine extends SexManagerDefault {

	public SMSixtyNine(Map<GameCharacter, SexSlot> dominants, Map<GameCharacter, SexSlot> submissives) {
		super(SexPositionBipeds.SIXTY_NINE,
				dominants,
				submissives);
	}

}


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

import java.util.Map;

import com.lilithsthrone.game.character.GameCharacter;
import com.lilithsthrone.game.sex.managers.SexManagerDefault;
import com.lilithsthrone.game.sex.positions.SexPosition;
import com.lilithsthrone.game.sex.positions.slots.SexSlot;

/**
 * @since 0.1.69
 * @version 0.3.4
 * @author Innoxia
 */
public class SMStanding extends SexManagerDefault {

	public SMStanding(Map<GameCharacter, SexSlot> dominants, Map<GameCharacter, SexSlot> submissives) {
		super(SexPosition.STANDING,
				dominants,
				submissives);
	}

}


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

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.Set;

import com.lilithsthrone.game.character.GameCharacter;
import com.lilithsthrone.game.character.body.BodyPartInterface;
import com.lilithsthrone.game.sex.OrgasmCumTarget;
import com.lilithsthrone.game.sex.Sex;
import com.lilithsthrone.game.sex.SexActionInteractions;
import com.lilithsthrone.game.sex.SexAreaInterface;
import com.lilithsthrone.game.sex.SexAreaOrifice;
import com.lilithsthrone.game.sex.SexAreaPenetration;
import com.lilithsthrone.game.sex.positions.slots.SexSlot;
import com.lilithsthrone.game.sex.positions.slots.SexSlotTag;
import com.lilithsthrone.game.sex.sexActions.SexActionInterface;
import com.lilithsthrone.game.sex.sexActions.SexActionPresets;
import com.lilithsthrone.game.sex.sexActions.SexActionType;
import com.lilithsthrone.utils.Util;
import com.lilithsthrone.utils.Util.Value;

/**
 * SexPositions determine what actions are available for each AbstractSexSlot.

 *
 * Each value holds a map, slotTargets, which maps AbstractSexSlots to a map of AbstractSexSlots, which in turn maps to positions available.
 *  By providing a character's position in sex, along with the position of the partner they're targeting, this map is used to fetch available actions.

 *
 *  Example:

 *  getSexInteractions(character1AbstractSexSlot, character2AbstractSexSlot)
returns the SexActionPresetPair which holds all available actions.

 *
 *  If character1AbstractSexSlot is SexPositionSlot.DOGGY_ON_ALL_FOURS, and character2SexPositionSlot is SexPositionSlot.DOGGY_BEHIND, then the returned actions would be those that
 *   are available for the character on all fours, in relation to a character kneeling behind them.
 *
 * @since 0.1.97
 * @version 0.3.4.5
 * @author Innoxia
 */
public abstract class AbstractSexPosition {

	private String name;
	private int maximumSlots;
	private boolean addStandardActions;

	private List<Class<?>> positioningClasses;
	private List<Class<?>> specialClasses;

	public static List<SexAreaOrifice> genericGroinForceCreampieAreas = Util.newArrayListOfValues(SexAreaOrifice.ANUS, SexAreaOrifice.VAGINA, SexAreaOrifice.URETHRA_VAGINA, SexAreaOrifice.URETHRA_PENIS);
	public static List<SexAreaOrifice> genericFaceForceCreampieAreas = Util.newArrayListOfValues(SexAreaOrifice.MOUTH);

	public AbstractSexPosition(String name,
			int maximumSlots,
			boolean addStandardActions,
			List<Class<?>> positioningClasses,
			List<Class<?>> specialClasses) {
		this.name = name;
		this.maximumSlots = maximumSlots;
		this.addStandardActions = addStandardActions;
		this.positioningClasses = positioningClasses;
		this.specialClasses = specialClasses;
	}

	public String getName() {
		return name;
	}

	public boolean isAddStandardActions() {
		return addStandardActions;
	}

	public List<Class<?>> getPositioningClasses() {
		return positioningClasses;
	}

	public List<Class<?>> getSpecialClasses() {
		return specialClasses;
	}

	public abstract String getDescription(Map<GameCharacter, SexSlot> occupiedSlots);

	public Value<Boolean, String> isAcceptablePosition(Map<GameCharacter, SexSlot> positioningSlots) {
		return new Value<Boolean, String>(true, "");
	}

	public Value<Boolean, String> isSlotUnlocked(GameCharacter characterToTakeSlot, SexSlot slot, Map<GameCharacter, SexSlot> positioningSlots) {
		return new Value<Boolean, String>(true, "");
	}

	public boolean isActionBlocked(GameCharacter performer, GameCharacter target, SexActionInterface action) {
		if(action.getActionType()==SexActionType.START_ONGOING
				|| action.getActionType()==SexActionType.REQUIRES_NO_PENETRATION_AND_EXPOSED
				|| action.getActionType()==SexActionType.REQUIRES_NO_PENETRATION) {

			// Block penis+non-appendage-non-pussy actions if target's penis is already in use:
			try {
				// Trying to interact a penis with a character who is already using a penis:
				if(action.getSexAreaInteractions().containsKey(SexAreaPenetration.PENIS)
						&& Collections.disjoint(action.getSexAreaInteractions().values(), SexActionPresets.appendageAreas)) {
					boolean ongoingAllowedFound = false;
					for(SexAreaInterface sa : Sex.getContactingSexAreas(target, SexAreaPenetration.PENIS, performer)) {
						if(!SexActionPresets.allowedInterPenetrationAreas.contains(sa)) {
							return true;
						} else if(sa==SexAreaOrifice.VAGINA) {
							ongoingAllowedFound = true;
						}
					}
					if(ongoingAllowedFound) {
						for(SexAreaInterface sa : action.getSexAreaInteractions().values()) {
							if(!SexActionPresets.allowedInterPenetrationAreas.contains(sa)) {
								return true;
							}
						}
					}
				}
			}catch(Exception ex) {}
			try {
				// Trying to interact a penis with a character who is already using a penis:
				if(action.getSexAreaInteractions().values().contains(SexAreaPenetration.PENIS)
						&& Collections.disjoint(action.getSexAreaInteractions().keySet(), SexActionPresets.appendageAreas)) {
					boolean ongoingAllowedFound = false;
					for(SexAreaInterface sa : Sex.getContactingSexAreas(performer, SexAreaPenetration.PENIS, target)) {
						if(!SexActionPresets.allowedInterPenetrationAreas.contains(sa)) {
							return true;
						} else if(sa==SexAreaOrifice.VAGINA) {
							ongoingAllowedFound = true;
						}
					}
					if(ongoingAllowedFound) {
						for(SexAreaInterface sa : action.getSexAreaInteractions().keySet()) {
							if(!SexActionPresets.allowedInterPenetrationAreas.contains(sa)) {
								return true;
							}
						}
					}
				}
			} catch(Exception ex) {}

			// Block tribbing and thigh sex if ongoing penis/vagina or penis/anus penetration:
			Set<SexAreaOrifice> impossibleTribbingAreas = Util.newHashSetOfValues(SexAreaOrifice.ANUS, SexAreaOrifice.VAGINA, SexAreaOrifice.THIGHS);
			if(action.getSexAreaInteractions().containsKey(SexAreaPenetration.CLIT) && action.getSexAreaInteractions().values().contains(SexAreaPenetration.CLIT)
				&& ((Sex.getOngoingActionsMap(performer).containsKey(SexAreaPenetration.PENIS) && Sex.getOngoingActionsMap(performer).get(SexAreaPenetration.PENIS).values().stream().anyMatch((set)->!Collections.disjoint(set, impossibleTribbingAreas)))
					|| (Sex.getOngoingActionsMap(target).containsKey(SexAreaPenetration.PENIS) && Sex.getOngoingActionsMap(target).get(SexAreaPenetration.PENIS).values().stream().anyMatch((set)->!Collections.disjoint(set, impossibleTribbingAreas))))) {
				return true;
			}
			if(((action.getSexAreaInteractions().containsKey(SexAreaPenetration.PENIS) && !Collections.disjoint(action.getSexAreaInteractions().values(), impossibleTribbingAreas))
					|| (!Collections.disjoint(action.getSexAreaInteractions().keySet(), impossibleTribbingAreas) && action.getSexAreaInteractions().values().contains(SexAreaPenetration.PENIS)))
				&& ((Sex.getOngoingActionsMap(performer).containsKey(SexAreaPenetration.CLIT) && Sex.getOngoingActionsMap(performer).get(SexAreaPenetration.CLIT).values().stream().anyMatch((set)->set.contains(SexAreaPenetration.CLIT)))
					|| (Sex.getOngoingActionsMap(target).containsKey(SexAreaPenetration.CLIT) && Sex.getOngoingActionsMap(target).get(SexAreaPenetration.CLIT).values().stream().anyMatch((set)->set.contains(SexAreaPenetration.CLIT))))) {
				return true;
			}

			if(!(Sex.getSexPositionSlot(performer).hasTag(SexSlotTag.SIXTY_NINE) && Sex.getSexPositionSlot(target).hasTag(SexSlotTag.LYING_DOWN))
					&& !(Sex.getSexPositionSlot(target).hasTag(SexSlotTag.SIXTY_NINE) && Sex.getSexPositionSlot(performer).hasTag(SexSlotTag.LYING_DOWN))) {
				boolean ongoingGroinToGroin = false;
				boolean ongoingGroinToBreasts = false;
				boolean ongoingGroinToMouth = false;

				for(SexAreaInterface sArea : SexActionPresets.groinAreas) {
					// Groin-groin actions:
					if((Sex.getOngoingActionsMap(target).containsKey(sArea)
							&& Sex.getOngoingActionsMap(target).get(sArea).containsKey(performer)
							&& !Collections.disjoint(Sex.getOngoingActionsMap(target).get(sArea).get(performer), SexActionPresets.groinAreas))
						|| (Sex.getOngoingActionsMap(performer).containsKey(sArea)
							&& Sex.getOngoingActionsMap(performer).get(sArea).containsKey(target)
							&& !Collections.disjoint(Sex.getOngoingActionsMap(performer).get(sArea).get(target), SexActionPresets.groinAreas))) {
						ongoingGroinToGroin = true;
					}
					// Groin-breast actions:
					if((Sex.getOngoingActionsMap(target).containsKey(sArea)
							&& Sex.getOngoingActionsMap(target).get(sArea).containsKey(performer)
							&& !Collections.disjoint(Sex.getOngoingActionsMap(target).get(sArea).get(performer), SexActionPresets.breastAreas))
						|| (Sex.getOngoingActionsMap(performer).containsKey(sArea)
							&& Sex.getOngoingActionsMap(performer).get(sArea).containsKey(target)
							&& !Collections.disjoint(Sex.getOngoingActionsMap(performer).get(sArea).get(target), SexActionPresets.breastAreas))) {
						ongoingGroinToBreasts = true;
					}
					// Groin-mouth actions:
					if((Sex.getOngoingActionsMap(target).containsKey(sArea)
							&& Sex.getOngoingActionsMap(target).get(sArea).containsKey(performer)
							&& !Collections.disjoint(Sex.getOngoingActionsMap(target).get(sArea).get(performer), SexActionPresets.mouthAreas))
						|| (Sex.getOngoingActionsMap(performer).containsKey(sArea)
							&& Sex.getOngoingActionsMap(performer).get(sArea).containsKey(target)
							&& !Collections.disjoint(Sex.getOngoingActionsMap(performer).get(sArea).get(target), SexActionPresets.mouthAreas))) {
						ongoingGroinToMouth = true;
					}
				}

				// Block oral + groin actions if there is any groin-groin or groin-breast action going on:
				if((!Collections.disjoint(action.getSexAreaInteractions().keySet(), SexActionPresets.groinAreas) && !Collections.disjoint(action.getSexAreaInteractions().values(), SexActionPresets.mouthAreas))
						|| (!Collections.disjoint(action.getSexAreaInteractions().values(), SexActionPresets.groinAreas) && !Collections.disjoint(action.getSexAreaInteractions().keySet(), SexActionPresets.mouthAreas))) {
					return ongoingGroinToGroin || ongoingGroinToBreasts;
				}
				// Block groin + breast actions if there is any groin-mouth or groin-groin action going on:
				if((!Collections.disjoint(action.getSexAreaInteractions().keySet(), SexActionPresets.groinAreas) && !Collections.disjoint(action.getSexAreaInteractions().values(), SexActionPresets.breastAreas))
						|| (!Collections.disjoint(action.getSexAreaInteractions().values(), SexActionPresets.groinAreas) && !Collections.disjoint(action.getSexAreaInteractions().keySet(), SexActionPresets.breastAreas))) {
					return ongoingGroinToGroin || ongoingGroinToMouth;
				}
			}
		}

		return false;
	}

	public int getMaximumSlots() {
		return maximumSlots;
	}
//		Set<SexSlot> uniqueSlots = new HashSet<>();
//
//		for(Entry<SexSlot, Map<SexSlot, SexActionInteractions>> e : getSlotTargets().entrySet()) {
//			uniqueSlots.add(e.getKey());
//			uniqueSlots.addAll(e.getValue().keySet());
//		}
//
//		return uniqueSlots.size();
//	}

	public Set<SexSlot> getAllAvailableSexPositions() {
		Set<SexSlot> positions = new HashSet<>(getSlotTargets().keySet());

		getSlotTargets().entrySet().stream().forEach(e -> positions.addAll(e.getValue().keySet()));

		return positions;
	}

	/**
	 * Key is role position. Value is list of all slots that this slot can interact with.
	 */
	public abstract Map<SexSlot, Map<SexSlot, SexActionInteractions>> getSlotTargets();

	protected static Map<SexSlot, Map<SexSlot, SexActionInteractions>> generateSlotTargetsMap(List<Value<SexSlot, Map<SexSlot, SexActionInteractions>>> values) {
		Map<SexSlot, Map<SexSlot, SexActionInteractions>> returnMap = new HashMap<>();

		for(Value<SexSlot, Map<SexSlot, SexActionInteractions>> value : values) {
			returnMap.putIfAbsent(value.getKey(), new HashMap<>());

			for(Entry<SexSlot, SexActionInteractions> innerValue : value.getValue().entrySet()) {
				returnMap.get(value.getKey()).put(innerValue.getKey(), innerValue.getValue());
			}
		}
//		System.out.println("size: "+returnMap.size());
		return returnMap;
	}

	public SexActionInteractions getSexInteractions(SexSlot performer, SexSlot target) {
		if(getSlotTargets().containsKey(performer) && getSlotTargets().get(performer).containsKey(target)) {
			return getSlotTargets().get(performer).get(target);
		}

		// If the targeted sex position is not defined, allow cumming on floor:
		return new SexActionInteractions(null,
				Util.newArrayListOfValues(OrgasmCumTarget.FLOOR),
				Util.newArrayListOfValues(OrgasmCumTarget.FLOOR));
	}

	/**
	 * Override to set limitations on the amount of penetration types allowed in this position.
	 * For example, the 'Mating Press' position defines fingers(i.e. hands) as being bottomMax-topMax for bottom, and topMax-bottomMax for top, as the one on top is pinning down as many of the bottom's hands as they can.
	 * @return A map of penetration types, the value to which they are mapped representing the penetration count modifier. The value should always be a negative integer.
	 */
	public Map<SexAreaPenetration, Integer> getRestrictedPenetrationCounts(GameCharacter penetrator) {
		return null;
	}

	/**
	 * The underlying map of body parts to orifice lists which is used in the public method isForcedCreampieEnabled(). This is overridden in SexPositionTypes that need to define forced creampies.
	 *
	 * @param cumTarget The character who is both receiving and forcing the creampie.
	 * @param cumProvider The one who is being forced to cum inside the cumTarget.
	 * @return A map containing keys of body parts, which then map to lists of orifices.
	 * The key represents the body part that can be used by the cumTarget in order to force the cumProvider to cum inside any of the orifices in the value list.
	 */
	protected Map<Class<? extends BodyPartInterface>, List<SexAreaOrifice>> getForcedCreampieMap(GameCharacter cumTarget, GameCharacter cumProvider) {
		return null;
	}

	/**
	 * Taking into account the AbstractSexSlot of the two characters specified, as well as the body part being used, this method returns a list of areas which the cumTarget can force the cumProvider to cum inside of.
	 * This is used in determining whether the 'leg-lock', 'tail-lock', 'tentacle-lock', or 'force creampie' actions (in the GenericOrgasms class) are available.
	 *
	 * @param bodyPartUsed The body part which the cumTarget is using to force the creampie. Will almost certainly be either:

	 * {@link Arm.class}: Always requires at least two free arms, and for arms to not be restricted.

	 * {@link Leg.class}: Always requires at least two free legs, and for legs to not be restricted.

	 * {@link Tail.class}: Requires tail(s), and for them to be prehensile.

	 * {@link Tentacle.class}: Requires tentacle(s).

	 * {@link Skin.class}: Used to represent the full body being used.
	 * @param orifice The orifice into which the creampie is to be forced.
	 * @param cumTarget The character who is both receiving and forcing the creampie.
	 * @param cumProvider The one who is being forced to cum inside the cumTarget.
	 * @return True if the orifice can be forcibly creampied by the supplied body part.
	 */
	public boolean isForcedCreampieEnabled(Class<? extends BodyPartInterface> bodyPartUsed, SexAreaOrifice orifice, GameCharacter cumTarget, GameCharacter cumProvider) {
		if(getForcedCreampieMap(cumTarget, cumProvider)!=null && getForcedCreampieMap(cumTarget, cumProvider).containsKey(bodyPartUsed)) {
			return getForcedCreampieMap(cumTarget, cumProvider).get(bodyPartUsed).contains(orifice);
		}
		return false;
	}
}