code:
package com.lilithsthrone.game.dialogue.responses;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import com.lilithsthrone.game.PropertyValue;
import com.lilithsthrone.game.character.GameCharacter;
import com.lilithsthrone.game.character.attributes.Attribute;
import com.lilithsthrone.game.character.attributes.CorruptionLevel;
import com.lilithsthrone.game.character.body.valueEnums.Femininity;
import com.lilithsthrone.game.character.effects.AbstractPerk;
import com.lilithsthrone.game.character.effects.StatusEffect;
import com.lilithsthrone.game.character.fetishes.Fetish;
import com.lilithsthrone.game.character.race.Race;
import com.lilithsthrone.game.combat.CombatMove;
import com.lilithsthrone.game.dialogue.DialogueNode;
import com.lilithsthrone.game.dialogue.utils.UtilText;
import com.lilithsthrone.game.sex.Sex;
import com.lilithsthrone.game.sex.SexAreaInterface;
import com.lilithsthrone.game.sex.SexControl;
import com.lilithsthrone.game.sex.SexPace;
import com.lilithsthrone.game.sex.sexActions.SexActionType;
import com.lilithsthrone.main.Main;
import com.lilithsthrone.utils.Colour;
import com.lilithsthrone.utils.Util;

/**
 * @since 0.1.69
 * @version 0.3.4.5
 * @author Innoxia
 */
public class Response {

	public static final int DEFAULT_TIME_PASSED_VALUE = Integer.MIN_VALUE;

	protected String title;
	protected String tooltipText;
	protected DialogueNode nextDialogue;

	protected List<Fetish> fetishesRequired;
	protected CorruptionLevel corruptionBypass;
	private List<AbstractPerk> perksRequired;
	private Femininity femininityRequired;
	private Race raceRequired;

	private CombatMove combatMove;

	// Sex action variables:

	private SexActionType sexActionType;

	private GameCharacter characterPerformingSexAction;
	private List<SexAreaInterface> sexAreaAccessRequiredForPerformer;

	private GameCharacter characterTargetedForSexAction;
	private List<SexAreaInterface> sexAreaAccessRequiredForTargeted;

	public Response(String title,
			String tooltipText,
			DialogueNode nextDialogue) {

		this(title, tooltipText, nextDialogue,
				null, null,
				null, null, null);
	}

	public Response(String title,
			String tooltipText,
			DialogueNode nextDialogue,
			List<Fetish> fetishesForUnlock,
			CorruptionLevel corruptionBypass,
			List<AbstractPerk> perksRequired,
			Femininity femininityRequired,
			Race raceRequired) {

		this(title, tooltipText, nextDialogue,
				fetishesForUnlock, corruptionBypass,
				perksRequired, femininityRequired, raceRequired,
				null, null, null, null, null);
	}

	public Response(String title,
			String tooltipText,
			DialogueNode nextDialogue,
			List<Fetish> fetishesForUnlock,
			CorruptionLevel corruptionBypass,
			List<AbstractPerk> perksRequired,
			Femininity femininityRequired,
			Race raceRequired,
			SexActionType sexActionType,
			GameCharacter characterPenetrating,
			Collection<SexAreaInterface> sexAreaAccessRequiredForPerformer,
			GameCharacter characterPenetrated,
			Collection<SexAreaInterface> sexAreaAccessRequiredForTargeted) {

		this.title = UtilText.parse(title);
		this.tooltipText = UtilText.parse(tooltipText);
		this.nextDialogue = nextDialogue;

		this.fetishesRequired = fetishesForUnlock;
		this.corruptionBypass = corruptionBypass;
		this.perksRequired = perksRequired;
		this.femininityRequired = femininityRequired;
		this.raceRequired = raceRequired;

		combatMove = null;

		// Sex action variables:

		this.sexActionType = sexActionType;

		this.sexAreaAccessRequiredForPerformer = new ArrayList<>();
		if(sexAreaAccessRequiredForPerformer!=null) {
			this.sexAreaAccessRequiredForPerformer.addAll(sexAreaAccessRequiredForPerformer);
		}

		this.sexAreaAccessRequiredForTargeted = new ArrayList<>();
		if(sexAreaAccessRequiredForTargeted!=null) {
			this.sexAreaAccessRequiredForTargeted.addAll(sexAreaAccessRequiredForTargeted);
		}

		this.characterPerformingSexAction=characterPenetrating;
		this.characterTargetedForSexAction=characterPenetrated;
	}

	public String getTitle() {
		return title;
	}

	public String getTooltipText() {
		return tooltipText;
	}

	public DialogueNode getNextDialogue() {
		if(isAvailable() || isAbleToBypass()) {
			return nextDialogue;
		} else {
			return null;
		}
	}

	/**
	 * When this returns a value other than DEFAULT_TIME_PASSED_VALUE, then it overrides the next DialogueNode's getSecondsPassed method, and is therefore used to determine how much time passes when selecting this Response.
	 *
	 * @return The number of seconds that pass when choosing this response.
	 */
	public int getSecondsPassed() {
		return DEFAULT_TIME_PASSED_VALUE;
	}

	public boolean disabledOnNullDialogue(){
		return true;
	}

	public boolean isSexHighlight() {
		return false;
	}

	public boolean isSexPenetrationHighlight() {
		return false;
	}

	public boolean isSexPositioningHighlight() {
		return false;
	}

	public boolean isCombatHighlight() {
		return false;
	}

	public boolean isCorruptionHighlight() {
		return false;
	}

	public boolean isTradeHighlight() {
		return false;
	}

	public CombatMove getAssociatedCombatMove() {
		return combatMove;
	}

	public Colour getHighlightColour() {
		if(isSexHighlight()) {
			return Colour.GENERIC_SEX;

		} else if(isSexPenetrationHighlight()) {
			return Colour.GENERIC_SEX;

		} else if(isSexPositioningHighlight()) {
			return Colour.GENERIC_ARCANE;

		} else if(isCombatHighlight()) {
			return Colour.GENERIC_COMBAT;

		} else if(isCorruptionHighlight()) {
			return Colour.ATTRIBUTE_CORRUPTION;

		} else if(isTradeHighlight()) {
			return Colour.BASE_YELLOW_LIGHT;

		} else {
			return Colour.TEXT;
		}
	}

	public SexPace getSexPace() {
		return null;
	}

	public SexActionType getSexActionType() {
		return null;
	}

	/**
	 * @return true if this response is generated from a SexAction which applies a START_ONGOING action while the related body parts are already in use (thus switching them).
	 */
	public boolean isSexActionSwitch() {
		return false;
	}
	/**
	 * @return Typically null, unless this method is overridden in order to set special requirements related to the availability of a sex action of type START_ADDITIONAL_ONGOING.
	 *  The keys correspond descriptions of requirements, while the value is used to determine if this requirement is met.
	 */
	public Map<String, Boolean> getAdditionalOngoingAvailableMap() {
		return null;
	}

	/**
	 * @return true if all values in the getAdditionalOngoingAvailableMap() are true.
	 */
	private boolean isAvailableFromAdditionalOngoingAvailableMap() {
		return getAdditionalOngoingAvailableMap()!=null && !getAdditionalOngoingAvailableMap().values().contains(false);
	}

	public final void applyEffects() {
		effects();
	}

	public void effects() {
	}

