code:
package com.lilithsthrone.controller.eventListeners.tooltips;

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

import org.w3c.dom.events.Event;
import org.w3c.dom.events.EventListener;

import com.lilithsthrone.controller.TooltipUpdateThread;
import com.lilithsthrone.game.character.GameCharacter;
import com.lilithsthrone.game.character.attributes.Attribute;
import com.lilithsthrone.game.character.body.CoverableArea;
import com.lilithsthrone.game.character.body.types.HornType;
import com.lilithsthrone.game.character.body.types.PenisType;
import com.lilithsthrone.game.character.body.types.TailType;
import com.lilithsthrone.game.character.body.types.VaginaType;
import com.lilithsthrone.game.character.body.types.WingType;
import com.lilithsthrone.game.character.body.valueEnums.Femininity;
import com.lilithsthrone.game.character.markings.Scar;
import com.lilithsthrone.game.character.markings.Tattoo;
import com.lilithsthrone.game.character.markings.TattooCounterType;
import com.lilithsthrone.game.character.markings.TattooWritingStyle;
import com.lilithsthrone.game.combat.Attack;
import com.lilithsthrone.game.combat.DamageType;
import com.lilithsthrone.game.combat.Spell;
import com.lilithsthrone.game.dialogue.utils.EnchantmentDialogue;
import com.lilithsthrone.game.dialogue.utils.InventoryDialogue;
import com.lilithsthrone.game.dialogue.utils.InventoryInteraction;
import com.lilithsthrone.game.dialogue.utils.UtilText;
import com.lilithsthrone.game.inventory.AbstractCoreItem;
import com.lilithsthrone.game.inventory.InventorySlot;
import com.lilithsthrone.game.inventory.ShopTransaction;
import com.lilithsthrone.game.inventory.clothing.AbstractClothing;
import com.lilithsthrone.game.inventory.clothing.AbstractClothingType;
import com.lilithsthrone.game.inventory.clothing.BodyPartClothingBlock;
import com.lilithsthrone.game.inventory.enchanting.EnchantingUtils;
import com.lilithsthrone.game.inventory.enchanting.ItemEffect;
import com.lilithsthrone.game.inventory.enchanting.TFEssence;
import com.lilithsthrone.game.inventory.enchanting.TFModifier;
import com.lilithsthrone.game.inventory.enchanting.TFPotency;
import com.lilithsthrone.game.inventory.item.AbstractItem;
import com.lilithsthrone.game.inventory.item.AbstractItemType;
import com.lilithsthrone.game.inventory.weapon.AbstractWeapon;
import com.lilithsthrone.game.inventory.weapon.AbstractWeaponType;
import com.lilithsthrone.main.Main;
import com.lilithsthrone.rendering.Pattern;
import com.lilithsthrone.rendering.RenderingEngine;
import com.lilithsthrone.utils.Colour;
import com.lilithsthrone.utils.Util;

/**
 * Shows the tooltip at the given element's position.
 *
 * @since 0.1.0
 * @version 0.3.4
 * @author Innoxia
 */
public class TooltipInventoryEventListener implements EventListener {
	private GameCharacter owner, equippedToCharacter;
	private AbstractCoreItem coreItem;
	private AbstractItem item;
	private Tattoo tattoo;
	private AbstractItemType genericItem;
	private AbstractWeapon weapon;
	private AbstractWeaponType genericWeapon;
	private DamageType dt;
	private AbstractClothing clothing;
	private Colour colour;
	private Colour secondaryColour;
	private Colour tertiaryColour;
	private Pattern pattern;
	private AbstractClothingType genericClothing;
	private AbstractClothing dyeClothing;
	private AbstractWeapon dyeWeapon;
	private DamageType damageType;
	private InventorySlot invSlot;
	private TFModifier enchantmentModifier;
	private TFPotency potency;
	private TFEssence essence;
	private static StringBuilder tooltipSB = new StringBuilder();

	private static final int LINE_HEIGHT = 17;
	private static final int TOOLTIP_WIDTH = 400;