	/**
	 * @return true if this response has any related requirements in order for it to be selected.
	 */
	public boolean hasRequirements() {
		return fetishesRequired != null
				|| corruptionBypass != null
				|| perksRequired != null
				|| femininityRequired != null
				|| raceRequired != null
				|| sexActionType==SexActionType.SPEECH
				|| getAdditionalOngoingAvailableMap()!=null
				|| !sexAreaAccessRequiredForPerformer.isEmpty()
				|| !sexAreaAccessRequiredForTargeted.isEmpty();
	}

	/**
	 * @return true if this action has no requirements, or if all requirements are met.
	 */
	public boolean isAvailable(){
		if(!hasRequirements()) {
			return true;
		}
		boolean corruptionOrFetishReqs = false;
		if(sexActionType!=null) {
			corruptionOrFetishReqs = isCorruptionWithinRange() || isAvailableFromFetishes() || (corruptionBypass==null && fetishesRequired==null);
		} else {
			if(corruptionBypass==null) {
				corruptionOrFetishReqs = isAvailableFromFetishes() || fetishesRequired==null;
			} else {
				corruptionOrFetishReqs = isCorruptionWithinRange() || isAvailableFromFetishes();
			}
		}

		return corruptionOrFetishReqs
					&& !isBlockedFromPerks()
					&& isFemininityInRange()
					&& isRequiredRace()
					&& (sexActionType!=SexActionType.SPEECH || !Sex.isOngoingActionsBlockingSpeech(Main.game.getPlayer()))
					&& (isAvailableFromAdditionalOngoingAvailableMap() || (isPenetrationTypeAvailable() && isOrificeTypeAvailable()));
	}

	/**
	 * @return true if this action is not available from the requirements, and is instead available due to being able to bypass the corruption requirements.
	 */
	public boolean isAbleToBypass(){
		if(!isAvailable()
				&& (!Main.game.isInSex() || Main.getProperties().hasValue(PropertyValue.bypassSexActions))
				&& !isBlockedFromPerks()
				&& isFemininityInRange()
				&& isRequiredRace()
				&& !isAvailableFromFetishes()
				&& (isAvailableFromAdditionalOngoingAvailableMap() || (isPenetrationTypeAvailable() && isOrificeTypeAvailable()))) {
			if(!Main.game.isInSex() && corruptionBypass==null) { // DO not allow bypass out of sex if there is no corruption bypassing
				return false;
			}
			return !isCorruptionWithinRange();
		}

		return false;
	}

	private StringBuilder SB;
	public String getTooltipCorruptionBypassText() {
		SB = new StringBuilder();

		if(!isAvailable() && !isAbleToBypass()) {
			SB.append("This action is being blocked, due to not meeting certain [style.colourBad(requirements)].");
		} else {
			if(isAvailableFromFetishes()) {
				SB.append("Your [style.colourFetish(fetish)] bypasses this action's [style.colourCorruption(corruption)] requirements!");
				return SB.toString();
			}

			if(corruptionBypass != null) {
				if(isCorruptionWithinRange())
					SB.append("Your "+Util.capitaliseSentence(Main.game.getPlayer().getCorruptionLevel().getName())+""
							+ " [style.colourCorruption(corruption)] has unlocked this action!");
				else
					SB.append("You will gain +"+corruptionBypass.getCorruptionBypass()+" [style.boldCorruption(corruption)], as you don't meet the [style.colourCorruption(corruption)] or [style.colourFetish(fetish)] requirements!");
			} else {
				SB.append("This action cannot be unlocked with [style.colourCorruption(corruption)].");
			}
		}

		return SB.toString();
	}

	public String getAdditionalSexActionInformationText() {
		if(this.getSexActionType()==SexActionType.START_ADDITIONAL_ONGOING) {
			return "This action will cause you to [style.colourSex(join in)] with the related ongoing action.";

		} else if(isSwitchOngoingActionAvailable()) {
			return "This action will cause [style.colourCorruption(some ongoing actions to be stopped)] before starting the related ongoing action.";
		}
		return "";
	}

	private boolean isSwitchOngoingActionAvailable() {
		if(this.sexActionType ==SexActionType.START_ONGOING
				&& Sex.getCharacterPerformingAction().isPlayer()
				&& Sex.getSexControl(characterPerformingSexAction).getValue()>=SexControl.ONGOING_PLUS_LIMITED_PENETRATIONS.getValue()) {
//			if(Sex.getCharactersHavingOngoingActionWith(characterTargetedForSexAction, this.sexAreaAccessRequiredForTargeted.get(0)).size()>1
//					|| (!Sex.getCharactersHavingOngoingActionWith(characterTargetedForSexAction, this.sexAreaAccessRequiredForTargeted.get(0)).contains(characterTargetedForSexAction)
//							&& !Sex.getCharactersHavingOngoingActionWith(characterTargetedForSexAction, this.sexAreaAccessRequiredForTargeted.get(0)).contains(Main.game.getPlayer()))) {
//				return false;
//			}
			List<GameCharacter> ongoingTargetedAreaCharacters = Sex.getCharactersHavingOngoingActionWith(characterTargetedForSexAction, this.sexAreaAccessRequiredForTargeted.get(0));
			List<GameCharacter> ongoingPerformingAreaCharacters = Sex.getCharactersHavingOngoingActionWith(characterPerformingSexAction, this.sexAreaAccessRequiredForPerformer.get(0));

			// If targeted area is having multiple ongoing actions, or non-self actions that do not involve the player do not allow switch:
			if(ongoingTargetedAreaCharacters.size()>1 || ongoingPerformingAreaCharacters.size()>1) {
				return false;
			}
			if(!ongoingTargetedAreaCharacters.isEmpty()
					&& !ongoingTargetedAreaCharacters.contains(characterTargetedForSexAction)
					&& !ongoingTargetedAreaCharacters.contains(Main.game.getPlayer())) {
				return false;
			}

			try {
				return !Sex.getOngoingActionsMap(characterPerformingSexAction).get(this.sexAreaAccessRequiredForPerformer.get(0)).get(characterTargetedForSexAction).contains(this.sexAreaAccessRequiredForTargeted.get(0));
			} catch(Exception ex) {
				return true;
			}
		} else {
			return false;
		}
	}