	@Override
	public void handleEvent(Event event) {

		if (item != null || (coreItem instanceof AbstractItem)) {
			if(coreItem != null) {
				item = (AbstractItem) coreItem;
			}
			itemTooltip(item);

		} else if (weapon != null || (coreItem instanceof AbstractWeapon)) {
			if(coreItem != null) {
				weapon = (AbstractWeapon) coreItem;
			}
			weaponTooltip(weapon);

		} else if (clothing != null || (coreItem instanceof AbstractClothing)) {
			if(coreItem != null) {
				clothing = (AbstractClothing) coreItem;
			}
			clothingTooltip(clothing);

		} else if(tattoo!=null) {
			tattooTooltip(tattoo);

		} else if (dyeClothing != null) {

			Main.mainController.setTooltipSize(TOOLTIP_WIDTH, 446);

			Colour subtitleColour = dyeClothing.isEnchantmentKnown()?dyeClothing.getRarity().getColour():Colour.RARITY_UNKNOWN;

			InventorySlot slotEquippedTo = dyeClothing.getSlotEquippedTo();
			if(slotEquippedTo==null) {
				slotEquippedTo = dyeClothing.getClothingType().getEquipSlots().get(0);
			}

			tooltipSB.setLength(0);
			if(colour!=null) {
				tooltipSB.append("
" + Util.capitaliseSentence(dyeClothing.getName()) + "
"
						+ "
" + Util.capitaliseSentence(colour.getName()) + "
"
						+ "
"
						+ dyeClothing.getClothingType().getSVGImage(
								slotEquippedTo,
								colour, InventoryDialogue.dyePreviewSecondary, InventoryDialogue.dyePreviewTertiary,
								InventoryDialogue.dyePreviewPattern,
								InventoryDialogue.dyePreviewPatternPrimary, InventoryDialogue.dyePreviewPatternSecondary, InventoryDialogue.dyePreviewPatternTertiary)
						+ "
");

			} else if(secondaryColour!=null) {
				tooltipSB.append("
" + Util.capitaliseSentence(dyeClothing.getName()) + "
"
						+ "
" + Util.capitaliseSentence(secondaryColour.getName()) + "
"
						+ "
"
						+ dyeClothing.getClothingType().getSVGImage(
								slotEquippedTo,
								InventoryDialogue.dyePreviewPrimary, secondaryColour, InventoryDialogue.dyePreviewTertiary,
								InventoryDialogue.dyePreviewPattern,
								InventoryDialogue.dyePreviewPatternPrimary, InventoryDialogue.dyePreviewPatternSecondary, InventoryDialogue.dyePreviewPatternTertiary)
						+ "
");

			} else if(tertiaryColour!=null) {
				tooltipSB.append("
" + Util.capitaliseSentence(dyeClothing.getName()) + "
"
						+ "
" + Util.capitaliseSentence(tertiaryColour.getName()) + "
"
						+ "
"
						+ dyeClothing.getClothingType().getSVGImage(
								slotEquippedTo,
								InventoryDialogue.dyePreviewPrimary, InventoryDialogue.dyePreviewSecondary, tertiaryColour,
								InventoryDialogue.dyePreviewPattern,
								InventoryDialogue.dyePreviewPatternPrimary, InventoryDialogue.dyePreviewPatternSecondary, InventoryDialogue.dyePreviewPatternTertiary)
						+ "
");

			} else if(pattern!=null) {
				tooltipSB.append("
" + Util.capitaliseSentence(dyeClothing.getName()) + "
"

						+ "
" + Util.capitaliseSentence(pattern.getNiceName()) + "
"

						+ "
"
						+ dyeClothing.getClothingType().getSVGImage(
								slotEquippedTo,
								InventoryDialogue.dyePreviewPrimary, InventoryDialogue.dyePreviewSecondary, InventoryDialogue.dyePreviewTertiary,
								pattern.getName(),
								InventoryDialogue.dyePreviewPatternPrimary, InventoryDialogue.dyePreviewPatternSecondary, InventoryDialogue.dyePreviewPatternTertiary)
						+ "
");

			}

			Main.mainController.setTooltipContent(UtilText.parse(tooltipSB.toString()));

		} else if (dyeWeapon != null) {
			Main.mainController.setTooltipSize(TOOLTIP_WIDTH-40, 446);

			tooltipSB.setLength(0);
			tooltipSB.append("
" + Util.capitaliseSentence(dyeWeapon.getName()) + "
");

			if(colour!=null) {
				tooltipSB.append("
" + Util.capitaliseSentence(colour.getName()) + "
"
						+ "
" + dyeWeapon.getWeaponType().getSVGImage(dyeWeapon.getDamageType(), colour, InventoryDialogue.dyePreviewSecondary) + "
");

			} else if(secondaryColour!=null) {
				tooltipSB.append("
" + Util.capitaliseSentence(secondaryColour.getName()) + "
"
						+ "
" + dyeWeapon.getWeaponType().getSVGImage(dyeWeapon.getDamageType(), InventoryDialogue.dyePreviewPrimary, secondaryColour) + "
");

			} else if(damageType!=null) {
				tooltipSB.append("
" + Util.capitaliseSentence(damageType.getName()) + "
"
						+ "
" + dyeWeapon.getWeaponType().getSVGImage(damageType, InventoryDialogue.dyePreviewPrimary, InventoryDialogue.dyePreviewSecondary) + "
");
			}

			Main.mainController.setTooltipContent(UtilText.parse(tooltipSB.toString()));

		} else if (genericItem != null) {
			Main.mainController.setTooltipSize(TOOLTIP_WIDTH, 416);

			tooltipSB.setLength(0);
			tooltipSB.append("
" + Util.capitaliseSentence(genericItem.getName(true)) + "
"

					+ "
" + genericItem.getSVGString() + "
");

			Main.mainController.setTooltipContent(UtilText.parse(tooltipSB.toString()));

		} else if (genericClothing != null) {
			String author = genericClothing.getAuthorDescription();
			Main.mainController.setTooltipSize(TOOLTIP_WIDTH, 480+(author.isEmpty()?0:64));

			tooltipSB.setLength(0);
			tooltipSB.append("
" + Util.capitaliseSentence(genericClothing.getName()) + "
"

					+ "
" + Util.capitaliseSentence(colour.getName()) + "
"

					+ "
"
						+ genericClothing.getSVGImage(
								genericClothing.getEquipSlots().get(0),
								colour, genericClothing.getAvailableSecondaryColours().isEmpty()?null:genericClothing.getAvailableSecondaryColours().get(0),
								genericClothing.getAvailableTertiaryColours().isEmpty()?null:genericClothing.getAvailableTertiaryColours().get(0),
								null, null, null, null)
					+ "
"
					+ (author.isEmpty()?"":"
" + author + "
"));

			Main.mainController.setTooltipContent(UtilText.parse(tooltipSB.toString()));

		} else if (genericWeapon != null) {

			String author = genericWeapon.getAuthorDescription();
			Main.mainController.setTooltipSize(TOOLTIP_WIDTH, 480+(author.isEmpty()?0:64));

			tooltipSB.setLength(0);
			tooltipSB.append("
" + Util.capitaliseSentence(genericWeapon.getName()) + "
"

					+ "
" + Util.capitaliseSentence(dt.getName()) + "
"

					+ "
"
						+ genericWeapon.getSVGImage(dt, null, null)
					+ "
"
					+ (author.isEmpty()?"":"
" + author + "
"));

			Main.mainController.setTooltipContent(UtilText.parse(tooltipSB.toString()));

		} else if (invSlot != null) {
			if (invSlot == InventorySlot.WEAPON_MAIN_1) {
				if (equippedToCharacter != null) {
					if (equippedToCharacter.getMainWeapon(0) == null) {
						Main.mainController.setTooltipSize(TOOLTIP_WIDTH, 60);
						Main.mainController.setTooltipContent("
Primary Weapon
");

					} else {
						weaponTooltip(equippedToCharacter.getMainWeapon(0));
					}
				} else {
					Main.mainController.setTooltipSize(TOOLTIP_WIDTH, 60);
					Main.mainController.setTooltipContent("
Primary Weapon
");
				}

			} else if (invSlot == InventorySlot.WEAPON_MAIN_2) {
				if (equippedToCharacter != null) {
					if (equippedToCharacter.getMainWeapon(1) == null) {
						if(equippedToCharacter.getArmRows()<2) {
							setBlockedTooltipContent(getTooltipText(equippedToCharacter,
									"You do not have a second pair of arms with which to hold another primary weapon!",
									"[npc.Name] [npc.does] not have a second pair of arms with which to hold another primary weapon!"));
						} else {
							Main.mainController.setTooltipSize(TOOLTIP_WIDTH, 60);
							Main.mainController.setTooltipContent("
Primary Weapon (2nd)
");
						}

					} else {
						weaponTooltip(equippedToCharacter.getMainWeapon(1));
					}
				} else {
					Main.mainController.setTooltipSize(TOOLTIP_WIDTH, 60);
					Main.mainController.setTooltipContent("
Primary Weapon (2nd)
");
				}

			} else if (invSlot == InventorySlot.WEAPON_MAIN_3) {
				if (equippedToCharacter != null) {
					if (equippedToCharacter.getMainWeapon(2) == null) {
						if(equippedToCharacter.getArmRows()<3) {
							setBlockedTooltipContent(getTooltipText(equippedToCharacter,
									"You do not have a third pair of arms with which to hold another primary weapon!",
									"[npc.Name] [npc.does] not have a third pair of arms with which to hold another primary weapon!"));
						} else {
							Main.mainController.setTooltipSize(TOOLTIP_WIDTH, 60);
							Main.mainController.setTooltipContent("
Primary Weapon (3rd)
");
						}

					} else {
						weaponTooltip(equippedToCharacter.getMainWeapon(2));
					}
				} else {
					Main.mainController.setTooltipSize(TOOLTIP_WIDTH, 60);
					Main.mainController.setTooltipContent("
Primary Weapon (3rd)
");
				}

			} else if (invSlot == InventorySlot.WEAPON_OFFHAND_1) {
				if (equippedToCharacter != null) {
					if (equippedToCharacter.getOffhandWeapon(0) == null) {
						AbstractWeapon primary = equippedToCharacter.getMainWeapon(0);
						if(primary!=null && primary.getWeaponType().isTwoHanded()) {
							setBlockedTooltipContent(getTooltipText(equippedToCharacter,
									primary.getWeaponType().isPlural()
										?"As your "+primary.getName()+" require two hands to wield correctly, you are unable to equip a weapon in your off-hand."
										:"As your "+primary.getName()+" requires two hands to wield correctly, you are unable to equip a weapon in your off-hand",
									primary.getWeaponType().isPlural()
										?"As [npc.namePos] "+primary.getName()+" require two hands to wield correctly, [npc.sheIsFull] unable to equip a weapon in [npc.her] off-hand."
										:"As [npc.namePos] "+primary.getName()+" requires two hands to wield correctly, [npc.sheIsFull] unable to equip a weapon in [npc.her] off-hand"));

						} else {
							Main.mainController.setTooltipSize(TOOLTIP_WIDTH, 60);
							Main.mainController.setTooltipContent("
Secondary Weapon
");
						}

					} else {
						weaponTooltip(equippedToCharacter.getOffhandWeapon(0));
					}
				} else {
					Main.mainController.setTooltipSize(TOOLTIP_WIDTH, 60);
					Main.mainController.setTooltipContent("
Secondary Weapon
");
				}

			} else if (invSlot == InventorySlot.WEAPON_OFFHAND_2) {
				if (equippedToCharacter != null) {
					if (equippedToCharacter.getOffhandWeapon(1) == null) {
						AbstractWeapon primary = equippedToCharacter.getMainWeapon(1);
						if(primary!=null && primary.getWeaponType().isTwoHanded()) {
							setBlockedTooltipContent(getTooltipText(equippedToCharacter,
									primary.getWeaponType().isPlural()
										?"As your "+primary.getName()+" require two hands to wield correctly, you are unable to equip a weapon in your off-hand."
										:"As your "+primary.getName()+" requires two hands to wield correctly, you are unable to equip a weapon in your off-hand",
									primary.getWeaponType().isPlural()
										?"As [npc.namePos] "+primary.getName()+" require two hands to wield correctly, [npc.sheIsFull] unable to equip a weapon in [npc.her] off-hand."
										:"As [npc.namePos] "+primary.getName()+" requires two hands to wield correctly, [npc.sheIsFull] unable to equip a weapon in [npc.her] off-hand"));

						} else if(equippedToCharacter.getArmRows()<2) {
							setBlockedTooltipContent(getTooltipText(equippedToCharacter,
									"You do not have a second pair of arms with which to hold another secondary weapon!",
									"[npc.Name] [npc.does] not have a second pair of arms with which to hold another secondary weapon!"));
						} else {
							Main.mainController.setTooltipSize(TOOLTIP_WIDTH, 60);
							Main.mainController.setTooltipContent("
Secondary Weapon (2nd)
");
						}
					} else {
						weaponTooltip(equippedToCharacter.getOffhandWeapon(1));
					}
				} else {
					Main.mainController.setTooltipSize(TOOLTIP_WIDTH, 60);
					Main.mainController.setTooltipContent("
Secondary Weapon (2nd)
");
				}

			} else if (invSlot == InventorySlot.WEAPON_OFFHAND_3) {
				if (equippedToCharacter != null) {
					if (equippedToCharacter.getOffhandWeapon(2) == null) {
						AbstractWeapon primary = equippedToCharacter.getMainWeapon(2);
						if(primary!=null && primary.getWeaponType().isTwoHanded()) {
							setBlockedTooltipContent(getTooltipText(equippedToCharacter,
									primary.getWeaponType().isPlural()
										?"As your "+primary.getName()+" require two hands to wield correctly, you are unable to equip a weapon in your off-hand."
										:"As your "+primary.getName()+" requires two hands to wield correctly, you are unable to equip a weapon in your off-hand",
									primary.getWeaponType().isPlural()
										?"As [npc.namePos] "+primary.getName()+" require two hands to wield correctly, [npc.sheIsFull] unable to equip a weapon in [npc.her] off-hand."
										:"As [npc.namePos] "+primary.getName()+" requires two hands to wield correctly, [npc.sheIsFull] unable to equip a weapon in [npc.her] off-hand"));

						} else if(equippedToCharacter.getArmRows()<3) {
							setBlockedTooltipContent(getTooltipText(equippedToCharacter,
									"You do not have a third pair of arms with which to hold another secondary weapon!",
									"[npc.Name] [npc.does] not have a third pair of arms with which to hold another secondary weapon!"));
						} else {
							Main.mainController.setTooltipSize(TOOLTIP_WIDTH, 60);
							Main.mainController.setTooltipContent("
Secondary Weapon (3rd)
");
						}
					} else {
						weaponTooltip(equippedToCharacter.getOffhandWeapon(2));
					}
				} else {
					Main.mainController.setTooltipSize(TOOLTIP_WIDTH, 60);
					Main.mainController.setTooltipContent("
Secondary Weapon (3rd)
");
				}

			} else {
				if (equippedToCharacter != null) {
					boolean renderingTattoos = false;

					if((equippedToCharacter.isPlayer() && RenderingEngine.ENGINE.isRenderingTattoosLeft()) || (!equippedToCharacter.isPlayer() && RenderingEngine.ENGINE.isRenderingTattoosRight())
							&& !invSlot.isJewellery()) {
						renderingTattoos = true;
					}

					if ((!renderingTattoos && equippedToCharacter.getClothingInSlot(invSlot)==null)
							|| (renderingTattoos && equippedToCharacter.getTattooInSlot(invSlot)==null)) {

						List<String> clothingBlockingThisSlot = new ArrayList<>();
						for (AbstractClothing c : equippedToCharacter.getClothingCurrentlyEquipped()) {
							if (c.getClothingType().getIncompatibleSlots(equippedToCharacter, c.getSlotEquippedTo()).contains(invSlot)) {
								clothingBlockingThisSlot.add(c.getName());
							}
						}

						BodyPartClothingBlock block = invSlot.getBodyPartClothingBlock(equippedToCharacter);

						if (!renderingTattoos && !clothingBlockingThisSlot.isEmpty()) {
							setBlockedTooltipContent(UtilText.parse(equippedToCharacter, "This slot is currently blocked by [npc.namePos] ")
									+ Util.stringsToStringList(clothingBlockingThisSlot, false) + ".");

						} else if (!renderingTattoos && block != null) {
							setBlockedTooltipContent("Restricted!", UtilText.parse(equippedToCharacter, block.getDescription()));

						} else {
							boolean piercingBlocked=false;
							boolean bypassesPiercing = !equippedToCharacter.getBody().getBodyMaterial().isRequiresPiercing();

							switch(invSlot){
								case PIERCING_VAGINA:
									if(equippedToCharacter.getVaginaType()==VaginaType.NONE) {
										setBlockedTooltipContent(getTooltipText(equippedToCharacter,
													"You don't have a vagina.",
													(equippedToCharacter.isAreaKnownByCharacter(CoverableArea.VAGINA, Main.game.getPlayer())
														?"[npc.Name] doesn't have a vagina."
														:"You don't know if [npc.name] has a vagina.")));
										piercingBlocked=true;

									} else if(!bypassesPiercing && !equippedToCharacter.isPiercedVagina()) {
										setBlockedTooltipContent(getTooltipText(equippedToCharacter,
												"Your vagina has not been pierced.",
												(equippedToCharacter.isAreaKnownByCharacter(CoverableArea.VAGINA, Main.game.getPlayer())
														?"[npc.NamePos] vagina has not been pierced."
														:"You don't know if [npc.name] has a vagina.")));
										piercingBlocked=true;
									}
									break;

								case PIERCING_EAR:
									if(!bypassesPiercing) {
										if(!equippedToCharacter.isPiercedEar()) {
											setBlockedTooltipContent(getTooltipText(equippedToCharacter,
													"Your ears have not been pierced.",
													"[npc.NamePos] ears have not been pierced."));
											piercingBlocked=true;
										}
									}
									break;

								case PIERCING_LIP:
									if(!bypassesPiercing) {
										if(!equippedToCharacter.isPiercedLip()) {
											setBlockedTooltipContent(getTooltipText(equippedToCharacter,
													"Your lips have not been pierced.",
													"[npc.NamePos] lips have not been pierced."));
											piercingBlocked=true;
										}
									}
									break;

								case PIERCING_NIPPLE:
									if(!bypassesPiercing) {
										if(!equippedToCharacter.isPiercedNipple()) {
											setBlockedTooltipContent(getTooltipText(equippedToCharacter,
													"Your nipples have not been pierced.",
													(equippedToCharacter.isAreaKnownByCharacter(CoverableArea.NIPPLES, Main.game.getPlayer())
															?"[npc.NamePos] nipples have not been pierced."
															:"You don't know if [npc.namePos] nipples have been pierced.")));
											piercingBlocked=true;
										}
									}
									break;

								case PIERCING_NOSE:
									if(!bypassesPiercing) {
										if(!equippedToCharacter.isPiercedNose()) {
											setBlockedTooltipContent(getTooltipText(equippedToCharacter,
													"Your nose has not been pierced.",
													"[npc.NamePos] nose has not been pierced."));
											piercingBlocked=true;
										}
									}
									break;

								case PIERCING_PENIS:
									if(equippedToCharacter.getPenisType()==PenisType.NONE) {
										setBlockedTooltipContent(getTooltipText(equippedToCharacter,
												"You don't have a penis.",
												(equippedToCharacter.isAreaKnownByCharacter(CoverableArea.PENIS, Main.game.getPlayer())
														?"[npc.Name] doesn't have a penis."
														:"You don't know if [npc.name] has a penis.")));
										piercingBlocked=true;

									} else if(!bypassesPiercing && !equippedToCharacter.isPiercedPenis()) {
										setBlockedTooltipContent(getTooltipText(equippedToCharacter,
												"Your penis has not been pierced.",
												(equippedToCharacter.isAreaKnownByCharacter(CoverableArea.PENIS, Main.game.getPlayer())
														?"[npc.NamePos] penis has not been pierced."
														:"You don't know if [npc.name] has a penis.")));
										piercingBlocked=true;
									}
									break;

								case PIERCING_STOMACH:
									if(!bypassesPiercing) {
										if(!equippedToCharacter.isPiercedNavel()) {
											setBlockedTooltipContent(getTooltipText(equippedToCharacter,
													"Your navel has not been pierced.",
													"[npc.NamePos] navel has not been pierced."));
											piercingBlocked=true;
										}
									}
									break;

								case PIERCING_TONGUE:
									if(!bypassesPiercing) {
										if(!equippedToCharacter.isPiercedTongue()) {
											setBlockedTooltipContent(getTooltipText(equippedToCharacter,
													"Your tongue has not been pierced.",
													"[npc.NamePos] tongue has not been pierced."));
											piercingBlocked=true;
										}
									}
									break;

								case HORNS:
									if(equippedToCharacter.getHornType().equals(HornType.NONE)) {
										setBlockedTooltipContent(getTooltipText(equippedToCharacter,
												"You don't have any horns.",
												"[npc.Name] doesn't have any horns."));
										piercingBlocked=true;
									}
									break;

								case PENIS:
									if(!equippedToCharacter.hasPenisIgnoreDildo()) {
										setBlockedTooltipContent(getTooltipText(equippedToCharacter,
												"You don't have a penis.",
												"[npc.Name] doesn't have a penis."));
										piercingBlocked=true;
									}
									break;

								case TAIL:
									if(equippedToCharacter.getTailType()==TailType.NONE) {
										setBlockedTooltipContent(getTooltipText(equippedToCharacter,
												"You don't have a tail.",
												"[npc.Name] doesn't have a tail."));
										piercingBlocked=true;
									}
									break;

								case VAGINA:
									if(!equippedToCharacter.hasVagina()) {
										setBlockedTooltipContent(getTooltipText(equippedToCharacter,
												"You don't have a vagina.",
												"[npc.Name] doesn't have a vagina."));
										piercingBlocked=true;
									}
									break;

								case WINGS:
									if(equippedToCharacter.getWingType()==WingType.NONE) {
										setBlockedTooltipContent(getTooltipText(equippedToCharacter,
												"You don't have any wings.",
												"[npc.Name] doesn't have any wings."));
										piercingBlocked=true;
									}
									break;

								default:
									break;
							}

							if(!piercingBlocked){
								if(renderingTattoos) {
									scarTooltip(equippedToCharacter.getScarInSlot(invSlot));

								} else {
									setEmptyInventorySlotTooltipContent();
								}
							}
						}

					} else {
						if(renderingTattoos && !invSlot.isJewellery()) {
							tattooTooltip(equippedToCharacter.getTattooInSlot(invSlot));
						} else {
							clothingTooltip(equippedToCharacter.getClothingInSlot(invSlot));
						}
					}

				} else {
					setEmptyInventorySlotTooltipContent();
				}
			}

		} else if (enchantmentModifier != null) {
			Main.mainController.setTooltipSize(TOOLTIP_WIDTH, 152);
			Main.mainController.setTooltipContent(UtilText.parse(
					"
"
							+ Util.capitaliseSentence(enchantmentModifier.getName())
					+ "
"
					+ "
"
					+ UtilText.parse(enchantmentModifier.getDescription())
					+ "
"
					+ "
"
					+(EnchantmentDialogue.getIngredient() instanceof Tattoo
							? UtilText.formatAsMoney(enchantmentModifier.getValue()*EnchantingUtils.FLAME_COST_MODIFER, "b")+" cost"
							: UtilText.formatAsEssences(enchantmentModifier.getValue(), "b", false)+" essence cost")
					+ "
"));

		} else if(potency!=null) {
			Main.mainController.setTooltipSize(TOOLTIP_WIDTH, 60);
			Main.mainController.setTooltipContent(UtilText.parse("
Set potency to " + Util.capitaliseSentence(potency.getName()) + "
"));

		} else if (essence != null) {
			Main.mainController.setTooltipSize(TOOLTIP_WIDTH, 60);
			Main.mainController.setTooltipContent(UtilText.parse("
" + Util.capitaliseSentence(essence.getName()) + " essence
"));

		}  else {
			return;
		}

		TooltipUpdateThread.updateToolTip(-1,-1);
		// Main.mainController.getTooltip().show(Main.primaryStage);
	}

	private void setBlockedTooltipContent(String description){
		setBlockedTooltipContent("Blocked!", description);
	}
	private void setBlockedTooltipContent(String title, String description){
		boolean dirty = equippedToCharacter.isDirtySlot(invSlot);
		Main.mainController.setTooltipSize(TOOLTIP_WIDTH, 144);
		Main.mainController.setTooltipContent(UtilText.parse(equippedToCharacter,
				"
"
						+ Util.capitaliseSentence(invSlot.getName())+ ": "+title
				+ "
"
				+"
"
					+ (dirty
						?"[npc.NamePos] "+invSlot.getName()+" "+(invSlot.isPlural()?"are":"is")
								+ " dirty!
"
						:"")
					 + UtilText.parse(description)
				 +"
"));
	}

	private void setEmptyInventorySlotTooltipContent(){
		if(equippedToCharacter==null) {
			Main.mainController.setTooltipSize(TOOLTIP_WIDTH, 60);
			Main.mainController.setTooltipContent("
"
					+ Util.capitaliseSentence(invSlot.getName())
			+ "
");
			return;
		}
		boolean dirty = equippedToCharacter.isDirtySlot(invSlot);
		Main.mainController.setTooltipSize(TOOLTIP_WIDTH, 60+(dirty?56:0));
		Main.mainController.setTooltipContent(UtilText.parse(equippedToCharacter,
				"
"
						+ Util.capitaliseSentence(invSlot.getName())
				+ "
"
				+ (dirty
					?"
"
							+ "[npc.NamePos] "+invSlot.getName()+" "+(invSlot.isPlural()?"have":"has")
							+ " been dirtied by sexual fluids!"
						+ "
"
					:"")));
	}

	public TooltipInventoryEventListener setCoreItem(AbstractCoreItem coreItem, GameCharacter owner, GameCharacter equippedToCharacter) {
		resetVariables();
		this.coreItem = coreItem;
		this.equippedToCharacter = equippedToCharacter;
		this.owner = owner;
		return this;
	}

	public TooltipInventoryEventListener setItem(AbstractItem item, GameCharacter owner, GameCharacter equippedToCharacter) {
		resetVariables();
		this.item = item;
		this.equippedToCharacter = equippedToCharacter;
		this.owner = owner;
		return this;
	}

	public TooltipInventoryEventListener setTattoo(InventorySlot invSlot, Tattoo tattoo, GameCharacter owner, GameCharacter equippedToCharacter) {
		resetVariables();
		this.invSlot = invSlot;
		this.tattoo = tattoo;
		this.equippedToCharacter = equippedToCharacter;
		this.owner = owner;
		return this;
	}

	public TooltipInventoryEventListener setGenericItem(AbstractItemType genericItem) {
		resetVariables();
		this.genericItem = genericItem;
		return this;
	}

	public TooltipInventoryEventListener setClothing(AbstractClothing clothing, GameCharacter owner, GameCharacter equippedToCharacter) {
		resetVariables();
		this.clothing = clothing;
		this.equippedToCharacter = equippedToCharacter;
		this.owner = owner;
		return this;
	}

	public TooltipInventoryEventListener setDyeClothingPrimary(AbstractClothing dyeClothing, Colour colour) {
		resetVariables();
		this.dyeClothing = dyeClothing;
		this.colour = colour;
		return this;
	}

	public TooltipInventoryEventListener setDyeClothingSecondary(AbstractClothing dyeClothing, Colour secondaryColour) {
		resetVariables();
		this.dyeClothing = dyeClothing;
		this.secondaryColour = secondaryColour;
		return this;
	}

	public TooltipInventoryEventListener setDyeClothingTertiary(AbstractClothing dyeClothing, Colour tertiaryColour) {
		resetVariables();
		this.dyeClothing = dyeClothing;
		this.tertiaryColour = tertiaryColour;
		return this;
	}

	public TooltipInventoryEventListener setDyeWeaponPrimary(AbstractWeapon dyeWeapon, Colour colour) {
		resetVariables();
		this.dyeWeapon = dyeWeapon;
		this.colour = colour;
		return this;
	}

	public TooltipInventoryEventListener setDyeWeaponSecondary(AbstractWeapon dyeWeapon, Colour secondaryColour) {
		resetVariables();
		this.dyeWeapon = dyeWeapon;
		this.secondaryColour = secondaryColour;
		return this;
	}

	public TooltipInventoryEventListener setDamageTypeWeapon(AbstractWeapon dyeWeapon, DamageType damageType) {
		resetVariables();
		this.dyeWeapon = dyeWeapon;
		this.damageType = damageType;
		return this;
	}

	public TooltipInventoryEventListener setDyeClothingPattern(AbstractClothing dyeClothing, Pattern pattern) {
		resetVariables();
		this.dyeClothing = dyeClothing;
		this.pattern = pattern;
		return this;
	}

	public TooltipInventoryEventListener setGenericClothing(AbstractClothingType genericClothing, Colour colour) {
		resetVariables();
		this.genericClothing = genericClothing;
		this.colour = colour;
		return this;
	}

	public TooltipInventoryEventListener setGenericWeapon(AbstractWeaponType genericWeapon, DamageType dt) {
		resetVariables();
		this.genericWeapon = genericWeapon;
		this.dt = dt;
		return this;
	}

	public TooltipInventoryEventListener setWeapon(AbstractWeapon weapon, GameCharacter owner, boolean isEquipped) {
		resetVariables();
		this.weapon = weapon;
		if(isEquipped) {
			this.equippedToCharacter = owner;
		}
		this.owner = owner;
		return this;
	}

	public TooltipInventoryEventListener setInventorySlot(InventorySlot invSlot, GameCharacter equippedToCharacter) {
		resetVariables();
		this.invSlot = invSlot;
		this.equippedToCharacter = equippedToCharacter;
		this.owner = equippedToCharacter;
		return this;
	}

	public TooltipInventoryEventListener setTFModifier(TFModifier enchantmentModifier) {
		resetVariables();
		this.enchantmentModifier = enchantmentModifier;
		return this;
	}

	public TooltipInventoryEventListener setTFPotency(TFPotency potency) {
		resetVariables();
		this.potency = potency;
		return this;
	}

	public TooltipInventoryEventListener setEssence(TFEssence essence) {
		resetVariables();
		this.essence = essence;
		return this;
	}

	private void resetVariables() {
		owner = null;
		equippedToCharacter = null;
		coreItem = null;
		item = null;
		tattoo = null;
		genericItem = null;
		weapon = null;
		genericWeapon = null;
		dt = null;
		clothing = null;
		colour = null;
		dyeClothing = null;
		dyeWeapon = null;
		damageType = null;
		secondaryColour = null;
		tertiaryColour = null;
		pattern = null;
		genericClothing = null;
		invSlot = null;
		enchantmentModifier = null;
		potency = null;
		essence = null;
	}

	private void itemTooltip(AbstractItem absItem) {

		int yIncrease = 0;
		int listIncrease = 0;

		if(!absItem.getEffects().isEmpty()) {
			listIncrease+=1;
			for(ItemEffect ie : absItem.getEffects()) {
				listIncrease += ie.getEffectsDescription(Main.game.getPlayer(), Main.game.getPlayer()).size();
			}
		}

		yIncrease += Math.max(0, listIncrease-3);

		// Title:
		tooltipSB.setLength(0);
		tooltipSB.append(""
			+ "

" + Util.capitaliseSentence(absItem.getDisplayName(true)) + "

");

		// Core info:
		tooltipSB.append("
"
				+ (absItem.isConsumedOnUse()
						? "Consumed on use"
						: "Infinite uses")
				+ "
"
					);

		tooltipSB.append("
"
				+ "
"
				+ ""+Util.capitaliseSentence(absItem.getRarity().getName())+""
				);

		for(ItemEffect ie : absItem.getEffects()) {
			for(int i=0; i<ie.getEffectsDescription(Main.game.getPlayer(), Main.game.getPlayer()).size(); i++) {
				tooltipSB.append("
"+ie.getEffectsDescription(Main.game.getPlayer(), Main.game.getPlayer()).get(i));
			}
		}

		tooltipSB.append("
");

		// Picture:
		tooltipSB.append("
"
								+ "
"
									+(absItem.getSVGString())
								+ "
"
							+ "
");

		tooltipSB.append("
");

		tooltipSB.append("
"
						+ absItem.getDescription()
					+ "
");

		// Value:

		if (InventoryDialogue.getInventoryNPC() != null && InventoryDialogue.getNPCInventoryInteraction() == InventoryInteraction.TRADING) {
			if (owner.isPlayer()) {
				if (InventoryDialogue.getInventoryNPC().willBuy(absItem)) {
					tooltipSB.append("
"
										+ "Value: "+UtilText.formatAsMoney(absItem.getValue())
										+" | "
										+ InventoryDialogue.getInventoryNPC().getName("The") + " offers: " + UtilText.formatAsMoney(absItem.getPrice(InventoryDialogue.getInventoryNPC().getBuyModifier()))
									+ "
");
				} else {
					tooltipSB.append("
"
										+ "Value: "+UtilText.formatAsMoney(absItem.getValue())
										+" | "
										+ "" + InventoryDialogue.getInventoryNPC().getName("The") + " will not buy this"
									+ "
");
				}
			} else {
				if (InventoryDialogue.isBuyback()) {
					tooltipSB.append("
"
											+ "Value: "+UtilText.formatAsMoney(absItem.getValue())
											+" | "
											+ InventoryDialogue.getInventoryNPC().getName("The") + " wants " + UtilText.formatAsMoney( + getBuybackPriceFor(absItem))
									+ "
");
				} else {
					tooltipSB.append("
"
											+ "Value: "+UtilText.formatAsMoney(absItem.getValue())
											+" | "
											+ InventoryDialogue.getInventoryNPC().getName("The") + " wants " + UtilText.formatAsMoney(absItem.getPrice(InventoryDialogue.getInventoryNPC().getSellModifier()))
									+ "
");
				}
			}
		} else {
			tooltipSB.append("
" + "Value: "+UtilText.formatAsMoney(absItem.getValue()) + "
");
		}

		tooltipSB.append("");

		Main.mainController.setTooltipSize(TOOLTIP_WIDTH, 364 + (yIncrease * LINE_HEIGHT));
		Main.mainController.setTooltipContent(UtilText.parse(tooltipSB.toString()));

	}

	private void weaponTooltip(AbstractWeapon absWep) {

		int yIncrease = 0;
		int listIncrease = 2 + absWep.getAttributeModifiers().size();
		listIncrease += absWep.getSpells().size();

		String author = absWep.getWeaponType().getAuthorDescription();
		if(!author.isEmpty()) {
			yIncrease+=5;
		}

		// Title:
		tooltipSB.setLength(0);
		tooltipSB.append(""
			+ "

" + Util.capitaliseSentence(absWep.getDisplayName(true)) + "

");

		// Core info:
		tooltipSB.append("
" + Util.capitaliseSentence(absWep.getDamageType().getName()) + " damage
");
		tooltipSB.append("
"
							+ (absWep.getWeaponType().getClothingSet() == null
								? "Not part of a set"
								: ""+absWep.getWeaponType().getClothingSet().getName() + " set")
						+ "
");

		// Attribute modifiers:
		tooltipSB.append("
"
				+ "
"
				+ ""+Util.capitaliseSentence(absWep.getRarity().getName())+""+ " | "+(absWep.getWeaponType().isMelee()?"Melee":"Ranged")+"
"
				+ (absWep.getWeaponType().isTwoHanded()? "Two-handed" : "One-handed")+"
"
				);

		float res = absWep.getWeaponType().getPhysicalResistance();
		if(res>0) {
			listIncrease++;
			tooltipSB.append("[style.boldGood(+"+res+")] Natural [style.boldResPhysical("+Util.capitaliseSentence(Attribute.RESISTANCE_PHYSICAL.getName())+")]
");
		}

		int cost = absWep.getWeaponType().getArcaneCost();
		if(cost>0) {
			listIncrease++;
			tooltipSB.append("Costs [style.boldArcane("+cost+" Arcane essence"+(cost>1?"s":"")+")] "+(absWep.getWeaponType().isMelee()?"per attack":"to fire")+"
");
		}

		if (equippedToCharacter != null) {
			tooltipSB.append(""
								+ Attack.getMinimumDamage(equippedToCharacter, null, Attack.MAIN, absWep) + " - " + Attack.getMaximumDamage(equippedToCharacter, null, Attack.MAIN, absWep)
							+ ""
							+ " Damage");

		} else {
			if(owner!=null && !owner.isPlayer()) {
				listIncrease++;
				tooltipSB.append(UtilText.parse(owner, "[npc.Name]: ")
					+""
						+ Attack.getMinimumDamage(owner, null, Attack.MAIN, absWep) + " - " + Attack.getMaximumDamage(owner, null, Attack.MAIN, absWep)
					+ ""
					+ " Damage
"
					+ "You: ");
			}
			tooltipSB.append(""
								+ Attack.getMinimumDamage(Main.game.getPlayer(), null, Attack.MAIN, absWep) + " - " + Attack.getMaximumDamage(Main.game.getPlayer(), null, Attack.MAIN, absWep)
							+ ""
							+ " Damage");
		}

//		if (absWep.getEffects().size() != 0) { TODO enchanting effects
//			for (ItemEffect e : absWep.getEffects()) {
//				for(String s : e.getEffectsDescription(owner, owner)) {
//					tooltipSB.append("
"+ s);
//				}
//			}
			for(Entry<Attribute, Integer> entry : absWep.getAttributeModifiers().entrySet()) {
				tooltipSB.append("
"+
						(entry.getValue()<0
								?"[style.boldBad("+entry.getValue()+")] "
								:"[style.boldGood(+"+entry.getValue()+")] ")
						+ ""+Util.capitaliseSentence(entry.getKey().getName())+"");
			}
//		} else {
//			tooltipSB.append("
[style.colourDisabled(No bonuses)]");
//		}

			for(Spell s : absWep.getSpells()) {
				tooltipSB.append("
Grants Spell: "+Util.capitaliseSentence(s.getName())+"");
			}

		tooltipSB.append("
");

		// Picture:
		tooltipSB.append("
"
							+ "
"
								+ ((owner!=null && owner.hasWeaponEquipped(absWep))
									?absWep.getSVGEquippedString(owner)
									:absWep.getSVGString())
							+ "
"
						+ "
");

		tooltipSB.append("
");

		tooltipSB.append("
"
						+ absWep.getWeaponType().getDescription()
					+ "
");

		if(owner!=null && owner.getEssenceCount(TFEssence.ARCANE)<absWep.getWeaponType().getArcaneCost()) {
			yIncrease+=2;
			tooltipSB.append("
"
								+ "[style.colourBad(Not enough essences to fire!)]"
							+ "
");
		}

		// Value:

		if (InventoryDialogue.getInventoryNPC() != null && InventoryDialogue.getNPCInventoryInteraction() == InventoryInteraction.TRADING) {
			if (owner.isPlayer()) {
				if (InventoryDialogue.getInventoryNPC().willBuy(absWep)) {
					tooltipSB.append("
"
										+ "Value: "+UtilText.formatAsMoney(absWep.getValue())
										+" | "
										+ InventoryDialogue.getInventoryNPC().getName("The") + " offers: " + UtilText.formatAsMoney(absWep.getPrice(InventoryDialogue.getInventoryNPC().getBuyModifier()))
									+ "
");
				} else {
					tooltipSB.append("
"
										+ "Value: "+UtilText.formatAsMoney(absWep.getValue())
										+" | "
										+ "" + InventoryDialogue.getInventoryNPC().getName("The") + " will not buy this"
									+ "
");
				}
			} else {
				if (InventoryDialogue.isBuyback()) {
					tooltipSB.append("
"
											+ "Value: "+UtilText.formatAsMoney(absWep.getValue())
											+" | "
											+ InventoryDialogue.getInventoryNPC().getName("The") + " wants " + UtilText.formatAsMoney( + getBuybackPriceFor(absWep))
									+ "
");
				} else {
					tooltipSB.append("
"
											+ "Value: "+UtilText.formatAsMoney(absWep.getValue())
											+" | "
											+ InventoryDialogue.getInventoryNPC().getName("The") + " wants " + UtilText.formatAsMoney(absWep.getPrice(InventoryDialogue.getInventoryNPC().getSellModifier()))
									+ "
");
				}
			}
		} else {
			tooltipSB.append(
					"
"
							+ "Value: "+UtilText.formatAsMoney(absWep.getValue())
					+ "
");
		}
		if(Main.game.isEnchantmentCapacityEnabled()) {
			int enchCapacityCost = absWep.getEnchantmentCapacityCost();
			tooltipSB.append(
					"
"
							+(enchCapacityCost==0
								?Util.capitaliseSentence(Attribute.ENCHANTMENT_LIMIT.getName())+" cost: [style.boldDisabled("+enchCapacityCost+")]"
								:"[style.colourEnchantment("+Util.capitaliseSentence(Attribute.ENCHANTMENT_LIMIT.getName())+" cost)]: [style.boldBad("+enchCapacityCost+")]")
					+ "
");
		}
		if(!author.isEmpty()) {
			tooltipSB.append("
" + author + "
");
		}

		tooltipSB.append("");

		yIncrease += Math.max(0, listIncrease-4);
		Main.mainController.setTooltipSize(TOOLTIP_WIDTH, 364 + (Main.game.isEnchantmentCapacityEnabled()?32:0) + (yIncrease * LINE_HEIGHT));
		Main.mainController.setTooltipContent(UtilText.parse(tooltipSB.toString()));

	}

	private void clothingTooltip(AbstractClothing absClothing) {
		int yIncrease = 0;

		int listIncrease = absClothing.getAttributeModifiers().size();

		InventorySlot slotEquippedTo = absClothing.getSlotEquippedTo();
		if(slotEquippedTo==null) {
			slotEquippedTo = absClothing.getClothingType().getEquipSlots().get(0);
		}

		yIncrease += absClothing.getExtraDescriptions(equippedToCharacter, slotEquippedTo).size();

		for(ItemEffect ie : absClothing.getEffects()) {
			if(ie.getSecondaryModifier()==TFModifier.CLOTHING_ENSLAVEMENT
					|| ie.getSecondaryModifier()==TFModifier.CLOTHING_SEALING) {
				listIncrease+=1;

			} else if(ie.getPrimaryModifier()!=TFModifier.CLOTHING_ATTRIBUTE && ie.getPrimaryModifier()!=TFModifier.CLOTHING_MAJOR_ATTRIBUTE) {
				listIncrease+=2;
			}
		}
		yIncrease += Math.max(0, listIncrease-4);

		String author = absClothing.getClothingType().getAuthorDescription();
		if(!author.isEmpty()) {
			yIncrease+=4;
		}

		// Title:
		tooltipSB.setLength(0);
		tooltipSB.append(""
			+ "

" + Util.capitaliseSentence(absClothing.getDisplayName(true)) + "

");

		// Core info:
		tooltipSB.append("
");
			for(int i=0; i<absClothing.getClothingType().getEquipSlots().size(); i++) {
				InventorySlot slot = absClothing.getClothingType().getEquipSlots().get(i);
				boolean equipped = absClothing.getSlotEquippedTo() == slot;
				tooltipSB.append(
						(equipped || absClothing.getSlotEquippedTo()==null
							?Util.capitaliseSentence(slot.getName())
							:"[style.colourDisabled("+Util.capitaliseSentence(slot.getName())+")]")
						+(i==absClothing.getClothingType().getEquipSlots().size()-1
							?""
							:(absClothing.getSlotEquippedTo()!=null
								?"[style.colourDisabled(/)]"
								:"/")));
			}
		tooltipSB.append("
");
		tooltipSB.append("
"
							+ (absClothing.getClothingType().getClothingSet() == null
								? "Not part of a set"
								: ""+absClothing.getClothingType().getClothingSet().getName() + " set")
						+ "
");

		// Attribute modifiers:
		tooltipSB.append("
"
				+ "
");

		Femininity femininityRestriction = absClothing.getClothingType().getFemininityRestriction();
		tooltipSB.append(
				""
						+Util.capitaliseSentence(absClothing.isEnchantmentKnown()?absClothing.getRarity().getName():"Unknown")
				+""
				+ " | "
				+ (femininityRestriction==null || femininityRestriction==Femininity.ANDROGYNOUS
					?"[style.boldAndrogynous(Unisex)]"
					:(femininityRestriction.isFeminine()
						?"[style.boldFeminine(Feminine)]"
						:"[style.boldMasculine(Masculine)]")));

		float res = absClothing.getClothingType().getPhysicalResistance();
		if(res>0) {
			yIncrease++;
			tooltipSB.append("
[style.boldGood(+"+res+")] Natural [style.boldResPhysical("+Util.capitaliseSentence(Attribute.RESISTANCE_PHYSICAL.getName())+")]");
		}

		if (!absClothing.getEffects().isEmpty()) {
			if (!absClothing.isEnchantmentKnown()) {
				tooltipSB.append("
[style.colourDisabled(Unidentified effects!)]");
			} else {
				for (ItemEffect e : absClothing.getEffects()) {
					if(e.getPrimaryModifier()!=TFModifier.CLOTHING_ATTRIBUTE && e.getPrimaryModifier()!=TFModifier.CLOTHING_MAJOR_ATTRIBUTE) {
						for(String s : e.getEffectsDescription(owner, owner)) {
							tooltipSB.append("
"+ s);
						}
					}
				}
				for(Entry<Attribute, Integer> entry : absClothing.getAttributeModifiers().entrySet()) {
					tooltipSB.append("
"+
							(entry.getValue()<0
									?"[style.boldBad("+entry.getValue()+")] "
									:"[style.boldGood(+"+entry.getValue()+")] ")
							+ ""+Util.capitaliseSentence(entry.getKey().getName())+"");
				}
			}

		} else {
			tooltipSB.append("
[style.colourDisabled(No bonuses)]");
		}

		tooltipSB.append("
");

		// Picture:
		tooltipSB.append("
"
							+ "
"
								+ (owner!=null && owner.getClothingCurrentlyEquipped().contains(absClothing)?absClothing.getSVGEquippedString(owner):absClothing.getSVGString())
							+ "
"
						+ "
");

		tooltipSB.append("
");

		tooltipSB.append("
"
						+ absClothing.getTypeDescription()
					+ "
");

		tooltipSB.append("
");

		if(absClothing.getSlotEquippedTo()==null && absClothing.getClothingType().getEquipSlots().size()>1) {
			for(int i=0; i<absClothing.getClothingType().getEquipSlots().size();i++) {
				if(i>0) {
					tooltipSB.append("
");
					yIncrease++;
				}
				InventorySlot slot = absClothing.getClothingType().getEquipSlots().get(i);

				tooltipSB.append("When equipped into '"+slot.getName()+"' slot:");
				if (absClothing.getExtraDescriptions(equippedToCharacter, slot).isEmpty()) {
					tooltipSB.append("
No Status");
					yIncrease++;
				} else {
					for (String s : absClothing.getExtraDescriptions(equippedToCharacter, slot)) {
						tooltipSB.append("
" + s);
						yIncrease++;
					}
				}
			}

		} else {
			if (absClothing.getExtraDescriptions(equippedToCharacter, slotEquippedTo).isEmpty()) {
				tooltipSB.append("No Status");
			} else {
				tooltipSB.append("Status");
				for (String s : absClothing.getExtraDescriptions(equippedToCharacter, slotEquippedTo)) {
					tooltipSB.append("
" + s);
				}
			}
		}
		tooltipSB.append("
");

		// Value:

		if (InventoryDialogue.getInventoryNPC() != null && InventoryDialogue.getNPCInventoryInteraction() == InventoryInteraction.TRADING) {
			if (owner.isPlayer()) {
				if (InventoryDialogue.getInventoryNPC().willBuy(absClothing)) {
					tooltipSB.append("
"
										+ "Value: "+(absClothing.isEnchantmentKnown() ? UtilText.formatAsMoney(absClothing.getValue()) : UtilText.formatAsMoney("?", "b"))
										+" | "
										+ InventoryDialogue.getInventoryNPC().getName("The") + " offers " + UtilText.formatAsMoney(absClothing.getPrice(InventoryDialogue.getInventoryNPC().getBuyModifier()))
									+ "
");
				} else {
					tooltipSB.append("
"
										+ "Value: "+(absClothing.isEnchantmentKnown() ? UtilText.formatAsMoney(absClothing.getValue()) : UtilText.formatAsMoney("?", "b"))
										+" | "
										+ "" + InventoryDialogue.getInventoryNPC().getName("The") + " will not buy this"
									+ "
");
				}
			} else {
				if (InventoryDialogue.isBuyback()) {
					tooltipSB.append("
"
											+ "Value: "+(absClothing.isEnchantmentKnown() ? UtilText.formatAsMoney(absClothing.getValue()) : UtilText.formatAsMoney("?", "b"))
											+" | "
											+ InventoryDialogue.getInventoryNPC().getName("The") + " wants " + UtilText.formatAsMoney( + getBuybackPriceFor(absClothing))
									+ "
");
				} else {
					tooltipSB.append("
"
											+ "Value: "+(absClothing.isEnchantmentKnown() ? UtilText.formatAsMoney(absClothing.getValue()) : UtilText.formatAsMoney("?", "b"))
											+" | "
											+ InventoryDialogue.getInventoryNPC().getName("The") + " wants " + UtilText.formatAsMoney(absClothing.getPrice(InventoryDialogue.getInventoryNPC().getSellModifier()))
									+ "
");
				}
			}
		} else {
			tooltipSB.append("
Value: "+ (absClothing.isEnchantmentKnown() ? UtilText.formatAsMoney(absClothing.getValue()) : UtilText.formatAsMoney("?", "b")) + "
");
		}

		if(Main.game.isEnchantmentCapacityEnabled()) {
			int enchCapacityCost = absClothing.getEnchantmentCapacityCost();
			tooltipSB.append(
					"
"
							+(absClothing.isEnchantmentKnown() ?
								(enchCapacityCost==0
									?Util.capitaliseSentence(Attribute.ENCHANTMENT_LIMIT.getName())+" cost: [style.boldDisabled("+enchCapacityCost+")]"
									:"[style.colourEnchantment("+Util.capitaliseSentence(Attribute.ENCHANTMENT_LIMIT.getName())+" cost)]: [style.boldBad("+enchCapacityCost+")]")
							:Util.capitaliseSentence(Attribute.ENCHANTMENT_LIMIT.getName())+" cost: ?")
					+ "
");
		}

		if(!author.isEmpty()) {
			tooltipSB.append("
" + author + "
");
		}

		tooltipSB.append("");

		int specialIncrease = 0;
		if(absClothing.getDisplayName(false).length()>40) {
			specialIncrease = 26;
		}
		Main.mainController.setTooltipSize(TOOLTIP_WIDTH, 400 + (Main.game.isEnchantmentCapacityEnabled()?32:0) + (yIncrease * LINE_HEIGHT) + specialIncrease);
		Main.mainController.setTooltipContent(UtilText.parse(tooltipSB.toString()));

	}

	private void scarTooltip(Scar scar) {
		// Title:
		tooltipSB.setLength(0);
		tooltipSB.append(""
			+ "

No tattoo

");

		// Core info:
		tooltipSB.append("
" + Util.capitaliseSentence(invSlot.getTattooSlotName()) + "
");
		tooltipSB.append("
"
							+ (scar==null
									? "No scars"
									: ""+Util.capitaliseSentence(owner.getScarInSlot(invSlot).getName())+"")
						+ "
");

		Main.mainController.setTooltipSize(TOOLTIP_WIDTH, 88);
		Main.mainController.setTooltipContent(UtilText.parse(tooltipSB.toString()));
	}

	private void tattooTooltip(Tattoo tattoo) {
		int yIncrease = 0;
		int specialIncrease = 0;

		if (tattoo.getWriting()!=null && !tattoo.getWriting().getText().isEmpty()) {
			specialIncrease+=8;
			yIncrease++;
		}
		if (tattoo.getCounter()!=null && tattoo.getCounter().getType()!=TattooCounterType.NONE) {
			specialIncrease+=16;
			yIncrease++;
			yIncrease++;
		}
		int lSize=0;
		for (ItemEffect e : tattoo.getEffects()) {
			if(e.getPrimaryModifier()==TFModifier.CLOTHING_ATTRIBUTE
					|| e.getPrimaryModifier()==TFModifier.CLOTHING_MAJOR_ATTRIBUTE
					|| e.getPrimaryModifier()==TFModifier.TF_MOD_FETISH_BEHAVIOUR
					|| e.getPrimaryModifier()==TFModifier.TF_MOD_FETISH_BODY_PART) {
				lSize++;
			} else {
				lSize+=2;
			}
		}
		lSize-=4;
		if(lSize<0) {
			lSize=0;
		}

		// Title:
		tooltipSB.setLength(0);
		tooltipSB.append(""
			+ "

" + Util.capitaliseSentence(tattoo.getDisplayName(true)) + "

");

		// Core info:
		tooltipSB.append("
" + (invSlot.getTattooSlotName()==null?"[style.colourDisabled(Cannot be tattooed)]":Util.capitaliseSentence(invSlot.getTattooSlotName())) + "
");
		tooltipSB.append("
"
							+ (owner.getScarInSlot(invSlot)==null
									? "No scars"
									: ""+Util.capitaliseSentence(owner.getScarInSlot(invSlot).getName())+"")
						+ "
");

		// Attribute modifiers:
		tooltipSB.append("
"
				+ "
");

		if (!tattoo.getEffects().isEmpty()) {
			int i=0;
			for (ItemEffect e : tattoo.getEffects()) {
				if(e.getPrimaryModifier()!=TFModifier.CLOTHING_ATTRIBUTE && e.getPrimaryModifier()!=TFModifier.CLOTHING_MAJOR_ATTRIBUTE) {
					for(String s : e.getEffectsDescription(owner, owner)) {
						tooltipSB.append((i>0?"
"+s:s));
					}
					i++;
				}
			}
			for(Entry<Attribute, Integer> entry : tattoo.getAttributeModifiers().entrySet()) {
				tooltipSB.append((i>0?"
":"")
						+ (entry.getValue()<0
								?"[style.boldBad("+entry.getValue()+")] "
								:"[style.boldGood(+"+entry.getValue()+")] ")
						+ ""+Util.capitaliseSentence(entry.getKey().getName())+"");
				i++;
			}

		} else {
			tooltipSB.append("[style.colourDisabled(No bonuses)]");
		}

		tooltipSB.append("
");

		// Picture:
		tooltipSB.append("
"
						+ tattoo.getSVGImage(equippedToCharacter)
					+ "
");

		tooltipSB.append("
");

		tooltipSB.append("
"
						+ tattoo.getType().getDescription());

			if (tattoo.getWriting()!=null && !tattoo.getWriting().getText().isEmpty()) {
				tooltipSB.append("
");
				if(tattoo.getWriting().getStyles().isEmpty()) {
					tooltipSB.append("Normal,");
				} else {
					int i=0;
					for(TattooWritingStyle style : tattoo.getWriting().getStyles()) {
						tooltipSB.append(i==0?Util.capitaliseSentence(style.getName()):", "+style.getName());
						i++;
					}
				}
				tooltipSB.append(" "+tattoo.getWriting().getColour().getName()+" writing forms part of the tattoo.");
			}

			if (tattoo.getCounter()!=null && tattoo.getCounter().getType()!=TattooCounterType.NONE) {
				tooltipSB.append("
"
									+ "An enchanted, "+tattoo.getCounter().getColour().getName()+" "+tattoo.getCounter().getType().getName()+" counter has been applied to the tattoo.");
			}

		tooltipSB.append("
");

		if (tattoo.getWriting()!=null && !tattoo.getWriting().getText().isEmpty()) {
			tooltipSB.append("
");
			tooltipSB.append("The writing reads:
");
			tooltipSB.append(tattoo.getFormattedWritingOutput()
					+ "
");
		} else {
			tooltipSB.append(
					"
"
						+"[style.colourDisabled(This tattoo doesn't have any writing.)]"
					+ "
");
		}

		if (tattoo.getCounter()!=null && tattoo.getCounter().getType()!=TattooCounterType.NONE) {
			tooltipSB.append("
"
								+ "The '"+tattoo.getCounter().getType().getName()+"' counter displays:
"
									+ ""
											+tattoo.getFormattedCounterOutput(equippedToCharacter)
									+""
							+ "
");
		} else {
			tooltipSB.append(
					"
"
						+"[style.colourDisabled(This tattoo doesn't have a counter.)]"
					+ "
");
		}

		if(Main.game.isEnchantmentCapacityEnabled()) {
			int enchCapacityCost = tattoo.getEnchantmentCapacityCost();
			tooltipSB.append(
					"
"
							+(enchCapacityCost==0
								?Util.capitaliseSentence(Attribute.ENCHANTMENT_LIMIT.getName())+" cost: [style.boldDisabled("+enchCapacityCost+")]"
								:"[style.colourEnchantment("+Util.capitaliseSentence(Attribute.ENCHANTMENT_LIMIT.getName())+" cost)]: [style.boldBad("+enchCapacityCost+")]")
					+ "
");
		}

		tooltipSB.append("
");

		tooltipSB.append("");

		if(tattoo.getDisplayName(false).length()>40) {
			specialIncrease = 26;
		}
		Main.mainController.setTooltipSize(TOOLTIP_WIDTH, 368 + (Main.game.isEnchantmentCapacityEnabled()?32:0) + (yIncrease * LINE_HEIGHT) + specialIncrease);
		Main.mainController.setTooltipContent(UtilText.parse(tooltipSB.toString()));
	}

	private int getBuybackPriceFor(AbstractCoreItem item) {
	    for (ShopTransaction s : Main.game.getPlayer().getBuybackStack()) {
	        if (s.getAbstractItemSold() == item) {
	            return s.getPrice();
	        }
	    }
	    throw new IllegalArgumentException("That's not a buyback item");
	}

	private static String getTooltipText(GameCharacter character, String playerText, String NPCText) {
		if(character.isPlayer()) {
			return playerText;
		} else {
			return UtilText.parse(character, NPCText);
		}
	}
}


code:
package com.lilithsthrone.controller.eventListeners.tooltips;

import org.w3c.dom.events.Event;
import org.w3c.dom.events.EventListener;
import org.w3c.dom.events.MouseEvent;

import com.lilithsthrone.main.Main;

/**
 * @since 0.1.0
 * @version 0.1.97
 * @author Innoxia
 */
public class TooltipMoveEventListener implements EventListener {
	@Override
	public void handleEvent(Event event) {
//		boolean tooWide = false;
		double xPosition = ((MouseEvent) event).getScreenX() + 16;

		int tooltipWidth = (int) (Main.mainController.getTooltip().getWidth() + 8);

		if (xPosition + tooltipWidth +16 > Main.primaryStage.getX() + Main.primaryStage.getWidth()) {
			xPosition = ((MouseEvent) event).getScreenX() - tooltipWidth - 16;
//			tooWide = true;
		}

		Main.mainController.getTooltip().setAnchorX(xPosition);

		double yPosition = ((MouseEvent) event).getScreenY() + 16;
//		if(tooWide) {
//			if (yPosition + Main.mainController.getTooltip().getHeight() +16 > Main.primaryStage.getY() + Main.primaryStage.getHeight()) {
//				yPosition = ((MouseEvent) event).getScreenY() - Main.mainController.getTooltip().getHeight() - 16;
//			}
//		} else {
//			System.out.println(((((MouseEvent) event).getScreenY() - Main.primaryStage.getY()) + Main.mainController.getTooltip().getHeight()) - Main.primaryStage.getHeight());

			if(((((MouseEvent) event).getScreenY() - Main.primaryStage.getY()) + Main.mainController.getTooltip().getHeight()) - Main.primaryStage.getHeight() + 24 > 0) {
				yPosition -= (((((MouseEvent) event).getScreenY() - Main.primaryStage.getY()) + Main.mainController.getTooltip().getHeight()) - Main.primaryStage.getHeight() + 24);
			}
//		}

		Main.mainController.getTooltip().setAnchorY(yPosition);

	}
}

repo_name: klorpa/Pedo

license: unknown


code:
package com.lilithsthrone.controller.eventListeners.tooltips;

import org.w3c.dom.events.Event;
import org.w3c.dom.events.EventListener;
import org.w3c.dom.events.MouseEvent;

import com.lilithsthrone.controller.MainController;
import com.lilithsthrone.controller.TooltipUpdateThread;
import com.lilithsthrone.game.combat.CombatMove;
import com.lilithsthrone.game.dialogue.responses.Response;
import com.lilithsthrone.game.dialogue.responses.ResponseSex;
import com.lilithsthrone.game.dialogue.utils.UtilText;
import com.lilithsthrone.main.Main;
import com.lilithsthrone.utils.Colour;
import com.lilithsthrone.utils.Util;

/**
 * @since 0.1.0
 * @version 0.3.4.5
 * @author Innoxia
 */
public class TooltipResponseDescriptionEventListener implements EventListener {
	private int index;
	private boolean nextPage = false;
	private boolean previousPage = false;

	private static StringBuilder tooltipSB;
	static {
		tooltipSB = new StringBuilder();
	}

	@Override
	public void handleEvent(Event event) {

		Main.mainController.setTooltipContent("");

		if (nextPage) {
			if (Main.game.isHasNextResponsePage()) {

				Main.mainController.setTooltipSize(360, 60);

				double xPosition = ((MouseEvent) event).getScreenX() + 16 - 180;
				if (xPosition + 360 > Main.primaryStage.getX() + Main.primaryStage.getWidth() - 16)
					xPosition = Main.primaryStage.getX() + Main.primaryStage.getWidth() - 360 - 16;
				double yPosition = Main.primaryStage.getY() + Main.primaryStage.getHeight() - (34*(MainController.RESPONSE_COUNT/5) + 4) - Main.mainController.getTooltip().getHeight()
						- (Main.mainScene.getWindow().getHeight() - Main.mainScene.getHeight() - Main.mainScene.getY());

				Main.mainController.getTooltip().setAnchorX(xPosition);
				Main.mainController.getTooltip().setAnchorY(yPosition);

				Main.mainController.setTooltipContent("
Next Page
");

				Main.mainController.getTooltip().setAnchorX(xPosition);
				Main.mainController.getTooltip().setAnchorY(yPosition);

				TooltipUpdateThread.updateToolTip(xPosition,yPosition);
			}
		} else if (previousPage) {
			if (Main.game.getResponsePage() != 0) {

				Main.mainController.setTooltipSize(360, 60);

				double xPosition = ((MouseEvent) event).getScreenX() + 16 - 180;
				if (xPosition + 360 > Main.primaryStage.getX() + Main.primaryStage.getWidth() - 16)
					xPosition = Main.primaryStage.getX() + Main.primaryStage.getWidth() - 360 - 16;
				double yPosition = Main.primaryStage.getY() + Main.primaryStage.getHeight() - (34*(MainController.RESPONSE_COUNT/5) + 4) - Main.mainController.getTooltip().getHeight()
						- (Main.mainScene.getWindow().getHeight() - Main.mainScene.getHeight() - Main.mainScene.getY());

				Main.mainController.getTooltip().setAnchorX(xPosition);
				Main.mainController.getTooltip().setAnchorY(yPosition);

				Main.mainController.setTooltipContent("
Previous Page
");

				Main.mainController.getTooltip().setAnchorX(xPosition);
				Main.mainController.getTooltip().setAnchorY(yPosition);

				TooltipUpdateThread.updateToolTip(xPosition,yPosition);
			}

		} else {
			Response response = null;
			if(Main.game.getCurrentDialogueNode()!=null) {
				if (Main.game.getResponsePage() == 0) {
					response = Main.game.getCurrentDialogueNode().getResponse(Main.game.getResponseTab(), index);
				} else {
					if (index != 0) {
						response = Main.game.getCurrentDialogueNode().getResponse(Main.game.getResponseTab(), Main.game.getResponsePage() * MainController.RESPONSE_COUNT + index - 1);
					} else {
						response = Main.game.getCurrentDialogueNode().getResponse(Main.game.getResponseTab(), Main.game.getResponsePage() * MainController.RESPONSE_COUNT + MainController.RESPONSE_COUNT-1);
					}
				}
			}

			if (response != null) {
				tooltipSB.setLength(0);

				int boxHeight = 130;

				if(!response.hasRequirements()) {
					if(response instanceof ResponseSex) {
						if(((ResponseSex)response).isPlayerDom()) {
							tooltipSB.append("
Dominant Sex
");
						} else {
							tooltipSB.append("
Submissive Sex
");
						}
						boxHeight+=44;
						tooltipSB.append("
" + response.getTooltipText() + "
");

					} else if(response.isCombatHighlight()) {
						tooltipSB.append("
Combat
");
						boxHeight+=44;
						tooltipSB.append("
" + response.getTooltipText() + "
");

					} else if(response.getAssociatedCombatMove()!=null) {
						CombatMove move = response.getAssociatedCombatMove();
						boolean coreMove = Main.game.getPlayer().getEquippedMoves().contains(move);

						tooltipSB.append("
"+Util.capitaliseSentence(move.getName(0, Main.game.getPlayer()))+"
");
						boxHeight+=44;

						int cost = move.getAPcost(Main.game.getPlayer());
						int cooldown = move.getCooldown(Main.game.getPlayer());

						tooltipSB.append(
								"
"
									+""
									+(coreMove?cost:(cost-1)+"[style.colourBad(+1)]")
									+" AP"
								+ "
"
								+ "
"
									+ "<span style='color:"+(cooldown-(coreMove?0:1)<=0?Colour.GENERIC_MINOR_GOOD:Colour.GENERIC_MINOR_BAD).toWebHexString()+";'>"
									+(coreMove?cooldown:(cooldown-1)+"[style.colourBad(+1)]")
									+" turn"+(cooldown==1?"":"s")+" cooldown"
								+ "
");

						boxHeight+=36;

						tooltipSB.append("
" + response.getTooltipText() + "
");

						tooltipSB.append(
								"
"
										+ (coreMove
											?"This is one of your [style.colourMinorGood(core moves)], so there is no extra AP cost or cooldown time for using it!"
											:"This is [style.colourMinorBad(not one of your core moves)], so there is an extra cost of [style.colourBad(+1 AP)] and [style.colourBad(+1 turn cooldown)] for using it!")
								+"
");
						boxHeight+=54;

					} else {
						tooltipSB.append("
" + response.getTooltipText() + "
");
					}

				} else {
					if(response.isAvailable()) {
						if(response instanceof ResponseSex) {
							if(((ResponseSex)response).isPlayerDom()) {
								tooltipSB.append("
Dominant Sex (Available)
");
							} else {
								tooltipSB.append("
Submissive Sex (Available)
");
							}
						} else if(response.isCombatHighlight()) {
							tooltipSB.append("
Combat (Available)
");
						} else {
							tooltipSB.append("
Available
");
						}
						boxHeight+=44;

						if(response.getSexPace()!=null) {
							tooltipSB.append("
Sex Pace:"
									+ " "+Util.capitaliseSentence(response.getSexPace().getName())+"
");
							boxHeight+=44;
						}

						tooltipSB.append("
" + response.getTooltipText() + "
");

					} else if(response.isAbleToBypass()) {
						if(response instanceof ResponseSex) {
							if(((ResponseSex)response).isPlayerDom()) {
								tooltipSB.append("
Dominant Sex"
										+ " (Corruptive)
");
							} else {
								tooltipSB.append("
Submissive Sex"
										+ " (Corruptive)
");
							}
						} else if(response.isCombatHighlight()) {
							tooltipSB.append("
Combat (Corruptive)
");
						} else {
							tooltipSB.append("
Corruptive
");
						}
						boxHeight+=44;

						if(response.getSexPace()!=null) {
							tooltipSB.append("
Sex Pace:"
									+ " "+Util.capitaliseSentence(response.getSexPace().getName())+"
");
							boxHeight+=44;
						}

						tooltipSB.append("
" + response.getTooltipText() + "
");

					} else {
						if(response instanceof ResponseSex) {
							if(((ResponseSex)response).isPlayerDom()) {
								tooltipSB.append("
Dominant Sex"
										+ " (Unavailable)
");
							} else {
								tooltipSB.append("
Submissive Sex"
										+ " (Unavailable)
");
							}
						} else if(response.isCombatHighlight()) {
							tooltipSB.append("
Combat (Unavailable)
");
						} else {
							tooltipSB.append("
Unavailable
");
						}
						boxHeight+=44;

						if(response.getSexPace()!=null) {
							tooltipSB.append("
Sex Pace:"
									+ " "+Util.capitaliseSentence(response.getSexPace().getName())+"
");
							boxHeight+=44;
						}

						tooltipSB.append("
" + response.getTooltipText() + "
");
					}

					tooltipSB.append(
							"
"
									+ "Availability:"
									+response.getTooltipBlockingList()+response.getTooltipRequiredList()
							+"
");

					tooltipSB.append(
							"
"
									+response.getTooltipCorruptionBypassText()
							+"
");

					String extraSexInfo = response.getAdditionalSexActionInformationText();
					if(!extraSexInfo.isEmpty()) {
						tooltipSB.append(
								"
"
										+extraSexInfo
								+"
");
						boxHeight+=54;
					}

					boxHeight+=54;

					boxHeight+= 28 + ((response.lineHeight()+1)*18);
				}

				Main.mainController.setTooltipSize(360, boxHeight);

				double xPosition = ((MouseEvent) event).getScreenX() + 16 - 180;
				if (xPosition + 360 > Main.primaryStage.getX() + Main.primaryStage.getWidth() - 16)
					xPosition = Main.primaryStage.getX() + Main.primaryStage.getWidth() - 360 - 16;

				double yPosition = Main.primaryStage.getY() + Main.primaryStage.getHeight() - (34*(MainController.RESPONSE_COUNT/5) + 4) - boxHeight
						- (Main.mainScene.getWindow().getHeight() - Main.mainScene.getHeight() - Main.mainScene.getY());

				Main.mainController.getTooltip().setAnchorX(xPosition);
				Main.mainController.getTooltip().setAnchorY(yPosition);

				TooltipUpdateThread.updateToolTip(xPosition,yPosition);
				Main.mainController.setTooltipContent(UtilText.parse(tooltipSB.toString()));
			}

		}
	}

	public TooltipResponseDescriptionEventListener setIndex(int index) {
		this.index = index;

		nextPage = false;
		previousPage = false;
		return this;
	}

	public TooltipResponseDescriptionEventListener nextPage() {
		nextPage = true;
		previousPage = false;

		return this;
	}

	public TooltipResponseDescriptionEventListener previousPage() {
		nextPage = false;
		previousPage = true;

		return this;
	}

}


code:
package com.lilithsthrone.controller.eventListeners.tooltips;

import org.w3c.dom.events.Event;
import org.w3c.dom.events.EventListener;
import org.w3c.dom.events.MouseEvent;

import com.lilithsthrone.controller.MainController;
import com.lilithsthrone.main.Main;

/**
 * @since 0.1.61
 * @version 0.1.69
 * @author Innoxia
 */
public class TooltipResponseMoveEventListener implements EventListener {

	@Override
	public void handleEvent(Event event) {
		double xPosition = ((MouseEvent) event).getScreenX() + 16 - 180;
		if (xPosition + 360 > Main.primaryStage.getX() + Main.primaryStage.getWidth() - 16)
			xPosition = Main.primaryStage.getX() + Main.primaryStage.getWidth() - 360 - 16;

		Main.mainController.getTooltip().setAnchorX(xPosition);
		Main.mainController.getTooltip().setAnchorY(Main.primaryStage.getY() + Main.primaryStage.getHeight() - (34*(MainController.RESPONSE_COUNT/5) + 4) - Main.mainController.getTooltip().getHeight()
				- (Main.mainScene.getWindow().getHeight() - Main.mainScene.getHeight() - Main.mainScene.getY()));
	}
}


code:
package com.lilithsthrone.controller.xmlParsing;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.DOMException;
import org.w3c.dom.Document;

/**
 * @author BlazingMagpie@gmail.com (or ping BlazingMagpie in Discord)
 * @since 0.2.5.8
 * @version 0.2.12
 *
 *
Wrapper for org.w3c.dom.Element that's supposed to simplify mod support

 */
public class Element {

	private final org.w3c.dom.Element innerElement;//contains wrapped element
	private final String fileDirectory;//contains location of the file to show in case of error
	private final org.w3c.dom.Document document;//document the element is in, legacy

	/**
	 *

	 * Construct new instance with given {@linkplain  org.w3c.dom.Element} and
	 * location of XML document as String (used only for error reporting).

	 *

	 * Should only be made by other Element or {@link Element#getDocumentRootElement(java.io.File) }.

	 *
	 * @param w3cElement {@link org.w3c.dom.Element} to wrap
	 * @param fileDirectory String of a location of original file
	 */
	private Element(org.w3c.dom.Element w3cElement, String fileDirectory, org.w3c.dom.Document document) {
		innerElement = w3cElement;
		this.fileDirectory = fileDirectory;
		this.document = document;
	}

	/**
	 * Loads and prepares document for reading and returns root element, for example {@code <clothing>} in clothes mods.
	 *
	 * @param xmlFile File in XML format. Throws an exception with necessary details if something goes wrong. {@link XMLLoadException} for mode details.
	 *
	 * @return Element at the root of the document
	 * @throws XMLLoadException
	 */
	public static Element getDocumentRootElement(File xmlFile) throws XMLLoadException{
		try{
			String fileDirectory = xmlFile.getAbsolutePath();
			Document parsedDocument = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(xmlFile);
			parsedDocument.getDocumentElement().normalize();
			return new Element(parsedDocument.getDocumentElement(), fileDirectory, parsedDocument);

		} catch(Exception e){
			throw new XMLLoadException(e);
		}
	}

	public static Element getElement(org.w3c.dom.Element w3cElement, String fileDirectory, org.w3c.dom.Document document) throws XMLLoadException{
		try{
			return new Element(w3cElement, "", document);

		} catch(Exception e){
			throw new XMLLoadException(e);
		}
	}

	/**
	 *

	 * Returns tag name of the element

	 *
	 * @return Tag name as String, for example: {@code <element></element>}
	 * returns "element"
	 */
	public String getTagName() {
		return innerElement.getTagName();
	}

	/**
	 *

	 * Returns value of attribute requested by name
.
	 *
	 * @param name String name of the attribute to return value of
	 *
	 * @return Attribute as String, for example:
	 * {@code <element attribute = "value"></element>} returns "value". Returns an empty String if no such attribute was present.
	 */
	public String getAttribute(String name) {
		return innerElement.getAttribute(name);
	}

	/**
	 *

	 * Returns raw text inside the element.

	 *
	 *

	 * If text content size exceeds allowed by internal method, error will be
	 * output, but method will continue and return empty string.

	 *
	 * @return Text content as String, for example:
	 * {@code <element>Inner text</element>} returns "Inner text".
	 */
	public String getTextContent() {
		try {
			return innerElement.getTextContent();

		} catch (DOMException ex) {
			System.err.println(String.format("DOM exception: text content in element \"%s\" probably exceeds max allowed."
			+ "XML parsing will try to continue with empty text content",
			getTagName()));
			return "";
		}
	}

	/**
	 * Returns inner element. Used to support legacy code
	 *
	 * @return w3c DOM Element that wrapper contains
	 * @deprecated
	 */
	@Deprecated
	public org.w3c.dom.Element getInnerElement(){
		return innerElement;
	}

	/**
	 * Returns the document. Used for legacy code
	 *
	 * @return w3c DOM Document element is in.
	 * @deprecated
	 */
	@Deprecated
	public org.w3c.dom.Document getDocument(){
		return document;
	}

	/**
	 *

	 * Returns first child element with a given tag, throwing a
	 * {@link XMLMissingTagException} if no such tag found. Used when
	 * document is invalid without element.

	 *

	 * Thrown exception provides the information for which tag in which file
	 * causes the error, so only one catch is needed to get all of the
	 * details.

	 *
	 * @param tag String tag of the element to return.
	 *
	 * @return First element found with given tag
	 * @throws XMLMissingTagException
	 */
	public Element getMandatoryFirstOf(String tag) throws XMLMissingTagException {
		org.w3c.dom.Element firstElement = (org.w3c.dom.Element) innerElement.getElementsByTagName(tag).item(0);
		if (firstElement == null) {
			throw new XMLMissingTagException(tag, fileDirectory);//throw error if there's no elements found
		}
		return new Element(firstElement, fileDirectory, document);
	}

	/**
	 *

	 * Returns first child element with a given tag, or empty
	 * {@link java.util.Optional} if none are found.

	 *

	 * Use it when document is still valid if element is missing. Use
	 * {@link java.util.Optional#ifPresent(java.util.function.Consumer)} to
	 * create blocks that are executed only when element with a given tag is
	 * found, for example:

	 *

	 *{@code
	 * parentElement.getOptionalFirstOf("extraDescription").ifPresent(
	 *		(Element e) ->{
	 *		System.out.println(e.getTextContent);
	 *		}
	 * );}
	 *

	 *
	 * @param tag String tag of the element to return.
	 *
	 * @return First element found with given tag.
	 */
	public Optional<Element> getOptionalFirstOf(String tag) {
		org.w3c.dom.Element foundElement = (org.w3c.dom.Element) innerElement.getElementsByTagName(tag).item(0);
		if (foundElement == null) {
		    return Optional.empty(); //return empty Optional if there's no elements found
		}
		return Optional.of(new Element(foundElement, fileDirectory, document));
	}

	/**
	 *

	 * Returns iterable list of all child elements with a given tag

	 *
	 * @param tag String tag of elements to return.
	 *
	 * @return {@link java.util.List} of Elements with given tag.
	 */
	public List<Element> getAllOf(String tag) {
		org.w3c.dom.NodeList nl = innerElement.getElementsByTagName(tag);
		List<Element> returnList = new ArrayList<>();
		for (int i = 0; i < nl.getLength(); i++) {
			returnList.add(new Element((org.w3c.dom.Element) nl.item(i), this.fileDirectory, this.document));
		}
		return returnList;
	}

	public List<Element> getAll(){
		org.w3c.dom.NodeList nl = innerElement.getChildNodes();
		List<Element> returnList = new ArrayList<>();
		for (int i = 0; i < nl.getLength(); i++) {
			returnList.add(new Element((org.w3c.dom.Element) nl.item(i), this.fileDirectory, this.document));
		}
		return returnList;
	}
}


code:
package com.lilithsthrone.controller.xmlParsing;

import java.io.File;

/**
 * @author BlazingMagpie@gmail.com (or ping BlazingMagpie in Discord)
 * @since 0.2.5.8
 * @version 0.2.12
 *
 *
Exception thrown when XML file fails to load, with details for exact cause.

 */
public class XMLLoadException extends Exception {
	private static final long serialVersionUID = 1L;

	/**
	 * @param cause Actual exception that was thrown, for example SAXException or XMLMissingTagException.
	 */
	public XMLLoadException(Throwable cause){
		super("XML file failed to load. Cause: " + cause.getMessage(), cause);
	}

	public XMLLoadException(Throwable cause, File causedByFile){
		super("XML file ("+causedByFile.getParentFile().getName()+"/"+causedByFile.getName()+") failed to load. Cause: " + cause.getMessage(), cause);
	}
}


code:
package com.lilithsthrone.controller.xmlParsing;

/**
 * @author BlazingMagpie@gmail.com (or ping BlazingMagpie in Discord)
 * @since 0.2.5.8
 *
 *
Exception thrown when mandatory tag is missing, thus making XML mod invalid. If mod can still be loaded and used without a tag, use {@link Element#getOptionalFirstOf(java.lang.String)} instead

 */
public class XMLMissingTagException extends Exception {
	private static final long serialVersionUID = 1L;

	/**
	 *
Error that reports the missing tag and file in which missing tag was reported.

	 *
	 * @param tag String tag that was attempted to look for
	 * @param fileDirectory String location of the file which has missing tag
	 */
	public XMLMissingTagException(String tag, String fileDirectory){
		super(String.format("XML missing tag exception: \"%s\" tag is required, but not found in %s", tag, fileDirectory));
	}
}