	public String getTooltipBlockingList(){
		SB = new StringBuilder();

		if(perksRequired!=null) {
			for(AbstractPerk p : perksRequired){
				if(Main.game.getPlayer().hasTrait(p, true)) {
					SB.append("
"
							+"Requirement"
							+ " (Perk): "
							+Util.capitaliseSentence(p.getName(Main.game.getPlayer())));
				} else {
					SB.append("
"
							+"Requirement"
							+ " (Perk): "
							+Util.capitaliseSentence(p.getName(Main.game.getPlayer())));
				}
			}
		}

		if(femininityRequired!=null) {
			if(isFemininityInRange()) {
				SB.append("
"
						+"Requirement"
						+ " (Femininity): "
						+ ""+Util.capitaliseSentence(femininityRequired.getName(false))+"");
			} else {
				SB.append("
"
						+"Requirement"
						+ " (Femininity): "
						+ ""+Util.capitaliseSentence(femininityRequired.getName(false))+"");
			}
		}

		if(raceRequired!=null) {
			if(isRequiredRace()) {
				SB.append("
"
						+"Requirement"
						+ " (Race): "
						+""+Util.capitaliseSentence(raceRequired.getName(false))+"");
			} else {
				SB.append("
"
						+"Requirement"
						+ " (Race): "
						+""+Util.capitaliseSentence(raceRequired.getName(false))+"");
			}
		}

		if(sexActionType==SexActionType.SPEECH) {
			if(!Sex.isOngoingActionsBlockingSpeech(Main.game.getPlayer())) {
				SB.append("
"
						+"Requirement"
						+ " (Speech): [style.colourMinorGood(Unblocked mouth)]");
			} else {
				SB.append("
"
						+"Requirement"
						+ " (Speech): [style.colourMinorBad(Unblocked mouth)]");
			}
		}

		if(getAdditionalOngoingAvailableMap()!=null) {
			for(Entry<String, Boolean> e : getAdditionalOngoingAvailableMap().entrySet()) {
				if(e.getValue()) {
					SB.append("
[style.colourGood("+Util.capitaliseSentence(UtilText.parse(Sex.getCharacterPerformingAction(), Sex.getTargetedPartner(Sex.getCharacterPerformingAction()), e.getKey()))+")]");
				} else {
					SB.append("
[style.colourBad("+Util.capitaliseSentence(UtilText.parse(Sex.getCharacterPerformingAction(), Sex.getTargetedPartner(Sex.getCharacterPerformingAction()), e.getKey()))+")]");
				}
			}

		} else {
			if(sexAreaAccessRequiredForPerformer!=null && characterPerformingSexAction!=null) {
				boolean penetrationAccess = true;
				for(SexAreaInterface sArea : this.sexAreaAccessRequiredForPerformer) {
					if(sArea!=null && (!characterPerformingSexAction.isSexAreaExposed(sArea) || (getSexActionType()==SexActionType.REQUIRES_NO_PENETRATION && characterPerformingSexAction.isCoverableAreaBlockedFromGroping(sArea.getRelatedCoverableArea())))) {
						penetrationAccess = false;
					}
				}
				boolean penetrationFree = true;
				if(!isSwitchOngoingActionAvailable()) {
					for(SexAreaInterface sArea : this.sexAreaAccessRequiredForPerformer) {
						if(sArea!=null && !sArea.isFree(characterPerformingSexAction)) {
							penetrationFree = false;
						}
					}
				}

				for(SexAreaInterface sArea : sexAreaAccessRequiredForPerformer) {
					if(sArea!=null) {
						String penetrationName = Util.capitaliseSentence(sArea.getName(characterPerformingSexAction));
						String accessText = (penetrationAccess?"access":"access");
						String freeText = (penetrationFree?"free":"free");
						String targetName = (characterPerformingSexAction.isPlayer()?"Your":UtilText.parse(characterPerformingSexAction, "[npc.Name]'s"));

						if(getSexActionType()==SexActionType.REQUIRES_NO_PENETRATION_AND_EXPOSED
								|| getSexActionType()==SexActionType.START_ONGOING
								|| getSexActionType()==SexActionType.REQUIRES_NO_PENETRATION) {
							if(penetrationAccess && penetrationFree) {
								SB.append("
Requirement ("+accessText+" & "+freeText+"): "+targetName+" "+ penetrationName);
							} else {
								SB.append("
Requirement ("+accessText+" & "+freeText+"): "+targetName+" "+ penetrationName);
							}

						} else if(getSexActionType()==SexActionType.REQUIRES_EXPOSED) {
							if(penetrationAccess) {
								SB.append("
Requirement ("+accessText+"): "+targetName+" "+ penetrationName);
							} else {
								SB.append("
Requirement ("+accessText+"): "+targetName+" "+ penetrationName);
							}

						} else {
							if(penetrationAccess) {
								SB.append("
Requirement ("+accessText+"): "+targetName+" "+ penetrationName);
							} else {
								SB.append("
Requirement ("+accessText+"): "+targetName+" "+ penetrationName);
							}
						}
					}
				}
			}

			if(sexAreaAccessRequiredForTargeted!=null && characterTargetedForSexAction!=null) {
				boolean orificeAccess = true;
				for(SexAreaInterface sArea : this.sexAreaAccessRequiredForTargeted) {
					if(sArea!=null && (!characterTargetedForSexAction.isSexAreaExposed(sArea) || (getSexActionType()==SexActionType.REQUIRES_NO_PENETRATION && characterTargetedForSexAction.isCoverableAreaBlockedFromGroping(sArea.getRelatedCoverableArea())))) {
						orificeAccess = false;
					}
				}
				boolean orificeFree = true;
				if(!isSwitchOngoingActionAvailable()) {
					for(SexAreaInterface sArea : this.sexAreaAccessRequiredForTargeted) {
						if(sArea!=null && !sArea.isFree(characterTargetedForSexAction)) {
							orificeFree = false;
						}
					}
				}

				for(SexAreaInterface sArea : sexAreaAccessRequiredForTargeted) {
					if(sArea!=null) {
						String orificeName = Util.capitaliseSentence(sArea.getName(characterTargetedForSexAction));
						String accessText = (orificeAccess?"access":"access");
						String freeText = (orificeFree?"free":"free");
						String targetName = (characterTargetedForSexAction.isPlayer()?"Your":UtilText.parse(characterTargetedForSexAction, "[npc.Name]'s"));

						if(getSexActionType()==SexActionType.REQUIRES_NO_PENETRATION_AND_EXPOSED
								|| getSexActionType()==SexActionType.START_ONGOING
								|| getSexActionType()==SexActionType.REQUIRES_NO_PENETRATION) {
							if(orificeAccess && orificeFree) {
								SB.append("
Requirement ("+accessText+" & "+freeText+"): "+targetName+" "+ orificeName);
							} else {
								SB.append("
Requirement ("+accessText+" & "+freeText+"): "+targetName+" "+ orificeName);
							}

						} else if(getSexActionType()==SexActionType.REQUIRES_EXPOSED) {
							if(orificeAccess) {
								SB.append("
Requirement ("+accessText+"): "+targetName+" "+ orificeName);
							} else {
								SB.append("
Requirement ("+accessText+"): "+targetName+" "+ orificeName);
							}

						} else {
							if(orificeAccess) {
								SB.append("
Requirement ("+accessText+"): "+targetName+" "+ orificeName);
							} else {
								SB.append("
Requirement ("+accessText+"): "+targetName+" "+ orificeName);
							}
						}
					}
				}
			}
		}

		return SB.toString();
	}

	public String getTooltipRequiredList(){
		SB = new StringBuilder();

		if(fetishesRequired!=null) {
			for(Fetish f : fetishesRequired){
				if(Main.game.getPlayer().hasFetish(f)) {
					SB.append("
"
							+"Associated Fetish"
							+ " (owned): "
							+ Util.capitaliseSentence(f.getName(Main.game.getPlayer())));

				} else {
					SB.append("
"
							+"Associated Fetish"
							+ " (not owned): "
							+ Util.capitaliseSentence(f.getName(Main.game.getPlayer())));
				}
			}
		}

		if(corruptionBypass!=null) {
			if(isCorruptionWithinRange()) {
				SB.append("
"
						+"Associated Corruption"
						+ " (within range): "
						+ Util.capitaliseSentence(corruptionBypass.getName()));
			} else {
				SB.append("
"
						+"Associated Corruption"
						+ " (out of range): "
						+ Util.capitaliseSentence(corruptionBypass.getName()));
			}
		}

		return SB.toString();
	}

	public int lineHeight(){
		int lineHeight = 0;

		if(perksRequired!=null) {
			lineHeight+=perksRequired.size();
		}
		if(femininityRequired!=null) {
			lineHeight++;
		}
		if(raceRequired!=null) {
			lineHeight++;
		}
		if(sexActionType==SexActionType.SPEECH) {
			lineHeight++;
		}

		if(fetishesRequired!=null) {
			lineHeight+=fetishesRequired.size();
		}
		if(corruptionBypass!=null) {
			lineHeight++;
		}

		if(getAdditionalOngoingAvailableMap()!=null) {
			lineHeight+=getAdditionalOngoingAvailableMap().size();
		}
		if(sexAreaAccessRequiredForPerformer!=null) {
			lineHeight ++;
		}
		if(sexAreaAccessRequiredForTargeted!=null) {
			lineHeight ++;
		}

		return lineHeight;
	}

	public boolean isCorruptionWithinRange() {
		return corruptionBypass != null && corruptionBypass.getMinimumValue() <= Main.game.getPlayer().getAttributeValue(Attribute.MAJOR_CORRUPTION);
	}

	public boolean isAvailableFromFetishes() {
		if(fetishesRequired==null) {
			return false;
		}

		for (Fetish f : fetishesRequired) {
			if(Main.game.getPlayer().hasFetish(f)) {
				if(f==Fetish.FETISH_PURE_VIRGIN) {
					if(Main.game.getPlayer().hasStatusEffect(StatusEffect.FETISH_PURE_VIRGIN)) { // Virginity fetish only blocks if player is still a virgin.
						return true;
					}
				} else {
					return true;
				}
			}
		}
		return false;
	}

	public boolean isBlockedFromPerks() {
		if(perksRequired==null) {
			return false;
		}
		for (AbstractPerk p : perksRequired) {
			if(!Main.game.getPlayer().hasPerkAnywhereInTree(p)) {
				return true;
			}
		}
		return false;
	}

	public boolean isFemininityInRange() {
		if(femininityRequired==null) {
			return true;
		}

		switch(femininityRequired){
			case ANDROGYNOUS:
				return Femininity.valueOf(Main.game.getPlayer().getFemininityValue()) == Femininity.ANDROGYNOUS;
			case FEMININE:
			case FEMININE_STRONG:
				return Main.game.getPlayer().getFemininityValue() >= femininityRequired.getMinimumFemininity();
			case MASCULINE:
			case MASCULINE_STRONG:
				return Main.game.getPlayer().getFemininityValue() <= femininityRequired.getMaximumFemininity();
			default:
				return true;
		}
	}

	public boolean isRequiredRace() {
		return raceRequired == null || Main.game.getPlayer().getRace() == raceRequired;
	}

	public boolean isPenetrationTypeAvailable() {
		if(sexAreaAccessRequiredForPerformer==null || sexAreaAccessRequiredForPerformer.isEmpty()) {
			return true;
		}

		// Don't care if exposed or not:
		if(getSexActionType()!=null) {
			switch(getSexActionType()){
				case REQUIRES_NO_PENETRATION:
					for(SexAreaInterface sArea : sexAreaAccessRequiredForPerformer) {
						if(sArea!=null && (!sArea.isFree(characterPerformingSexAction) || characterPerformingSexAction.isCoverableAreaBlockedFromGroping(sArea.getRelatedCoverableArea()))) {
							return false;
						}
					}
					return true;
				case START_ONGOING:
					// Allow characters who have control to switch from one ongoing penetration to another
					if(!isSwitchOngoingActionAvailable()) {
						for(SexAreaInterface sArea : sexAreaAccessRequiredForPerformer) {
							if(sArea!=null && !sArea.isFree(characterPerformingSexAction)) {
								return false;
							}
						}
					}
					break;
				default:
					break;
			}
		}

		// Check to make sure penetrationType is exposed:
		for(SexAreaInterface sArea : sexAreaAccessRequiredForPerformer) {
			if(!characterPerformingSexAction.isSexAreaExposed(sArea)) {
				return false;
			}
		}

		if(getSexActionType()!=null) {
			switch(getSexActionType()){
				case REQUIRES_NO_PENETRATION_AND_EXPOSED:
					for(SexAreaInterface sArea : sexAreaAccessRequiredForPerformer) {
						if(sArea!=null && !sArea.isFree(characterPerformingSexAction)) {
							return false;
						}
					}
					break;
				default:
					break;
			}
		}

		return true;
	}

	public boolean isOrificeTypeAvailable() {
		if(sexAreaAccessRequiredForTargeted==null || sexAreaAccessRequiredForTargeted.isEmpty()) {
			return true;
		}

		// Don't care if exposed or not:
		if(getSexActionType()!=null) {
			switch(getSexActionType()){
				case REQUIRES_NO_PENETRATION:
					for(SexAreaInterface sArea : sexAreaAccessRequiredForTargeted) {
						if(sArea!=null && (!sArea.isFree(characterTargetedForSexAction) || characterTargetedForSexAction.isCoverableAreaBlockedFromGroping(sArea.getRelatedCoverableArea()))) {
							return false;
						}
					}
					return true;
				case START_ONGOING:
					// Allow characters who have control to switch from one ongoing penetration to another
					if(!isSwitchOngoingActionAvailable()) {
						for(SexAreaInterface sArea : sexAreaAccessRequiredForTargeted) {
							if(sArea!=null && !sArea.isFree(characterTargetedForSexAction)) {
								return false;
							}
						}
					}
					break;
				default:
					break;
			}
		}

		// Check to make sure penetrationType is exposed:
		for(SexAreaInterface sArea : sexAreaAccessRequiredForTargeted) {
			if(!characterTargetedForSexAction.isSexAreaExposed(sArea)) {
				return false;
			}
		}

		if(getSexActionType()!=null) {
			switch(getSexActionType()){
				case REQUIRES_NO_PENETRATION_AND_EXPOSED:
					for(SexAreaInterface sArea : sexAreaAccessRequiredForTargeted) {
						if(sArea!=null && !sArea.isFree(characterTargetedForSexAction)) {
							return false;
						}
					}
					break;
				default:
					break;
			}
		}

		return true;
	}

	public List<Fetish> getFetishesForUnlock() {
		return fetishesRequired;
	}

	public CorruptionLevel getCorruptionNeeded() {
		return corruptionBypass;
	}

	public List<AbstractPerk> getPerksRequired() {
		return perksRequired;
	}

	public Femininity getFemininityRequired() {
		return femininityRequired;
	}

	public Race getRaceRequired() {
		return raceRequired;
	}

	public static Response getDisallowedSpittingResponse() {
		return getDisallowedSpittingResponse("Spit");
	}

	public static Response getDisallowedSpittingResponse(String desc) {
		return new Response(desc, "[style.italicsBad(Rejection of TF potions is disabled!)]
Your opponent is forcing you to drink down the potion!", null);
	}
}


code:
package com.lilithsthrone.game.dialogue.responses;

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

import com.lilithsthrone.game.character.GameCharacter;
import com.lilithsthrone.game.character.npc.NPC;
import com.lilithsthrone.game.combat.Combat;
import com.lilithsthrone.game.dialogue.DialogueNode;
import com.lilithsthrone.main.Main;

/**
 * @since 0.1.69
 * @version 0.2.4
 * @author Innoxia
 */
public class ResponseCombat extends Response {

	private List<NPC> allies;
	private NPC enemyLeader;
	private List<NPC> enemies;

	private Map<GameCharacter, String> openingDescriptions;

	public ResponseCombat(String title, String tooltipText, NPC opponent) {
		super(title, tooltipText, null);
		this.allies = new ArrayList<>();
		for(GameCharacter companion : Main.game.getPlayer().getCompanions()) {
			this.allies.add((NPC) companion);
		}

		this.enemyLeader = opponent;

		this.enemies = new ArrayList<>();
		this.enemies.add(opponent);
		for(GameCharacter companion : opponent.getCompanions()) {
			this.enemies.add((NPC) companion);
		}

		this.openingDescriptions = new HashMap<>();
	}

	public ResponseCombat(String title, String tooltipText, NPC opponent, Map<GameCharacter, String> openingDescriptions) {
		super(title, tooltipText, null);
		this.allies = new ArrayList<>();
		for(GameCharacter companion : Main.game.getPlayer().getCompanions()) {
			this.allies.add((NPC) companion);
		}

		this.enemyLeader = opponent;

		this.enemies = new ArrayList<>();
		this.enemies.add(opponent);
		for(GameCharacter companion : opponent.getCompanions()) {
			this.enemies.add((NPC) companion);
		}

		if(openingDescriptions!=null) {
			this.openingDescriptions = openingDescriptions;
		}
	}

	public ResponseCombat(String title, String tooltipText, NPC enemyLeader, List<GameCharacter> enemies, Map<GameCharacter, String> openingDescriptions) {
		super(title, tooltipText, null);
		this.allies = new ArrayList<>();
		for(GameCharacter companion : Main.game.getPlayer().getCompanions()) {
			this.allies.add((NPC) companion);
		}

		this.enemyLeader = enemyLeader;

		// Irbynx's note:
		// Assuming this function overload is used for very specific combat instances in mind. To add companions to equation, just pass them mixed in with the lists
		this.enemies = new ArrayList<>();
		for(GameCharacter enemy : enemies) {
			this.enemies.add((NPC) enemy);
		}

		if(openingDescriptions!=null) {
			this.openingDescriptions = openingDescriptions;
		}
	}

	public ResponseCombat(String title, String tooltipText, List<GameCharacter> allies, NPC enemyLeader, List<GameCharacter> enemies, Map<GameCharacter, String> openingDescriptions) {
		super(title, tooltipText, null);
		this.allies = new ArrayList<>();
		if(allies!=null) {
			for(GameCharacter companion : allies) {
				this.allies.add((NPC) companion);
			}
		}

		this.enemyLeader = enemyLeader;

		// Irbynx's note:
		// Assuming this function overload is used for very specific combat instances in mind. To add companions to equation, just pass them mixed in with the lists
		this.enemies = new ArrayList<>();
		for(GameCharacter enemy : enemies) {
			this.enemies.add((NPC) enemy);
		}

		if(openingDescriptions!=null) {
			this.openingDescriptions = openingDescriptions;
		}
	}

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

	public DialogueNode initCombat() {
		Combat.COMBAT.initialiseCombat(allies, enemyLeader, enemies, openingDescriptions);
		return Combat.COMBAT.startCombat();
	}

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

repo_name: klorpa/Pedo

license: unknown


code:
package com.lilithsthrone.game.dialogue.responses;

/**
 * A Response class that does not progress to a new DialogueNode.

 *
 * As a result of there being no defined DialogueNode, no time will be passed unless you override this Response's getSecondsPassed() method.
 *
 * @since 0.1.69
 * @version 0.3.1
 * @author Innoxia
 */
public class ResponseEffectsOnly extends Response {

	public ResponseEffectsOnly(String title, String tooltipText) {
		super(title, tooltipText, null);
	}

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


code:
package com.lilithsthrone.game.dialogue.responses;

import com.lilithsthrone.game.character.npc.NPC;
import com.lilithsthrone.game.dialogue.utils.InventoryInteraction;
import com.lilithsthrone.main.Main;

/**
 * @since 0.1.85
 * @version 0.1.85
 * @author Innoxia
 */
public class ResponseManageNPC extends Response {

	private NPC npcToManage;

	public ResponseManageNPC(String title, String tooltipText, NPC npcToManage) {
		super(title, tooltipText, null);

		this.npcToManage=npcToManage;
	}

	public void openNPCManagement() {
		Main.mainController.openInventory(npcToManage, InventoryInteraction.FULL_MANAGEMENT);
	}

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


code:
package com.lilithsthrone.game.dialogue.responses;

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.attributes.CorruptionLevel;
import com.lilithsthrone.game.character.body.valueEnums.Femininity;
import com.lilithsthrone.game.character.effects.AbstractPerk;
import com.lilithsthrone.game.character.fetishes.Fetish;
import com.lilithsthrone.game.character.race.Race;
import com.lilithsthrone.game.dialogue.DialogueNode;
import com.lilithsthrone.game.sex.InitialSexActionInformation;
import com.lilithsthrone.game.sex.SexPace;
import com.lilithsthrone.game.sex.managers.SexManagerDefault;
import com.lilithsthrone.game.sex.managers.SexManagerInterface;
import com.lilithsthrone.game.sex.managers.universal.SMAllFours;
import com.lilithsthrone.game.sex.managers.universal.SMGeneric;
import com.lilithsthrone.game.sex.managers.universal.SMLyingDown;
import com.lilithsthrone.game.sex.managers.universal.SMStanding;
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.SexSlotGeneric;
import com.lilithsthrone.game.sex.positions.slots.SexSlotLyingDown;
import com.lilithsthrone.game.sex.positions.slots.SexSlotStanding;
import com.lilithsthrone.main.Main;
import com.lilithsthrone.utils.Colour;
import com.lilithsthrone.utils.Util;
import com.lilithsthrone.utils.Util.Value;

/**
 * @since 0.1.69
 * @version 0.3.4
 * @author Innoxia
 */
public class ResponseSex extends Response {

	private boolean consensual;
	private boolean subHasEqualControl;
	private SexManagerInterface sexManager;
	private List<GameCharacter> dominantSpectators;
	private List<GameCharacter> submissiveSpectators;
	private DialogueNode postSexDialogue;
	private String sexStartDescription;

	public ResponseSex(String title,
			String tooltipText,
			boolean consensual,
			boolean subHasEqualControl,
			List<GameCharacter> dominants,
			List<GameCharacter> submissives,
			List<GameCharacter> dominantSpectators,
			List<GameCharacter> submissiveSpectators,
			DialogueNode postSexDialogue,
			String sexStartDescription,
			ResponseTag... tags) {
		this(title,
				tooltipText,
				null,
				null,
				null,
				null,
				null,
				null,
				consensual,
				subHasEqualControl,
				dominants,
				submissives,
				dominantSpectators,
				submissiveSpectators,
				postSexDialogue,
				sexStartDescription,
				tags);
	}

	/**
	 * Automatically generates the SexManager (which assigns positions), based on the submissives and dominants passed in.
	 *
	 * @param title The action title.
	 * @param tooltipText The text displayed in the action's tooltip.
	 * @param fetishesForUnlock Fetishes that remove corruption increases from selecting this action.
	 * @param fetishesBlocking Fetishes that block this action from being selected.
	 * @param corruptionBypass The level of corruption required to choose this action without incurring corruption increases.
	 * @param perksRequired Perks that are required to unlock this action.
	 * @param femininityRequired If this action requires the player to be of a certain femininity.
	 * @param raceRequired If this action requires the player to be of a certain race.
	 * @param consensual If this sex scene is consensual or not.
	 * @param subHasEqualControl If the submissives are able to use actions that start penetrations.
	 * @param dominants The dominant characters to use in this sex scene.
	 * @param submissives The submissive characters to use in this sex scene.
	 * @param dominantSpectators The characters spectating this sex scene. (To be displayed in the dominant side of the UI.)
	 * @param submissiveSpectators The characters spectating this sex scene. (To be displayed in the submissive side of the UI.)
	 * @param postSexDialogue The DialogueNode to be returned once the sex scene ends.
	 */
	public ResponseSex(String title,
			String tooltipText,
			List<Fetish> fetishesForUnlock,
			List<Fetish> fetishesBlocking,
			CorruptionLevel corruptionBypass,
			List<AbstractPerk> perksRequired,
			Femininity femininityRequired,
			Race raceRequired,
			boolean consensual,
			boolean subHasEqualControl,
			List<GameCharacter> dominants,
			List<GameCharacter> submissives,
			List<GameCharacter> dominantSpectators,
			List<GameCharacter> submissiveSpectators,
			DialogueNode postSexDialogue,
			String sexStartDescription,
			ResponseTag... tags) {
		this(title,
				tooltipText,
				fetishesForUnlock,
				fetishesBlocking,
				corruptionBypass,
				perksRequired,
				femininityRequired,
				raceRequired,
				consensual,
				subHasEqualControl,
				null,
				dominantSpectators,
				submissiveSpectators,
				postSexDialogue,
				sexStartDescription);

		// If size difference with just two participants, return relevant standing position:
		if(submissives.size()==1 && dominants.size()==1) {
			boolean sexManagerSet = false;
			for(ResponseTag tag : tags) {
				if(tag!=null) {
					switch(tag) {
						case PREFER_ORAL:
							generateOralPosition(submissives, dominants, dominantSpectators, submissiveSpectators, tags);
							sexManagerSet = true;
							break;
						case PREFER_MISSIONARY:
							generateMissionaryPosition(submissives, dominants, dominantSpectators, submissiveSpectators, tags);
							sexManagerSet = true;
							break;
						case PREFER_DOGGY:
							generateDoggyPosition(submissives, dominants, dominantSpectators, submissiveSpectators, tags);
							sexManagerSet = true;
							break;
						default:
							break;
					}
				}
			}

			if(!sexManagerSet) {
				this.sexManager = new SMStanding(
						Util.newHashMapOfValues(new Value<>(dominants.get(0), SexSlotStanding.STANDING_DOMINANT)),
						Util.newHashMapOfValues(new Value<>(submissives.get(0), SexSlotStanding.STANDING_SUBMISSIVE))) {
					@Override
					public SexPace getStartingSexPaceModifier(GameCharacter character) {
						if(character.isPlayer()) {
							for(ResponseTag tag : tags) {
								if(tag!=null) {
									switch(tag) {
										case START_PACE_PLAYER_DOM_GENTLE:
											return SexPace.DOM_GENTLE;
										case START_PACE_PLAYER_DOM_ROUGH:
											return SexPace.DOM_ROUGH;
										case START_PACE_PLAYER_SUB_RESISTING:
											return SexPace.SUB_RESISTING;
										case START_PACE_PLAYER_SUB_EAGER:
											return SexPace.SUB_EAGER;
										case PREFER_ORAL:
										case PREFER_MISSIONARY:
										case PREFER_DOGGY:
										case PREFER_COW_GIRL:
										case DISABLE_POSITIONING:
											break;
									}
								}
							}
						}
						return null;
					}
					@Override
					public boolean isPositionChangingAllowed(GameCharacter character) {
						if(Arrays.asList(tags).contains(ResponseTag.DISABLE_POSITIONING)) {
							return false;
						}
						return super.isPositionChangingAllowed(character);
					}
				};
			}

		// If group sex:
		} else {
			// Sort so penis is used in humping/sex:
			List<GameCharacter> sortedDominants = new ArrayList<>(dominants);
			sortedDominants.sort((e1, e2) -> e1.hasPenis()?e2.hasPenis()?0:-1:1);

			boolean sexManagerSet = false;

			for(ResponseTag tag : tags) {
				if(tag!=null) {
					switch(tag) {
						case PREFER_ORAL:
							generateOralPosition(submissives, sortedDominants, dominantSpectators, submissiveSpectators, tags);
							sexManagerSet = true;
							break;
						case PREFER_MISSIONARY:
							generateMissionaryPosition(submissives, sortedDominants, dominantSpectators, submissiveSpectators, tags);
							sexManagerSet = true;
							break;
						case PREFER_DOGGY:
							generateDoggyPosition(submissives, sortedDominants, dominantSpectators, submissiveSpectators, tags);
							sexManagerSet = true;
							break;
						default:
							break;
					}
				}
			}

			if(!sexManagerSet) {
				int penisCount = 0;
				for(GameCharacter dom : sortedDominants) {
					if(dom.hasPenis()) {
						penisCount++;
					}
				}

				if(sortedDominants.size()==1) {
					if(Math.random()>0.5) {
						generateMissionaryPosition(submissives, sortedDominants, dominantSpectators, submissiveSpectators, tags);
					} else {
						generateDoggyPosition(submissives, sortedDominants, dominantSpectators, submissiveSpectators, tags);
					}

				} else {
					switch(penisCount) {
						case 0:
						case 1:
							generateMissionaryPosition(submissives, sortedDominants, dominantSpectators, submissiveSpectators, tags);
							break;
						case 2:
							if(Math.random()>0.5) {
								generateMissionaryPosition(submissives, sortedDominants, dominantSpectators, submissiveSpectators, tags);
							} else {
								generateDoggyPosition(submissives, sortedDominants, dominantSpectators, submissiveSpectators, tags);
							}
							break;
						default:
							generateDoggyPosition(submissives, sortedDominants, dominantSpectators, submissiveSpectators, tags);
							break;
					}
				}
			}
		}
	}

	public ResponseSex(String title,
			String tooltipText,
			boolean consensual,
			boolean subHasEqualControl,
			SMGeneric sexManager,
			DialogueNode postSexDialogue,
			String sexStartDescription) {
		this(title, tooltipText, null, null, null, null, null, null, consensual, subHasEqualControl, sexManager, postSexDialogue, sexStartDescription);
	}

	public ResponseSex(String title,
			String tooltipText,
			List<Fetish> fetishesForUnlock,
			List<Fetish> fetishesBlocking,
			CorruptionLevel corruptionBypass,
			List<AbstractPerk> perksRequired,
			Femininity femininityRequired,
			Race raceRequired,
			boolean consensual,
			boolean subHasEqualControl,
			SMGeneric sexManager,
			DialogueNode postSexDialogue,
			String sexStartDescription) {
		this(title, tooltipText, fetishesForUnlock, fetishesBlocking, corruptionBypass, perksRequired, femininityRequired, raceRequired, consensual, subHasEqualControl, sexManager, null, null, postSexDialogue, sexStartDescription);
		this.dominantSpectators = sexManager.getDominantSpectators();
		this.submissiveSpectators = sexManager.getSubmissiveSpectators();
	}

	public ResponseSex(String title,
			String tooltipText,
			boolean consensual,
			boolean subHasEqualControl,
			SexManagerInterface sexManager,
			List<GameCharacter> dominantSpectators,
			List<GameCharacter> submissiveSpectators,
			DialogueNode postSexDialogue) {
		this(title, tooltipText, consensual, subHasEqualControl, sexManager, dominantSpectators, submissiveSpectators, postSexDialogue, "");
	}

	public ResponseSex(String title,
			String tooltipText,
			boolean consensual,
			boolean subHasEqualControl,
			SexManagerInterface sexManager,
			List<GameCharacter> dominantSpectators,
			List<GameCharacter> submissiveSpectators,
			DialogueNode postSexDialogue,
			String sexStartDescription) {
		this(title, tooltipText,
				null, null, null, null,
				null, null, consensual,
				subHasEqualControl, sexManager, dominantSpectators, submissiveSpectators, postSexDialogue, sexStartDescription);
	}

	public ResponseSex(String title,
			String tooltipText,
			List<Fetish> fetishesForUnlock,
			List<Fetish> fetishesBlocking,
			CorruptionLevel corruptionBypass,
			List<AbstractPerk> perksRequired,
			Femininity femininityRequired,
			Race raceRequired,
			boolean consensual,
			boolean subHasEqualControl,
			SexManagerInterface sexManager,
			List<GameCharacter> dominantSpectators,
			List<GameCharacter> submissiveSpectators,
			DialogueNode postSexDialogue,
			String sexStartDescription) {
		super(title, tooltipText, null,
				fetishesForUnlock, corruptionBypass,
				perksRequired, femininityRequired, raceRequired);

		this.consensual = consensual;
		this.subHasEqualControl = subHasEqualControl;

		this.sexManager = sexManager;

		if(dominantSpectators==null) {
			this.dominantSpectators = new ArrayList<>();
		} else {
			this.dominantSpectators = dominantSpectators;
			while(this.dominantSpectators.contains(null)) {
				this.dominantSpectators.remove(null);
			}
		}

		if(submissiveSpectators==null) {
			this.submissiveSpectators = new ArrayList<>();
		} else {
			this.submissiveSpectators = submissiveSpectators;
			while(this.submissiveSpectators.contains(null)) {
				this.submissiveSpectators.remove(null);
			}
		}

		this.postSexDialogue = postSexDialogue;
		this.sexStartDescription = sexStartDescription;
	}

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

	@Override
	public Colour getHighlightColour() {
		if(isPlayerDom()) {
			return Colour.GENERIC_SEX_AS_DOM;
		} else {
			return Colour.GENERIC_SEX;
		}
	}

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

	public boolean isPlayerDom() {
		return sexManager!=null && sexManager.isPlayerDom();
	}

	public List<InitialSexActionInformation> getInitialSexActions() {
		return new ArrayList<>();
	}

	public DialogueNode initSex() {
		return Main.sexEngine.initialiseSex(consensual, subHasEqualControl, sexManager, dominantSpectators, submissiveSpectators, postSexDialogue, sexStartDescription, getInitialSexActions());
	}

	/**
	 * This method is applied after the initSex() method has been called. It will not affect the content of the scene, so should just be used for modifying sex or foreplay preferences, or other background data.
	 */
	public void postSexInitEffects() {
	}

	private void generateOralPosition(List<GameCharacter> submissives, List<GameCharacter> sortedDominants, List<GameCharacter> dominantSpectators, List<GameCharacter> submissiveSpectators, ResponseTag... tags) {

		Map<GameCharacter, SexSlot> subMap = new HashMap<>();
		Map<GameCharacter, SexSlot> domMap = new HashMap<>();

		List<SexSlot> subSlots = new ArrayList<>();
		subSlots.add(SexSlotStanding.PERFORMING_ORAL);
		subSlots.add(SexSlotStanding.PERFORMING_ORAL_TWO);
		subSlots.add(SexSlotStanding.PERFORMING_ORAL_THREE);
		if(sortedDominants.size()==1) {
			subSlots.add(SexSlotStanding.PERFORMING_ORAL_BEHIND);
		} else {
			subSlots.add(SexSlotStanding.PERFORMING_ORAL_FOUR);
		}
		subSlots.add(SexSlotGeneric.MISC_WATCHING);
		subSlots.add(SexSlotGeneric.MISC_WATCHING);

		List<SexSlot> domSlots = new ArrayList<>();
		domSlots.add(SexSlotStanding.STANDING_DOMINANT);
		domSlots.add(SexSlotStanding.STANDING_DOMINANT_TWO);
		domSlots.add(SexSlotStanding.STANDING_DOMINANT_THREE);
		domSlots.add(SexSlotStanding.STANDING_DOMINANT_FOUR);
		domSlots.add(SexSlotGeneric.MISC_WATCHING);
		domSlots.add(SexSlotGeneric.MISC_WATCHING);

		for(int i=0; i<submissives.size(); i++) {
			subMap.put(submissives.get(i), subSlots.get(i));
		}

		for(int i=0; i<sortedDominants.size(); i++) {
			domMap.put(sortedDominants.get(i), domSlots.get(i));
		}

		this.sexManager = new SexManagerDefault(SexPosition.STANDING, domMap, subMap) {
			@Override
			public SexPace getStartingSexPaceModifier(GameCharacter character) {
				if(character.isPlayer()) {
					for(ResponseTag tag : tags) {
						if(tag!=null) {
							switch(tag) {
								case START_PACE_PLAYER_DOM_GENTLE:
									return SexPace.DOM_GENTLE;
								case START_PACE_PLAYER_DOM_ROUGH:
									return SexPace.DOM_ROUGH;
								case START_PACE_PLAYER_SUB_RESISTING:
									return SexPace.SUB_RESISTING;
								case START_PACE_PLAYER_SUB_EAGER:
									return SexPace.SUB_EAGER;
								case PREFER_ORAL:
								case PREFER_MISSIONARY:
								case PREFER_DOGGY:
								case PREFER_COW_GIRL:
								case DISABLE_POSITIONING:
									break;
							}
						}
					}
				}
				return null;
			}
			@Override
			public boolean isPositionChangingAllowed(GameCharacter character) {
				if(Arrays.asList(tags).contains(ResponseTag.DISABLE_POSITIONING)) {
					return false;
				}
				return super.isPositionChangingAllowed(character);
			}
		};

	}

	private void generateMissionaryPosition(List<GameCharacter> submissives, List<GameCharacter> sortedDominants, List<GameCharacter> dominantSpectators, List<GameCharacter> submissiveSpectators, ResponseTag... tags) {

		Map<GameCharacter, SexSlot> subMap = new HashMap<>();
		Map<GameCharacter, SexSlot> domMap = new HashMap<>();

		List<SexSlot> domSlots = new ArrayList<>();
		List<SexSlot> subSlots = new ArrayList<>();

		subSlots.add(SexSlotLyingDown.LYING_DOWN);
		subSlots.add(SexSlotLyingDown.LYING_DOWN_TWO);
		subSlots.add(SexSlotLyingDown.LYING_DOWN_THREE);
		subSlots.add(SexSlotLyingDown.LYING_DOWN_FOUR);
		subSlots.add(SexSlotGeneric.MISC_WATCHING);
		subSlots.add(SexSlotGeneric.MISC_WATCHING);

		if(submissives.size()==1) {
			domSlots.add(SexSlotLyingDown.MISSIONARY);
			domSlots.add(SexSlotLyingDown.FACE_SITTING);
			domSlots.add(SexSlotLyingDown.BESIDE);
			domSlots.add(SexSlotLyingDown.BESIDE_TWO);

		} else if(submissives.size()==2) {
			domSlots.add(SexSlotLyingDown.MISSIONARY);
			domSlots.add(SexSlotLyingDown.FACE_SITTING);
			domSlots.add(SexSlotLyingDown.MISSIONARY_TWO);
			domSlots.add(SexSlotLyingDown.FACE_SITTING_TWO);

		} else if(submissives.size()==3) {
			domSlots.add(SexSlotLyingDown.MISSIONARY);
			domSlots.add(SexSlotLyingDown.FACE_SITTING);
			domSlots.add(SexSlotLyingDown.MISSIONARY_TWO);
			domSlots.add(SexSlotLyingDown.MISSIONARY_THREE);

		} else {
			domSlots.add(SexSlotLyingDown.MISSIONARY);
			domSlots.add(SexSlotLyingDown.MISSIONARY_TWO);
			domSlots.add(SexSlotLyingDown.MISSIONARY_THREE);
			domSlots.add(SexSlotLyingDown.MISSIONARY_FOUR);
		}
		domSlots.add(SexSlotGeneric.MISC_WATCHING);
		domSlots.add(SexSlotGeneric.MISC_WATCHING);

		for(int i=0; i<submissives.size(); i++) {
			subMap.put(submissives.get(i), subSlots.get(i));
		}

		for(int i=0; i<sortedDominants.size(); i++) {
			domMap.put(sortedDominants.get(i), domSlots.get(i));
		}

		this.sexManager = new SMLyingDown(domMap, subMap) {
			@Override
			public SexPace getStartingSexPaceModifier(GameCharacter character) {
				if(character.isPlayer()) {
					for(ResponseTag tag : tags) {
						if(tag!=null) {
							switch(tag) {
								case START_PACE_PLAYER_DOM_GENTLE:
									return SexPace.DOM_GENTLE;
								case START_PACE_PLAYER_DOM_ROUGH:
									return SexPace.DOM_ROUGH;
								case START_PACE_PLAYER_SUB_RESISTING:
									return SexPace.SUB_RESISTING;
								case START_PACE_PLAYER_SUB_EAGER:
									return SexPace.SUB_EAGER;
								case PREFER_ORAL:
								case PREFER_MISSIONARY:
								case PREFER_DOGGY:
								case PREFER_COW_GIRL:
								case DISABLE_POSITIONING:
									break;
							}
						}
					}
				}
				return null;
			}
			@Override
			public boolean isPositionChangingAllowed(GameCharacter character) {
				if(Arrays.asList(tags).contains(ResponseTag.DISABLE_POSITIONING)) {
					return false;
				}
				return super.isPositionChangingAllowed(character);
			}
		};
	}

	private void generateDoggyPosition(List<GameCharacter> submissives, List<GameCharacter> sortedDominants, List<GameCharacter> dominantSpectators, List<GameCharacter> submissiveSpectators, ResponseTag... tags) {

		Map<GameCharacter, SexSlot> subMap = new HashMap<>();
		Map<GameCharacter, SexSlot> domMap = new HashMap<>();

		List<SexSlot> domSlots = new ArrayList<>();
		List<SexSlot> subSlots = new ArrayList<>();

		subSlots.add(SexSlotAllFours.ALL_FOURS);
		subSlots.add(SexSlotAllFours.ALL_FOURS_TWO);
		subSlots.add(SexSlotAllFours.ALL_FOURS_THREE);
		subSlots.add(SexSlotAllFours.ALL_FOURS_FOUR);
		subSlots.add(SexSlotGeneric.MISC_WATCHING);
		subSlots.add(SexSlotGeneric.MISC_WATCHING);

		if(submissives.size()==1) {
			domSlots.add(SexSlotAllFours.BEHIND);
			domSlots.add(SexSlotAllFours.IN_FRONT);
			domSlots.add(SexSlotAllFours.IN_FRONT_TWO);
			domSlots.add(SexSlotAllFours.HUMPING);

		} else if(submissives.size()==2) {
			domSlots.add(SexSlotAllFours.BEHIND);
			domSlots.add(SexSlotAllFours.IN_FRONT);
			domSlots.add(SexSlotAllFours.BEHIND_TWO);
			domSlots.add(SexSlotAllFours.IN_FRONT_TWO);

		} else if(submissives.size()==3) {
			domSlots.add(SexSlotAllFours.BEHIND);
			domSlots.add(SexSlotAllFours.IN_FRONT);
			domSlots.add(SexSlotAllFours.BEHIND_TWO);
			domSlots.add(SexSlotAllFours.BEHIND_THREE);

		} else {
			domSlots.add(SexSlotAllFours.BEHIND);
			domSlots.add(SexSlotAllFours.BEHIND_TWO);
			domSlots.add(SexSlotAllFours.BEHIND_THREE);
			domSlots.add(SexSlotAllFours.BEHIND_FOUR);
		}
		domSlots.add(SexSlotGeneric.MISC_WATCHING);
		domSlots.add(SexSlotGeneric.MISC_WATCHING);

		for(int i=0; i<submissives.size(); i++) {
			subMap.put(submissives.get(i), subSlots.get(i));
		}

		for(int i=0; i<sortedDominants.size(); i++) {
			domMap.put(sortedDominants.get(i), domSlots.get(i));
		}

		this.sexManager = new SMAllFours(domMap, subMap) {
			@Override
			public SexPace getStartingSexPaceModifier(GameCharacter character) {
				if(character.isPlayer()) {
					for(ResponseTag tag : tags) {
						if(tag!=null) {
							switch(tag) {
								case START_PACE_PLAYER_DOM_GENTLE:
									return SexPace.DOM_GENTLE;
								case START_PACE_PLAYER_DOM_ROUGH:
									return SexPace.DOM_ROUGH;
								case START_PACE_PLAYER_SUB_RESISTING:
									return SexPace.SUB_RESISTING;
								case START_PACE_PLAYER_SUB_EAGER:
									return SexPace.SUB_EAGER;
								case PREFER_ORAL:
								case PREFER_MISSIONARY:
								case PREFER_DOGGY:
								case PREFER_COW_GIRL:
								case DISABLE_POSITIONING:
									break;
							}
						}
					}
				}
				return null;
			}
			@Override
			public boolean isPositionChangingAllowed(GameCharacter character) {
				if(Arrays.asList(tags).contains(ResponseTag.DISABLE_POSITIONING)) {
					return false;
				}
				return super.isPositionChangingAllowed(character);
			}
		};
	}

}


code:
package com.lilithsthrone.game.dialogue.responses;

/**
 * @since 0.2.11
 * @version 0.3.4
 * @author Innoxia
 */
public enum ResponseTag {

	DISABLE_POSITIONING,

	PREFER_ORAL,
	PREFER_MISSIONARY,
	PREFER_DOGGY,
	PREFER_COW_GIRL,

	START_PACE_PLAYER_SUB_RESISTING,
	START_PACE_PLAYER_SUB_EAGER,
	START_PACE_PLAYER_DOM_GENTLE,
	START_PACE_PLAYER_DOM_ROUGH;
}


code:
package com.lilithsthrone.game.dialogue.responses;

import com.lilithsthrone.game.character.npc.NPC;
import com.lilithsthrone.game.dialogue.utils.InventoryInteraction;
import com.lilithsthrone.main.Main;

/**
 * @since 0.1.7?
 * @version 0.1.85
 * @author Innoxia
 */
public class ResponseTrade extends Response {

	private NPC tradePartner;

	public ResponseTrade(String title, String tooltipText, NPC tradePartner) {
		super(title, tooltipText, null);

		this.tradePartner=tradePartner;
	}

	public void openTrade() {
		Main.mainController.openInventory(tradePartner, InventoryInteraction.TRADING);
	}

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

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