code:
package com.lilithsthrone.game.dialogue;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import com.lilithsthrone.game.PropertyValue;
import com.lilithsthrone.game.character.CharacterUtils;
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.BodyPartType;
import com.lilithsthrone.game.character.body.valueEnums.BodyMaterial;
import com.lilithsthrone.game.character.fetishes.Fetish;
import com.lilithsthrone.game.character.gender.Gender;
import com.lilithsthrone.game.character.npc.NPC;
import com.lilithsthrone.game.character.npc.dominion.Brax;
import com.lilithsthrone.game.character.npc.dominion.Lilaya;
import com.lilithsthrone.game.character.npc.misc.GenericSexualPartner;
import com.lilithsthrone.game.character.persona.PersonalityTrait;
import com.lilithsthrone.game.character.quests.Quest;
import com.lilithsthrone.game.character.quests.QuestLine;
import com.lilithsthrone.game.character.race.RaceStage;
import com.lilithsthrone.game.character.race.Subspecies;
import com.lilithsthrone.game.combat.SpellSchool;
import com.lilithsthrone.game.dialogue.responses.Response;
import com.lilithsthrone.game.dialogue.responses.ResponseSex;
import com.lilithsthrone.game.dialogue.utils.BodyChanging;
import com.lilithsthrone.game.dialogue.utils.ParserCommand;
import com.lilithsthrone.game.dialogue.utils.ParserTarget;
import com.lilithsthrone.game.dialogue.utils.UtilText;
import com.lilithsthrone.game.inventory.InventorySlot;
import com.lilithsthrone.game.inventory.ItemTag;
import com.lilithsthrone.game.inventory.clothing.AbstractClothing;
import com.lilithsthrone.game.inventory.clothing.AbstractClothingType;
import com.lilithsthrone.game.inventory.clothing.ClothingType;
import com.lilithsthrone.game.inventory.enchanting.TFEssence;
import com.lilithsthrone.game.inventory.item.AbstractItemType;
import com.lilithsthrone.game.inventory.item.ItemType;
import com.lilithsthrone.game.inventory.weapon.AbstractWeaponType;
import com.lilithsthrone.game.inventory.weapon.WeaponType;
import com.lilithsthrone.game.sex.Sex;
import com.lilithsthrone.game.sex.SexAreaOrifice;
import com.lilithsthrone.game.sex.managers.universal.SMGeneric;
import com.lilithsthrone.main.Main;
import com.lilithsthrone.utils.BaseColour;
import com.lilithsthrone.utils.Colour;
import com.lilithsthrone.utils.Util;

/**
 * @since 0.1.0
 * @version 0.2.11
 * @author Innoxia
 */
public class DebugDialogue {

	public static final DialogueNode DEBUG_MENU = new DialogueNode("A powerful tool", "Open debug menu.", false) {

		@Override
		public String getContent() {
			return "
"
						+ "As you finish speaking the magic word, you suddenly hear a little thudding noise close behind you."
						+ " Spinning around, you see a small metallic device lying on the floor, which sort of resembles a t.v. remote from back in your old world."
					+ "
"

					+ "
"
						+ "You lean down and pick it up, and as you turn it over in your hands, you see a little label stuck to the back."
						+ " Someone's written a message on it, and you read the following:"
					+ "
"

					+ "
"
						+ "Hi [pc.name]!
"
						+ "It looks like you know about the magic debug code! Just to give you a warning, all the options here are really buggy!"
						+ " If you spawn in any clothing or items, just be aware that some of them aren't officially in the game just yet, so they may not work exactly as expected."
						+ " Thanks for playing!

"
						+ "~Innoxia~
"
					+ "
";
		}

		@Override
		public String getResponseTabTitle(int index) {
			if(index == 0) {
				return "Main";

			} else if(index == 1) {
				return "Stats";

			} else if(index == 2) {
				return "Misc.";

			} else if(index == 3) {
				return "Item view";

			} else if(index == 4) {
				return "Personality";
			}
			return null;
		}

		@Override
		public Response getResponse(int responseTab, int index) {
			if(index == 0) {
				return new Response("Back", "", DEBUG_MENU){
					@Override
					public DialogueNode getNextDialogue() {
						return Main.game.getDefaultDialogueNoEncounter();
					}
				};

			}

			if(responseTab==0) {
				if (index == 1) {
					return new Response("Open parser", "Test the parser.", PARSER);

				} else if (index == 2) {
					return new Response("Debug mode: ", "Unlocks enchanting and slavery without first having to do the associated quests.", DEBUG_MENU){
						@Override
						public String getTitle() {
							return "Debug mode: "+(Main.game.isDebugMode()?"[style.colourGood(ON)]":"[style.colourDisabled(OFF)]");
						}

						@Override
						public void effects() {
							Main.getProperties().setValue(PropertyValue.debugMode, !Main.game.isDebugMode());
						}
					};

				} else if (index == 3) {
					return new Response("Reveal Maps: ", "Reveals all map tiles.", DEBUG_MENU){
						@Override
						public String getTitle() {
							return "Reveal Maps: "+(Main.game.isMapReveal()?"[style.colourGood(ON)]":"[style.colourDisabled(OFF)]");
						}

						@Override
						public void effects() {
							Main.getProperties().setValue(PropertyValue.mapReveal, !Main.game.isMapReveal());
						}
					};

				} else if (index == 4) {
					return new Response("Reveal bodies: ", "When toggled on, clothing does not conceal inventory slots, and you'll know what all character's genitals look like without first having to see them.", DEBUG_MENU){
						@Override
						public String getTitle() {
							return "Reveal bodies: "+(Main.game.isConcealedSlotsReveal()?"[style.colourGood(ON)]":"[style.colourDisabled(OFF)]");
						}

						@Override
						public void effects() {
							Main.getProperties().setValue(PropertyValue.concealedSlotsReveal, !Main.game.isConcealedSlotsReveal());
						}
					};

				} else if (index == 6) {
					return new Response("Spawn Menu", "View the clothing, weapon, and item spawn menu.", SPAWN_MENU);

				} else if (index == 7) {
					return new Response("Transform", "Transform your body.", BodyChanging.BODY_CHANGING_CORE) {
						@Override
						public void effects() {
							BodyChanging.setTarget(Main.game.getPlayer(), true);
						}
					};

				} else if (index == 8) {
					return new Response("Set body material", "Set your body material.", BODY_PART_MATERIAL);

				} else if (index == 9) {
					return new Response("Race resets", "View the race reset options.", BODY_PART_RACE_RESET);

				} else if (index == 11) {
					return new Response(UtilText.formatAsMoney(100_000, "span"), "Add 100,000 flames.", DEBUG_MENU){
						@Override
						public void effects() {
							Main.game.getPlayer().incrementMoney(100_000);
						}
					};

				} else if (index == 12) {
					return new Response("+50 essences", "Add 50 arcane essences.", DEBUG_MENU){
						@Override
						public void effects() {
							for(TFEssence essence : TFEssence.values()) {
								Main.game.getPlayer().incrementEssenceCount(essence, 50, false);
							}
						}
					};

				}else if (index == 13) {
					return new Response("Very long action text for testing", "Very long action text for testing.", null);

				}

			} else if(responseTab==1) {
				if (index == 1) {
					return new Response("+500 xp", "Give yourself 500xp.", DEBUG_MENU){
						@Override
						public void effects() {
							Main.game.getPlayer().incrementExperience(500, false);

						}
					};

				} else if(index==2) {
					return new Response("+5 Physique", "", DEBUG_MENU){
						@Override
						public void effects() {
							Main.game.getPlayer().incrementAttribute(Attribute.MAJOR_PHYSIQUE, 5);
						}
					};

				} else if(index==3) {
					return new Response("+5 Arcane", "", DEBUG_MENU){
						@Override
						public void effects() {
							Main.game.getPlayer().incrementAttribute(Attribute.MAJOR_ARCANE, 5);
						}
					};

				} else if(index==4) {
					return new Response("+5 Corruption", "", DEBUG_MENU){
						@Override
						public void effects() {
							Main.game.getPlayer().incrementAttribute(Attribute.MAJOR_CORRUPTION, 5);
						}
					};

				} else if(index==5) {
					return new Response("Max all attributes", "", DEBUG_MENU){
						@Override
						public void effects() {
							Main.game.getPlayer().setAttribute(Attribute.MAJOR_PHYSIQUE, 100);
							Main.game.getPlayer().setAttribute(Attribute.MAJOR_ARCANE, 100);
							Main.game.getPlayer().setAttribute(Attribute.MAJOR_CORRUPTION, 100);
						}
					};

				}  else if(index==6) {
					return new Response("+1 Perk point", "", DEBUG_MENU){
						@Override
						public void effects() {
							Main.game.getPlayer().incrementPerkPoints(1);
						}
					};

				} else if(index==7) {
					return new Response("-5 Physique", "", DEBUG_MENU){
						@Override
						public void effects() {
							Main.game.getPlayer().incrementAttribute(Attribute.MAJOR_PHYSIQUE, -5);
						}
					};
				} else if(index==8) {
					return new Response("-5 Arcane", "", DEBUG_MENU){
						@Override
						public void effects() {
							Main.game.getPlayer().incrementAttribute(Attribute.MAJOR_ARCANE, -5);
						}
					};
				} else if(index==9) {
					return new Response("-5 Corruption", "", DEBUG_MENU){
						@Override
						public void effects() {
							Main.game.getPlayer().incrementAttribute(Attribute.MAJOR_CORRUPTION, -5);
						}
					};

				} else if (index == 11) {
					return new Response("Reset spells", "Resets all of your spells and upgrades, and removes all of your spell upgrade points.", DEBUG_MENU){
						@Override
						public void effects() {
							Main.game.getPlayer().resetSpells();
							Main.game.getPlayer().clearSpellUpgradePoints();

						}
					};

				} else if (index == 12) {
					return new Response("+10 Spell Points", "Add 10 spell points to each spell school.", DEBUG_MENU){
						@Override
						public void effects() {
							for(SpellSchool school : SpellSchool.values()) {
								Main.game.getPlayer().incrementSpellUpgradePoints(school, 10);
							}
						}
					};

				}

			} else if(responseTab==2) {
				if (index == 1) {
					return new Response("Test colours", "Test text for readability", COLOURS){
						@Override
						public void effects() {
							coloursSB = new StringBuilder("
");
							for (Colour c : Colour.values())
								coloursSB.append(c + ": Test text for readability.
");
							coloursSB.append("

");
							for (BaseColour bc : BaseColour.values())
								coloursSB.append(bc + ": Test text for readability.
");
							coloursSB.append("
");

						}
					};

				} else if (index == 2) {
					return new Response("Offspring", "View available offspring", OFFSPRING);

				} else if (index == 3) {
					return new Response("[style.boldBad(Month -)]", "Reduce current month by 1.", DEBUG_MENU){
						@Override
						public void effects() {
							Main.game.setStartingDateMonth(Main.game.getStartingDate().getMonth().minus(1));

						}
					};

				} else if (index == 4) {
						return new Response("[style.boldGood(Month +)]", "Increase current month by 1.", DEBUG_MENU){
							@Override
							public void effects() {
								Main.game.setStartingDateMonth(Main.game.getStartingDate().getMonth().plus(1));

							}
						};

				}
//				else if (index == 5) {
//					if(!Main.game.getPlayer().getLocationPlace().getPlaceType().equals(PlaceType.DOMINION_BACK_ALLEYS)) {
//						return new Response("Lumi test", "Lumi can only be spawned in alleyway tiles.", null);
//
//					} else if(!Main.game.getNonCompanionCharactersPresent().isEmpty()) {
//						return new Response("Lumi test", "Lumi can only be spawned into empty tiles!", null);
//
//					}  else if(Main.game.getCurrentWeather()==Weather.MAGIC_STORM) {
//						return new Response("Lumi test", "Lumi can not be spawned during an arcane storm.", null);
//					}
//					return new ResponseEffectsOnly("Lumi test", "Spawn Lumi to test her dialogue and scenes."){
//						@Override
//						public void effects() {
//							Main.game.setContent(new Response("", "", LumiDialogue.LUMI_APPEARS));
//						}
//					};
//
//				}
				else if (index == 6) {
					return new Response("Brax's revenge", "Brax cums in your vagina!", DEBUG_MENU){
						@Override
						public void effects() {
							Main.game.getPlayer().ingestFluid(Main.game.getNpc(Brax.class), Main.game.getNpc(Brax.class).getCum(), SexAreaOrifice.VAGINA, 1000);
						}
					};

				} else if (index == 7) {
					return new Response("Lilaya's hypocrisy", "Lilaya cums in your vagina!", DEBUG_MENU){
						@Override
						public void effects() {
							Main.game.getPlayer().ingestFluid(Main.game.getNpc(Lilaya.class), Main.game.getNpc(Lilaya.class).getCum(), SexAreaOrifice.VAGINA, 1000);
						}
					};

				} else if (index == 8) {
					return new Response("Lilaya's tests", "Automatically completes Lilaya's enchantment quest, making you able to enchant right away.", DEBUG_MENU){
						@Override
						public void effects() {
							if(!Main.game.getPlayer().isQuestCompleted(QuestLine.SIDE_ENCHANTMENT_DISCOVERY)){ //If the player hasn't completed the enchantment quest
								if(Main.game.getPlayer().hasQuest(QuestLine.SIDE_ENCHANTMENT_DISCOVERY)){ //But has started it
									Main.game.getTextEndStringBuilder().append(Main.game.getPlayer().setQuestProgress(QuestLine.SIDE_ENCHANTMENT_DISCOVERY, Quest.SIDE_UTIL_COMPLETE)); //Finish it
								}
								else{ //But hasn't started it
									Main.game.getTextEndStringBuilder().append(Main.game.getPlayer().startQuest(QuestLine.SIDE_ENCHANTMENT_DISCOVERY)); //Start the quest
									Main.game.getTextEndStringBuilder().append(Main.game.getPlayer().setQuestProgress(QuestLine.SIDE_ENCHANTMENT_DISCOVERY, Quest.SIDE_UTIL_COMPLETE)); //And finish it
								}
							}
						}
					};

				} else if (index == 9) {
					return new Response("+1 Epona stamp", "Gives you one stamp you can get by playing the breeder roulette.", DEBUG_MENU){
						@Override
						public void effects() {
							Main.game.getDialogueFlags().eponaStamps += 1;
							Main.game.getTextEndStringBuilder().append("Added 1 stamp, you now have "  + Main.game.getDialogueFlags().eponaStamps + " stamp(s)");
						}
					};

				} else if(index == 10){
					return new Response("Get slaver license", "Automatically completes the quest to get a slaver license. This will start the quest if you don't already have it, and finish it.", DEBUG_MENU){
						@Override
						public void effects(){
							if(!Main.game.getPlayer().isHasSlaverLicense()){ //If the player doesn't have the slaver license
								if(Main.game.getPlayer().hasQuest(QuestLine.SIDE_SLAVERY)){ //But has started the quest to get it
									Main.game.getTextEndStringBuilder().append(Main.game.getPlayer().setQuestProgress(QuestLine.SIDE_SLAVERY, Quest.SIDE_UTIL_COMPLETE)); //Finish it.
								}
								else{ //But hasn't started the quest to get it
									Main.game.getTextEndStringBuilder().append(Main.game.getPlayer().startQuest(QuestLine.SIDE_SLAVERY)); //Start the quest
									Main.game.getDialogueFlags().values.add(DialogueFlagValue.finchIntroduced); //Introduce Finch
									Main.game.getTextEndStringBuilder().append(Main.game.getPlayer().setQuestProgress(QuestLine.SIDE_SLAVERY, Quest.SIDE_UTIL_COMPLETE)); //And finish it
								}
							}
						}
					};

				} else if(index == 11){
					return new Response("Centaur", "A wild centaur appears! (Please only use this on a completely neutral tile, as it will probably break things otherwise.)", CENTAUR_SEX){
						@Override
						public void effects(){
							NPC target = new GenericSexualPartner(Gender.getGenderFromUserPreferences(false,  false), Main.game.getPlayer().getWorldLocation(), Main.game.getPlayer().getLocation(), false, (s)->s!=Subspecies.CENTAUR);
							try {
								Main.game.addNPC(target, false);
								Main.game.setActiveNPC(target);
							} catch (Exception e) {
								e.printStackTrace();
							}
						}
					};
				}

			} else if(responseTab == 3) {
				if(index==1) {
					return new Response("All",
							"View icons and ids of all the clothing, weapons, and items in the game. You can also spawn these items by clicking on their icons. Warning: Very sluggish and slow to load.",
							ITEM_VIEWER) {
						@Override
						public void effects() {
							viewItemVariablesReset();
							viewAll = true;
						}
					};

				} else if(index==2) {
					return new Response("Items",
							"View icons and ids of all the items in the game. You can also spawn these items by clicking on their icons. Warning: Very sluggish and slow to load.",
							ITEM_VIEWER) {
						@Override
						public void effects() {
							viewItemVariablesReset();
						}
					};

				} else if(index==3) {
					return new Response("Weapons",
							"View icons and ids of all the weapons in the game. You can also spawn these items by clicking on their icons. Warning: May be very sluggish and slow to load.",
							ITEM_VIEWER) {
						@Override
						public void effects() {
							viewItemVariablesReset();
							itemViewSlot = InventorySlot.WEAPON_MAIN_1;
						}
					};

				} else if(index==4) {
					return new Response("All clothing",
							"View icons and ids of all the clothing in the game. You can also spawn these items by clicking on their icons. Warning: Very sluggish and slow to load.",
							ITEM_VIEWER) {
						@Override
						public void effects() {
							viewItemVariablesReset();
							viewAllClothing = true;
						}
					};

				} else {
					List<InventorySlot> clothingSlots = new ArrayList<>(Arrays.asList(InventorySlot.values()));
					clothingSlots.remove(InventorySlot.WEAPON_MAIN_1);
					clothingSlots.remove(InventorySlot.WEAPON_MAIN_2);
					clothingSlots.remove(InventorySlot.WEAPON_MAIN_3);
					clothingSlots.remove(InventorySlot.WEAPON_OFFHAND_1);
					clothingSlots.remove(InventorySlot.WEAPON_OFFHAND_2);
					clothingSlots.remove(InventorySlot.WEAPON_OFFHAND_3);

					if(index-5 < clothingSlots.size()) {
						InventorySlot is = clothingSlots.get(index-5);
						return new Response(Util.capitaliseSentence(is.getName()),
								"View icons and ids of all the clothing in the the slot '"+is.getName()+"'. You can also spawn these items by clicking on their icons. Warning: May be very sluggish and slow to load.",
								ITEM_VIEWER) {
							@Override
							public void effects() {
								viewItemVariablesReset();
								itemViewSlot = is;
							}
						};
					}
				}
			} else if(responseTab==4) {
				List<PersonalityTrait> pt = Arrays.asList(PersonalityTrait.values());
				for(int i=1; i<=pt.size();i++) {
					if(i==index) {
						PersonalityTrait perTr = pt.get(index-1);
						boolean hasTrait = Main.game.getPlayer().hasPersonalityTrait(perTr);
						return new Response(
								hasTrait
									?""+Util.capitaliseSentence(perTr.getName())+""
									:Util.capitaliseSentence(perTr.getName()),
									(hasTrait
										?"[style.boldGood(Owned!)] "
										:"[style.colourMinorBad(Not owned!)] ")
									+perTr.getDescription(Main.game.getPlayer(), true, true),
								DEBUG_MENU) {
							@Override
							public void effects() {
								if(hasTrait) {
									Main.game.getPlayer().removePersonalityTrait(perTr);
								} else {
									Main.game.getPlayer().addPersonalityTrait(perTr);
								}
							}
						};
					}
				}
			}

			return null;
		}
	};
	private static StringBuilder coloursSB;
	public static final DialogueNode COLOURS = new DialogueNode("", "", false) {

		@Override
		public String getContent() {
			return coloursSB.toString();
		}

		@Override
		public String getResponseTabTitle(int index) {
			return DEBUG_MENU.getResponseTabTitle(index);
		}

		@Override
		public Response getResponse(int responseTab, int index) {
			return DEBUG_MENU.getResponse(responseTab, index);
		}
	};

	private static NPC activeOffspring = null;

	public static final DialogueNode OFFSPRING = new DialogueNode("", "", false) {

		@Override
		public String getContent() {
			UtilText.nodeContentSB.setLength(0);

			for(NPC npc : Main.game.getOffspring(true)) {
				boolean isBorn = true;
				if(npc.getMother().getPregnantLitter()!=null && npc.getMother().getPregnantLitter().getOffspring().contains(npc.getId())) {
					isBorn = false;
				}
				UtilText.nodeContentSB.append((isBorn?"":"(Not born yet) ")+""+npc.getName(true)+" ("+npc.getSubspecies().getName(npc)+")"
						+ " M:"+npc.getMother().getName(true)+" F:"+npc.getFather().getName(true)+"
");
			}
			if(activeOffspring!=null) {
				for(Fetish f : activeOffspring.getFetishes(true)) {
					UtilText.nodeContentSB.append("
[style.boldSex(Fetish:)] "+f.getName(activeOffspring));
				}
				UtilText.nodeContentSB.append(
						"
" + activeOffspring.getDescription()
						+"
" + activeOffspring.getBodyDescription());
			}

			return UtilText.nodeContentSB.toString();
		}

		@Override
		public Response getResponse(int responseTab, int index) {
			if (index == 0) {
				return new Response("Back", "", DEBUG_MENU);

			} else if(index-1 < Main.game.getOffspring(true).size()) {
				return new Response(Main.game.getOffspring(true).get(index-1).getName(true), "View the character page for this offspring.", OFFSPRING) {
					@Override
					public void effects() {
						activeOffspring = Main.game.getOffspring(true).get(index-1);
						for(CoverableArea ca : CoverableArea.values()) {
							activeOffspring.setAreaKnownByCharacter(ca, Main.game.getPlayer(), true);
						}
					}
				};

			} else {
				return null;
			}
		}
	};

	public static List<AbstractClothingType> clothingTotal = new ArrayList<>();
	public static InventorySlot activeSlot = null;
	public static ItemTag itemTag = null;
	public static int spawnCount = 1;
	public static List<AbstractItemType> itemsTotal = new ArrayList<>();
	public static List<AbstractWeaponType> weaponsTotal = new ArrayList<>();
	static {
		clothingTotal.addAll(ClothingType.getAllClothing());
		clothingTotal.removeIf((c) -> c.getDefaultItemTags().contains(ItemTag.REMOVE_FROM_DEBUG_SPAWNER) || c.getDefaultItemTags().contains(ItemTag.HIDDEN_IN_DEBUG_SPAWNER));

		weaponsTotal.addAll(WeaponType.getAllWeapons());
		weaponsTotal.removeIf((w) -> w.getItemTags().contains(ItemTag.REMOVE_FROM_DEBUG_SPAWNER) || w.getItemTags().contains(ItemTag.HIDDEN_IN_DEBUG_SPAWNER));

		itemsTotal.addAll(ItemType.getAllItems());
		itemsTotal.removeIf((i) -> i.getItemTags().contains(ItemTag.REMOVE_FROM_DEBUG_SPAWNER) || i.getItemTags().contains(ItemTag.HIDDEN_IN_DEBUG_SPAWNER));

	}
	private static StringBuilder inventorySB = new StringBuilder();
	public static final DialogueNode SPAWN_MENU = new DialogueNode("Spawn Menu", "Access the spawn menu.", false) {

		@Override
		public String getHeaderContent() {
			inventorySB.setLength(0);

			inventorySB.append("
");

			inventorySB.append(
					"
"
						+ (itemTag==ItemTag.HIDDEN_IN_DEBUG_SPAWNER
							?"[style.boldExcellent(Hidden Spawn Menu)]"
							:(activeSlot==null
								?"Spawn Item"
								:(activeSlot.isWeapon()
									? "Spawn Weapon ("+Util.capitaliseSentence(activeSlot==InventorySlot.WEAPON_MAIN_1?"Melee":"Ranged")+")"
									: "Spawn Clothing ("+Util.capitaliseSentence(activeSlot.getName())+")")))
					+"
");

			int count=0;
			inventorySB.append("
");
			if(itemTag==ItemTag.HIDDEN_IN_DEBUG_SPAWNER) {
				for(AbstractClothingType c : ClothingType.getAllClothing()) {
					if(c.getDefaultItemTags().contains(ItemTag.HIDDEN_IN_DEBUG_SPAWNER)) {
						inventorySB.append("
"
								+ "
"
									+c.getSVGImage(
											c.getEquipSlots().get(0),
											c.getAvailablePrimaryColours().get(0),
											c.getAvailableSecondaryColours().isEmpty()?null:c.getAvailableSecondaryColours().get(0),
											c.getAvailableTertiaryColours().isEmpty()?null:c.getAvailableTertiaryColours().get(0),
											null, null, null, null)
								+"
"
								+ "

"
							+ "
");
					}
				}
				for(AbstractWeaponType weaponType : WeaponType.getAllWeapons()) {
					if(weaponType.getItemTags().contains(ItemTag.HIDDEN_IN_DEBUG_SPAWNER)) {
						inventorySB.append("
"
								+ "
"+weaponType.getSVGImage(
										weaponType.getAvailableDamageTypes().get(0),
										weaponType.getAvailablePrimaryColours().isEmpty()?null:weaponType.getAvailablePrimaryColours().get(0),
										weaponType.getAvailableSecondaryColours().isEmpty()?null:weaponType.getAvailableSecondaryColours().get(0))
								+"
"
								+ "

"
							+ "
");
					}
				}
				for(AbstractItemType itemType : ItemType.getAllItems()) {
					if(itemType.getItemTags().contains(ItemTag.HIDDEN_IN_DEBUG_SPAWNER)) {
						inventorySB.append("
"
								+ "
"+itemType.getSVGString()+"
"
								+ "

"
							+ "
");
					}
				}

			} else if(activeSlot == null) {
				for(AbstractItemType itemType : itemsTotal) {
					if((itemTag==null
							&& (!itemType.getItemTags().contains(ItemTag.BOOK)
							&& !itemType.getItemTags().contains(ItemTag.ESSENCE)
							&& !itemType.getItemTags().contains(ItemTag.SPELL_BOOK)
							&& !itemType.getItemTags().contains(ItemTag.SPELL_SCROLL)))
							|| (itemTag!=null
								&& (itemType.getItemTags().contains(itemTag)
										|| (itemTag==ItemTag.SPELL_BOOK && itemType.getItemTags().contains(ItemTag.SPELL_SCROLL))))) {
						inventorySB.append("
"
												+ "
"+itemType.getSVGString()+"
"
												+ "

"
											+ "
");
					}
					count++;
				}

			} else if(activeSlot.isWeapon()) {
				for(AbstractWeaponType weaponType : weaponsTotal) {
					if((weaponType.isMelee() && activeSlot==InventorySlot.WEAPON_MAIN_1)
							|| (!weaponType.isMelee() && activeSlot==InventorySlot.WEAPON_OFFHAND_1)) {
						inventorySB.append("
"
												+ "
"+weaponType.getSVGImage(
														weaponType.getAvailableDamageTypes().get(0),
														weaponType.getAvailablePrimaryColours().isEmpty()?null:weaponType.getAvailablePrimaryColours().get(0),
														weaponType.getAvailableSecondaryColours().isEmpty()?null:weaponType.getAvailableSecondaryColours().get(0))
												+"
"
												+ "

"
											+ "
");
						count++;
					}
				}

			} else {
				for(AbstractClothingType clothingType : clothingTotal) {
					if(clothingType.getEquipSlots().contains(activeSlot)) {
						inventorySB.append("
"
												+ "
"
													+clothingType.getSVGImage(
															clothingType.getEquipSlots().get(0),
															clothingType.getAvailablePrimaryColours().get(0),
															clothingType.getAvailableSecondaryColours().isEmpty()?null:clothingType.getAvailableSecondaryColours().get(0),
															clothingType.getAvailableTertiaryColours().isEmpty()?null:clothingType.getAvailableTertiaryColours().get(0),
															null, null, null, null)
												+"
"
												+ "

"
											+ "
");
						count++;
					}
				}
			}

			// Fill space:
			for (int i = count; i <48; i++) {
				inventorySB.append("

");
			}
			inventorySB.append("
"
					+ "
");

			inventorySB.append("
");
			for(InventorySlot slot : InventorySlot.values()) {
				if(slot!=InventorySlot.WEAPON_MAIN_2
						&& slot!=InventorySlot.WEAPON_MAIN_3
						&& slot!=InventorySlot.WEAPON_OFFHAND_2
						&& slot!=InventorySlot.WEAPON_OFFHAND_3) {
					inventorySB.append("
"
							+(slot == InventorySlot.WEAPON_MAIN_1
								?"Melee"
								:(slot == InventorySlot.WEAPON_OFFHAND_1
										?"Ranged"
										:Util.capitaliseSentence(slot.getName())))
							+"
");
				}
			}
			inventorySB.append("
Items
");
			inventorySB.append("
Essences
");
			inventorySB.append("
Books
");
			inventorySB.append("
Spells
");
			inventorySB.append("
Hidden
");
			inventorySB.append("
");

			return inventorySB.toString();
		}

		@Override
		public String getContent() {
			return "";
		}

		@Override
		public String getResponseTabTitle(int index) {
			return DEBUG_MENU.getResponseTabTitle(index);
		}

		@Override
		public Response getResponse(int responseTab, int index) {
			return DEBUG_MENU.getResponse(responseTab, index);
		}
	};

	private static void viewItemVariablesReset() {
		viewAll = false;
		viewAllClothing = false;
		itemViewSlot = null;
	}

	private static boolean viewAll = false;
	private static boolean viewAllClothing = false;
	private static InventorySlot itemViewSlot = null;

	public static final DialogueNode ITEM_VIEWER = new DialogueNode("", "", false) {

		@Override
		public String getContent() {
			inventorySB.setLength(0);

			int width = 33;
			if(Main.primaryStage.getWidth()>=1900) {
				width = 25;
			}
			int imgWidth = 15;

			if(!viewAllClothing && (viewAll || itemViewSlot == null)) {
				inventorySB.append("
"
						+ "
Total items: "+itemsTotal.size()+"
");
				for(AbstractItemType itemType : itemsTotal) {
					if((itemTag==null
							&& (!itemType.getItemTags().contains(ItemTag.BOOK)
							&& !itemType.getItemTags().contains(ItemTag.ESSENCE)
							&& !itemType.getItemTags().contains(ItemTag.SPELL_BOOK)
							&& !itemType.getItemTags().contains(ItemTag.SPELL_SCROLL)))
							|| (itemTag!=null
								&& (itemType.getItemTags().contains(itemTag)
										|| (itemTag==ItemTag.SPELL_BOOK && itemType.getItemTags().contains(ItemTag.SPELL_SCROLL))))) {
						inventorySB.append("
"
												+ "
"
													+ "
"+itemType.getSVGString()+"
"
													+ "

"
												+ "
"
												+ ItemType.getItemToIdMap().get(itemType)
											+ "
");
					}
				}
				inventorySB.append("
");
			}

			if(viewAll || (itemViewSlot!=null && itemViewSlot.isWeapon())) {
				inventorySB.append("
"
						+ "
Total weapons: "+weaponsTotal.size()+"
");
				for(AbstractWeaponType weaponType : weaponsTotal) {
					inventorySB.append("
"
											+ "
"
												+ "
"+weaponType.getSVGImage(
														weaponType.getAvailableDamageTypes().get(0),
														weaponType.getAvailablePrimaryColours().isEmpty()?null:Util.randomItemFrom(weaponType.getAvailablePrimaryColours()),
														weaponType.getAvailableSecondaryColours().isEmpty()?null:Util.randomItemFrom(weaponType.getAvailableSecondaryColours()))
												+"
"
												+ "

"
											+ "
"
											+ WeaponType.getIdFromWeaponType(weaponType)
										+ "
");
				}
				inventorySB.append("
");
			}

			if(viewAll || viewAllClothing) {
				inventorySB.append("
"
						+ "
Total clothing: "+clothingTotal.size()+"
");
				for(AbstractClothingType clothingType : clothingTotal) {
					inventorySB.append("
"
										+ "
"
											+ "
"
												+clothingType.getSVGImage(
														clothingType.getEquipSlots().get(0),
														clothingType.getAvailablePrimaryColours().get(0),
														clothingType.getAvailableSecondaryColours().isEmpty()?null:Util.randomItemFrom(clothingType.getAvailableSecondaryColours()),
														clothingType.getAvailableTertiaryColours().isEmpty()?null:Util.randomItemFrom(clothingType.getAvailableTertiaryColours()),
														null, null, null, null)
											+"
"
											+ "

"
										+ "
"
										+ ClothingType.getIdFromClothingType(clothingType)
									+ "
");
				}
				inventorySB.append("
");

			} else if(itemViewSlot!=null && !itemViewSlot.isWeapon()) {
				List<AbstractClothingType> clothingToDisplay = clothingTotal.stream().filter((c) -> c.getEquipSlots().get(0)==itemViewSlot).collect(Collectors.toList());
				inventorySB.append("
"
						+ "
Total '"+itemViewSlot.getName()+"' slot clothing: "+clothingToDisplay.size()+"
");
				for(AbstractClothingType clothingType : clothingToDisplay) {
					inventorySB.append("
"
							+ "
"
								+ "
"
									+clothingType.getSVGImage(
											clothingType.getEquipSlots().get(0),
											clothingType.getAvailablePrimaryColours().get(0),
											clothingType.getAvailableSecondaryColours().isEmpty()?null:Util.randomItemFrom(clothingType.getAvailableSecondaryColours()),
											clothingType.getAvailableTertiaryColours().isEmpty()?null:Util.randomItemFrom(clothingType.getAvailableTertiaryColours()),
											null, null, null, null)
								+"
"
								+ "

"
							+ "
"
							+ ClothingType.getIdFromClothingType(clothingType)
						+ "
");
				}
				inventorySB.append("
");
			}

			return inventorySB.toString();
		}

		@Override
		public String getResponseTabTitle(int index) {
			return DEBUG_MENU.getResponseTabTitle(index);
		}

		@Override
		public Response getResponse(int responseTab, int index) {
			return DEBUG_MENU.getResponse(responseTab, index);
		}
	};

	public static final DialogueNode BODY_PART_MATERIAL = new DialogueNode("Set body material", "Set body material.", false) {

		@Override
		public String getContent() {
			return "Choose a material type.";
		}

		@Override
		public Response getResponse(int responseTab, int index) {
			if (index != 0 && index < BodyMaterial.values().length+1) {
				return new Response(Util.capitaliseSentence(BodyMaterial.values()[index - 1].getName()), "Set your body to be made out of "+BodyMaterial.values()[index - 1].getName()+".", BODY_PART_MATERIAL){
					@Override
					public void effects() {
						Main.game.getTextStartStringBuilder().append(Main.game.getPlayer().setBodyMaterial(BodyMaterial.values()[index - 1]));
					}
				};

			} else if (index == 0) {
				return new Response("Back", "", DEBUG_MENU);

			} else {
				return null;
			}
		}
	};

	public static final DialogueNode BODY_PART_RACE_RESET = new DialogueNode("Reset body", "Set race.", false) {

		@Override
		public String getContent() {
			return "
"
						+ "Select one of the races to reset your body to the default values of that race. (i.e. Regenerate your current body as that of a different race.)"
					+ "
"
					+ "
"
						+ "[style.colourTfPartial(Partial)]: Sets body to human, with selected race's antennae, eyes, ears, hair, horns, tail, and wings.
"
						+ "[style.colourTfMinor(Minor)]: Same as partial, but also includes ass, breasts, penis, and vagina.
"
						+ "[style.colourTfLesser(Lesser)]: Same as minor, but also includes arms and legs.
"
						+ "[style.colourTfGreater(Greater)]: Sets all parts to the race's.
"
					+ "
";
		}

		@Override
		public String getResponseTabTitle(int index) {
			if(index == 0) {
				return "[style.colourTfPartial(Partial)]";

			} else if(index == 1) {
				return "[style.colourTfMinor(Minor)]";

			} else if(index == 2) {
				return "[style.colourTfLesser(Lesser)]";

			} else if(index == 3) {
				return "[style.colourTfGreater(Greater)]";
			}
			return null;
		}

		@Override
		public Response getResponse(int responseTab, int index) {
			if (index != 0 && index < Subspecies.values().length) {
				String name = Subspecies.values()[index - 1].getName(null);
				return new Response(
						Util.capitaliseSentence(name),
						"Set your body as that of "+UtilText.generateSingularDeterminer(name)+" "+name+".",
						BODY_PART_RACE_RESET){
					@Override
					public void effects() {
						if(Subspecies.values()[index - 1]==Subspecies.HALF_DEMON) {
							Main.game.getPlayer().setSubspeciesOverride(null);
							Main.game.getPlayer().setBody(
									CharacterUtils.generateHalfDemonBody(Main.game.getPlayer(), Main.game.getPlayer().getGender(), Subspecies.HUMAN, false),
									false);
							System.out.println(Main.game.getPlayer().getSubspeciesOverride());

						} else {
							CharacterUtils.reassignBody(
									Main.game.getPlayer(),
									Main.game.getPlayer().getBody(),
									Main.game.getPlayer().getGender(),
									Subspecies.values()[index - 1],
									responseTab==0
										?RaceStage.PARTIAL
										:(responseTab==1
											?RaceStage.PARTIAL_FULL
											:(responseTab==2
												?RaceStage.LESSER
												:RaceStage.GREATER)),
									false);
						}
						Main.game.getTextEndStringBuilder().append(
								"
"
									+ "[style.boldTfGeneric(Transformed:)] You are now "+UtilText.generateSingularDeterminer(name)+" "+name+"!"
								+ "
");
					}
				};

			} else if (index == 0) {
				return new Response("Back", "", DEBUG_MENU);

			} else {
				return null;
			}
		}
	};

	public static final DialogueNode CLOTHING_COLLAGE = new DialogueNode("Clothing collage", "Clothing collage.", false) {
		/**
		 *
		 */

		@Override
		public String getContent() {
			return clothingCollage();
		}

		@Override
		public Response getResponse(int responseTab, int index) {
			if (index == 0) {
				return new Response("Back", "", DEBUG_MENU);

			} else {
				return null;
			}
		}
	};

	private static StringBuilder clothingCollageSB;

	private static String clothingCollage() {
		clothingCollageSB = new StringBuilder("
");

		for (AbstractClothingType c : ClothingType.getAllClothing()) {
			AbstractClothing ac = AbstractClothingType.generateClothing(c);
			clothingCollageSB.append("
" + ac.getSVGString() + "
");
		}

		clothingCollageSB.append("
");

		return clothingCollageSB.toString();
	}

	private static String parsedText = "";
	private static String rawText = "";
	private static String xmlFileText = "res/txt/ENTER_PATH.xml";
	public static final DialogueNode PARSER = new DialogueNode("Parser", "", true) {

		@Override
		public String getHeaderContent() {
			return ("
"
					+ "Please at least skim over the help page before viewing the 'commands' pages! (The number of commands could be off-putting if you're not aware of how simple they really are.)"
					+ "
"

					+ "
"
						+ "
"
							+ "
XML test (enter full path, including .xml):
"
							+ "
"
							+ "<form style='display:inline-block; width:100%; padding:0; margin:0; text-align:center;'><input type='text' id='xmlTest' style='width:50%;' placeholder='res/txt/...' value='"+xmlFileText+"'></form>"
						+ "
"
					+ "
"

					+ "
"
						+ "This parser accepts html formatting."
					+ "
"

					+ "
"
						+ "Parse:"
					+ "
"
					+ "<form style='padding:0;margin:0;text-align:center;'>"
					+ "<textarea id='parseInput' name='Text1' style='width:760px;height:200px;'>"+rawText+"</textarea>"
					+ "</form>");
		}

		@Override
		public String getContent() {
			return  "
"
					+ "Output:"
					+ "
"
					+ "
"+parsedText+"
";
		}

		@Override
		public Response getResponse(int responseTab, int index) {
			if (index == 1) {
				return new Response("Parse!", "Parse the text you've entered.", PARSER){
					@Override
					public void effects() {
						rawText = (String) Main.mainController.getWebEngine().executeScript("document.getElementById('parseInput').value");
						parsedText = UtilText.parse(rawText);
					}
				};

			} else if (index == 2) {
				return new Response("Help", "", PARSER_HELP);

			} else if (index == 3) {
				return new Response("Targets", "", PARSER_TARGETS);

			} else if (index == 4) {
				return new Response("Commands", "", PARSER_COMMANDS_NEAT);

			} else if (index == 5) {
				return new Response("Commands list", "", PARSER_COMMANDS);

			} else if (index == 6) {
				return new Response("Load example 1", "", PARSER){
					@Override
					public void effects() {
						rawText = "You see [lilaya.name] sitting at one of the tables in [lilaya.herHis] lab, playing around with an inert demonstone."
								+ " [rose.name] is sitting beside [lilaya.herPro], gazing up lovingly into [lilaya.namePos] [lilaya.eyes+] as [rose.her] [rose.tail+] gently swishes back and forth behind [rose.herPro]."
								+ "\n

\n"
								+ "Unable to concentrate with [lilaya.her] slave hovering so close at hand, [lilaya.name] places the demonstone down onto the table, before wrapping [lilaya.her] [lilaya.arms+] around [rose.namePos] back and pulling"
								+ " [rose.herPro] into [lilaya.her] lap. [rose.name] lets out a happy little cry as [rose.her] master finally starts giving [rose.herPro] the attention [rose.she] was craving, and as [lilaya.namePos] hand slips down"
								+ " under [rose.her] dress, [rose.she] starts moaning in delight.";
					}
				};

			} else if (index == 7) {
				return new Response("Load example 2", "", PARSER){
					@Override
					public void effects() {
						rawText = "[brax.name] paces back and forth in [brax.herHis] office, growling softly to [brax.himself], [brax.speech(Hrmph... My new poster still hasn't arrived...)]";
					}
				};

			} else if (index == 11) {
				return new Response("Xml test",
						"Parse every dialogue entry in the file whose path you've specified in order to check for errors. Lilaya, Brax, Rose, Ralph, Nyan, and Zaranix, are passed in as parser targets.",
						PARSER){
					@Override
					public void effects() {
						xmlFileText = ((String) Main.mainController.getWebEngine().executeScript("document.getElementById('xmlTest').value")).replaceAll("\u200b", "");
						parsedText = Main.game.runXmlTest(xmlFileText);
					}
				};

			} else if (index == 0) {
				return new Response("Back", "", DEBUG_MENU);

			} else {
				return null;
			}
		}

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

	public static final DialogueNode PARSER_HELP = new DialogueNode("Innoxia's super fun and interesting guide to parsing", "", true) {

		/*
		 * I've seen String concatenation... String concatenation that you've seen.
		 * But you have no right to call me a bad programmer.
		 * You have a right to ridicule my code. You have a right to do that... but you have no right to judge me.
		 *
		 * It's impossible for words to describe what is necessary to those who do not know what String concatenation means.
		 * String concatenation... String concatenation has a face... and you must make a friend of String concatenation. String concatenation and html parsing are your friends. If they are not, then they are enemies to be feared.
		 */
		@Override
		public String getHeaderContent() {
			return  "
You put in the input, and it returns a nice output!
"

					+ "
"
					+ "
Input:

"
					+"Everything is parsed using square brackets, split into the following pattern:
"
					+"[target.command]
"
					+"or, for the few special commands that require arguments:
"
					+"[target.command"
							+ "(arguments)]
"
					+"or, for parsing as a script:
"
					+"[#command]
"
					+"or, for parsing as a script which suppresses output:
"
					+"[##command]
"
					+ "(This means that the command will be executed, but no String output will be displayed.)"
					+ "
"

					+ "
"
					+"An example of use in a sentence would be:

"
					+"As you start to read Innoxia's tedious parsing documentation, [lilaya.name] steps up behind you and wraps [lilaya.her] [lilaya.tail+] around your [pc.leg]."
					+" Leaning in over your shoulder, [lilaya.she] groans, [lilaya.speech(Oh my God. This is so boring, [#pc.getName(true)]!)]'

"
					+ "parses to:

"
					+ UtilText.parse("As you start to read Innoxia's tedious parsing documentation, [lilaya.name] steps up behind you and wraps [lilaya.her] [lilaya.tail+] around your [pc.leg]."
							+ " Leaning in over your shoulder, [lilaya.she] groans, [lilaya.speech(Oh my God. This is so boring, [#pc.getName(true)]!)]")
					+ "
"
					+ "
"

					+"
Target tag:
"
					+"
"
					+"The target of a command is an NPC's name, or 'pc' for the player character. Target tags are case-insensitive. (i.e. pc is treated the same as PC, pC, or Pc)
"
					+" If an unrecognised name is passed, the output will read 'INVALID_TARGET_NAME'.
"
					+" Currently accepted target tags can be viewed in the 'Targets' page.
"
					+ "e.g.:
"
					+ "[lilaya.command]
"
					+ "[pc.command(arguments)]
"
					+ "[npc.command]"
					+ "
"
					+ "
"

					+"
Command and argument tags:
"
					+"
"
					+"Command tags determine what output is returned. They come in two varieties; with and without arguments.
"
					+"Arguments are passed inside brackets that follow the command, ignoring any spaces that you may insert.
"
					+ "i.e. [pc.command(arguments)] is the same as [pc.command   (arguments)].
"
					+"Command tags are only case-sensitive for the first letter. (i.e. command is treated the same as cOMMAND, cOmMaNd, or commanD)
"
					+ "Arguments are specific to each command, and you'll have to refer to the command documentation to find out what arguments a command takes. (Don't worry, there aren't many that take arguments.)
"
					+ "e.g.:
"
					+ "[pc.speech(Hello reader!)] outputs "+UtilText.parsePlayerSpeech("Hello reader!")+""
					+ ""
					+ "
"

					+"
"
					+ "Command modifier (a_ an_)
"
					+"You may insert 'a_' or 'an_' to automatically generate the appropriate pronoun before an argument. (It's your choice if you prefer a_ or an_, they both work in exactly the same way.)
"
					+"e.g.:
"
					+ "[npc.height] outputs 'tall'
"
					+ "[npc.a_height] outputs 'a tall'
"
					+ "[npc.an_height] also outputs 'a tall'

"

					+"For some body part names, this provides a little more complexity.
"
					+ "e.g.:
"
					+ "[npc.arms] outputs 'wings'
"
					+ "[npc.a_arms] outputs 'a pair of wings'
"
					+ "[npc.an_arms] also outputs 'a pair of wings'

"
					+ "
"

					+"
"
					+ "Command modifier (Capitalisation)
"
					+"Most commands are able to apply capitalisation. The ones that don't, such as numeric output commands, will still happily take a capitalised command, but capitalisation won't be applied.
"
					+ "To capitalise an output, all you have to do is capitalise the first letter of the command name.
"
					+"e.g.:
"
					+ "[npc.height] outputs 'tall'
"
					+ "[npc.hEIGHT] also outputs 'tall' (I'm sure nobody would ever do this...)
"
					+ "[npc.Height] outputs 'Tall'
"
					+ "[npc.a_height] outputs 'a tall'
"
					+ "[npc.A_height]  outputs 'A tall'
"
					+ "[npc.a_Height] also outputs 'A tall'

"

					+"
"
					+ "Command modifier (+ D)
"
					+"Most commands that return a name are able to apply additional randomised descriptors. (You can check to see what commands accept '+' and 'D' modifiers in the 'Commands' page.)
"
					+ "To apply additional descriptors to the returned output, all you have to do is add a '+', 'd', or 'D' to the end of the command.
"
					+ "This works in combination with 'a_ an_' and 'Capitalisation' modifiers.
"
					+"e.g.:
"
					+ "[npc.arms] outputs 'wings'
"
					+ "[npc.arms+] outputs 'feathered wings'
"
					+ "[npc.armsD] also outputs 'feathered wings'
"
					+ "[npc.armsd] also outputs 'feathered wings'
"
					+ "[npc.ArmsD] outputs 'Feathered wings'
"
					+ "[npc.a_arms+] outputs 'a pair of feathered wings'
"
					+ "[npc.A_arms+]  outputs 'A pair of feathered wings'

"

					+"Some outputs have more randomisation than others.
"
					+ "e.g.:
"
					+ "[npc.pussy] outputs 'slit'
"
					+ "[npc.pussy] outputs 'cunt'
"
					+ "[npc.pussy+] outputs 'wet pussy'
"
					+ "[npc.pussy+] outputs 'tentacle-lined twat'

"
					+ "
"
					+ "
"

					+ "
Conclusion:

"
					+"
"
					+"Valid command syntax:
"
					+"[target.command]
"
					+"or
"
					+"[target.command"
							+ "(arguments)]

"
					+ "
"

					+ "
Examples:

"
					+ "[brax.name]"
						+ " leans back in [brax.his]"
						+ " chair, wondering what happened to [arthur.name]"
						+ " after [brax.he]"
						+ " handed [[arthur.him]] over to Scarlett.
"
					+ "outputs:
"
					+ "'Brax leans back in his chair, wondering what happened to Arthur after he handed him over to Scarlett.'

"

					+ "[rose.name]"
						+ " leans back in "
						+ "[rose.herHuis]"
						+ " chair, letting out a sigh as "
						+ "[rose.shyeHe]"
						+ " takes a sip of [rose.name] coffee.
"
					+ "outputs:
"
					+ UtilText.parse("[rose.name] leans back in [rose.herHuis] chair, letting out a sigh as [rose.shyeHe] takes a sip of [rose.name] coffee.")+"
"
					+ "Note: Typos will cause the parsing system to return an invalid command string, whereas"
							+ " incorrect commands (such as typing .name instead of .herHis) will not throw an error!

"

					+ "[lilaya.name]"
						+ " storms up to [innoxia.name],"
						+ " shouting angrily in response to finding out that"
						+ " [lilaya.her] sex scenes haven't been fixed yet,"
								+ " [lilaya.speech"
										+ "(What the hell are you doing Innoxia?! You said my scenes were going to be re-written weeks ago!)]
"
					+ "outputs:
"
					+ "'Lilaya storms up to Innoxia, shouting angrily in response to finding out that her sex scenes haven't been fixed yet, "
						+UtilText.parseSpeech("What the hell are you doing Innoxia?! You said my scenes were going to be re-written weeks ago!", Main.game.getNpc(Lilaya.class))+"'"

					+ "
";
		}

		@Override
		public String getContent(){
			return "";
		}

		@Override
		public Response getResponse(int responseTab, int index) {
			if (index == 1) {
				return new Response("Parser", "", PARSER);

			} else if (index == 2) {
				return new Response("Help", "", null);

			} else if (index == 3) {
				return new Response("Targets", "", PARSER_TARGETS);

			} else if (index == 4) {
				return new Response("Commands", "", PARSER_COMMANDS_NEAT);

			} else if (index == 5) {
				return new Response("Commands list", "", PARSER_COMMANDS);

			} else if (index == 0) {
				return new Response("Back", "", DEBUG_MENU);

			} else {
				return null;
			}
		}

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

	public static final DialogueNode PARSER_TARGETS = new DialogueNode("Parser", "", true) {

		@Override
		public String getHeaderContent() {
			UtilText.nodeContentSB.setLength(0);

			UtilText.nodeContentSB.append("
Here are a list of accepted targets, for use in the parsing syntax:
"
					+ "[target.command"
							+ "(arguments)]
");

			for(ParserTarget character : ParserTarget.values()) {
				UtilText.nodeContentSB.append("
"
						+"
");

				boolean first=true;
				for(String s : character.getTags()) {
					UtilText.nodeContentSB.append((first?"":" | ") +""+s+"");
					first = false;
				}

				UtilText.nodeContentSB.append("
"
						+character.getDescription()
						+ "
");
			}

			return UtilText.nodeContentSB.toString();
		}

		@Override
		public String getContent() {
			return  "";
		}

		@Override
		public Response getResponse(int responseTab, int index) {
			if (index == 1) {
				return new Response("Parser", "", PARSER);

			} else if (index == 2) {
				return new Response("Help", "", PARSER_HELP);

			} else if (index == 3) {
				return new Response("Targets", "", null);

			} else if (index == 4) {
				return new Response("Commands", "", PARSER_COMMANDS_NEAT);

			} else if (index == 5) {
				return new Response("Commands list", "", PARSER_COMMANDS);

			} else if (index == 0) {
				return new Response("Back", "", DEBUG_MENU);

			} else {
				return null;
			}
		}

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

	public static final DialogueNode PARSER_COMMANDS = new DialogueNode("Parser", "", true) {

		@Override
		public String getHeaderContent() {
			UtilText.nodeContentSB.setLength(0);

			UtilText.nodeContentSB.append("
Here are a list of accepted commands, for use in the parsing syntax:
"
						+ "[target.command"
						+ "(arguments)]
"
						+ "
"
							+ "Please don't be intimidated by the number of commands!"
							+ " The vast majority of them are automatically generated 'standard' variations for body parts, following a (hopefully) intuitive naming system."
						+ "
");

			int count=1;
			for(ParserCommand command : UtilText.commandsList) {
				UtilText.nodeContentSB.append("
"
						+ "
"
						+ ""+String.format("%03d.", count)+" "+command.getTags().get(0)+"");

				if(command.getTags().size()>1) {
					for(int i = 1; i<command.getTags().size(); i++)
						UtilText.nodeContentSB.append(" | "+command.getTags().get(i)+"");
				}

				UtilText.nodeContentSB.append("
");
				count++;
			}

			return UtilText.nodeContentSB.toString();
		}

		@Override
		public String getContent() {
			return  "";
		}

		@Override
		public Response getResponse(int responseTab, int index) {
			if (index == 1) {
				return new Response("Parser", "", PARSER);

			} else if (index == 2) {
				return new Response("Help", "", PARSER_HELP);

			} else if (index == 3) {
				return new Response("Targets", "", PARSER_TARGETS);

			} else if (index == 4) {
				return new Response("Commands", "", PARSER_COMMANDS_NEAT);

			} else if (index == 5) {
				return new Response("Commands list", "", null);

			} else if (index == 0) {
				return new Response("Back", "", DEBUG_MENU);

			} else {
				return null;
			}
		}

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

	public static final DialogueNode PARSER_COMMANDS_NEAT = new DialogueNode("Parser", "", true) {

		@Override
		public String getHeaderContent() {
			UtilText.nodeContentSB.setLength(0);

			UtilText.nodeContentSB.append("
Here are a list of accepted commands, for use in the parsing syntax:
"
						+ "[target.command"
						+ "(arguments)]
"
						+ "
"
							+ "Please don't be intimidated by the number of commands!"
							+ " The vast majority of them are automatically generated 'standard' variations for body parts, following a (hopefully) intuitive naming system."
						+ "
");

			int count = 1;
			for(BodyPartType bpt : BodyPartType.values()) {
				UtilText.nodeContentSB.append(""
						+ "("
							+String.format("%03d", count)+" - "+String.format("%03d", count+UtilText.commandsMap.get(bpt).size()-1)+") "+Util.capitaliseSentence(bpt.getName())+"");
				for(ParserCommand command : UtilText.commandsMap.get(bpt)) {
					UtilText.nodeContentSB.append("
"
							+ "
"
							+ ""+String.format("%03d.", count)+" "+command.getTags().get(0)+"");

					if(command.getTags().size()>1) {
						for(int i = 1; i<command.getTags().size(); i++)
							UtilText.nodeContentSB.append(" | "+command.getTags().get(i)+"");
					}

					UtilText.nodeContentSB.append("
"
							+(command.getArguments()==""?"No arguments":""+command.getArguments()+"")+"
"
							+(command.isAllowsCapitalisation()?"Capitalisation":"Capitalisation")
								+ " | " +(command.isAllowsPronoun()?"Pronouns":"Pronouns")
								+"
"
							+command.getDescription()+"
"
							+"Examples:
"
							+ command.getExampleBeforeParse("lilaya", (command.getArguments()==""?"":command.getArgumentExample()))+" -> "
								+UtilText.parse("[lilaya."+command.getTags().get(0)+(command.getArguments()==""?"":"("+command.getArgumentExample()+")")+"]")+"
"

							+ command.getExampleBeforeParse("brax", (command.getArguments()==""?"":command.getArgumentExample()))+" -> "
								+UtilText.parse("[brax."+command.getTags().get(0)+(command.getArguments()==""?"":"("+command.getArgumentExample()+")")+"]")+"
"

							+ command.getExampleBeforeParse("kate", (command.getArguments()==""?"":command.getArgumentExample()))+" -> "
								+UtilText.parse("[kate."+command.getTags().get(0)+(command.getArguments()==""?"":"("+command.getArgumentExample()+")")+"]")
							+"
");

					count++;
				}

				UtilText.nodeContentSB.append("");
			}

			return UtilText.nodeContentSB.toString();
		}

		@Override
		public String getContent() {
			return  "";
		}

		@Override
		public Response getResponse(int responseTab, int index) {
			if (index == 1) {
				return new Response("Parser", "", PARSER);

			} else if (index == 2) {
				return new Response("Help", "", PARSER_HELP);

			} else if (index == 3) {
				return new Response("Targets", "", PARSER_TARGETS);

			} else if (index == 4) {
				return new Response("Commands", "", null);

			} else if (index == 5) {
				return new Response("Commands list", "", PARSER_COMMANDS);

			} else if (index == 0) {
				return new Response("Back", "", DEBUG_MENU);

			} else {
				return null;
			}
		}

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

	public static final DialogueNode POST_SEX_2KOMA = new DialogueNode("", "", true) {
		@Override
		public String getContent() {
			if(Sex.isDom(Main.game.getPlayer())) {
				GameCharacter target = Sex.getSubmissiveParticipants(false).entrySet().iterator().next().getKey();
				return UtilText.parseFromXMLFile("misc/misc", "POST_SEX_2KOMA", target);
			} else {
				GameCharacter target = Sex.getDominantParticipants(false).entrySet().iterator().next().getKey();
				return UtilText.parseFromXMLFile("misc/misc", "POST_SEX_2KOMA_AS_SUB", target);
			}
		}

		@Override
		public Response getResponse(int responseTab, int index) {
			if(index==1) {
				if(Sex.isDom(Main.game.getPlayer())) {
					return new Response("Continue", "Now that you've put this bitch in [npc.her] place, you can continue with what you were doing...", Main.game.getDefaultDialogueNoEncounter());
				} else {
					return new Response("Continue", "Now that you've been put in your place like the bitch you are, you can continue with what you were doing...", Main.game.getDefaultDialogueNoEncounter());
				}
			}
			return null;
		}
	};

	public static final DialogueNode CENTAUR_SEX = new DialogueNode("", "", true) {
		@Override
		public String getContent() {
			NPC centaur = Main.game.getActiveNPC();
			return UtilText.parseFromXMLFile("misc/misc", "A_WILD_CENTAUR_APPEARS", centaur);
		}

		@Override
		public Response getResponse(int responseTab, int index) {
			NPC centaur = Main.game.getActiveNPC();

			if(index==1) {
				return new ResponseSex(
						UtilText.parse(centaur, "Dom [npc.herHim]"),
						UtilText.parse(centaur, "Take the dominant role and fuck this [npc.race]."),
						true,
						false,
						new SMGeneric(
								Util.newArrayListOfValues(Main.game.getPlayer()),
								Util.newArrayListOfValues(centaur),
								Main.game.getPlayer().getCompanions(),
								null),
						DebugDialogue.POST_SEX_CENTAUR,
						"
"
							+ "Deciding that you want to dominate this [npc.race] who just magically appeared before you, you step up to [npc.herHim] and growl,"
							+ " [pc.speech(It's time to put you in your place!)]"
						+ "
");

			} else if(index==2) {
				return new ResponseSex(
						UtilText.parse(centaur, "Submit to [npc.herHim]"),
						UtilText.parse(centaur, "Take the submissive role and let this [npc.race] fuck you."),
						true,
						false,
						new SMGeneric(
								Util.newArrayListOfValues(centaur),
								Util.newArrayListOfValues(Main.game.getPlayer()),
								null,
								Main.game.getPlayer().getCompanions()),
						DebugDialogue.POST_SEX_CENTAUR,
						"
"
							+ "Deciding that you want to get dominated by this [npc.race] who just magically appeared before you, you step up to [npc.herHim] and plead,"
							+ " [pc.speech(It's time for you to put me in my place!)]"
						+ "
");

			} else if(index==0) {
				return new Response("Nevermind", UtilText.parse(centaur, "Decide not to do anything with this [npc.race], and instead just continue with what you were doing..."), Main.game.getDefaultDialogueNoEncounter()) {
					@Override
					public void effects() {
						Main.game.banishNPC(centaur);
					}
				};
			}
			return null;
		}
	};

	public static final DialogueNode POST_SEX_CENTAUR = new DialogueNode("", "", true) {
		@Override
		public String getContent() {
			if(Sex.isDom(Main.game.getPlayer())) {
				GameCharacter target = Sex.getSubmissiveParticipants(false).entrySet().iterator().next().getKey();
				return UtilText.parseFromXMLFile("misc/misc", "POST_SEX_CENTAUR", target);
			} else {
				GameCharacter target = Sex.getDominantParticipants(false).entrySet().iterator().next().getKey();
				return UtilText.parseFromXMLFile("misc/misc", "POST_SEX_CENTAUR_AS_SUB", target);
			}
		}

		@Override
		public Response getResponse(int responseTab, int index) {
			if(index==1) {
				NPC centaur = Main.game.getActiveNPC();
				if(Sex.isDom(Main.game.getPlayer())) {
					return new Response("Continue", UtilText.parse(centaur, "Now that you've put this [npc.race] in [npc.her] place, you can continue with what you were doing..."), Main.game.getDefaultDialogueNoEncounter()) {
						@Override
						public void effects() {
							Main.game.banishNPC(centaur);
						}
					};
				} else {
					return new Response("Continue", UtilText.parse(centaur, "Now that you've been put in your place by this [npc.race], you can continue with what you were doing..."), Main.game.getDefaultDialogueNoEncounter()) {
						@Override
						public void effects() {
							Main.game.banishNPC(centaur);
						}
					};
				}
			}
			return null;
		}
	};

}


code:
package com.lilithsthrone.game.dialogue;

/**
 * @since 0.1.89
 * @version 0.3.3.10
 * @author Innoxia
 */
public enum DialogueFlagValue {

	quickTrade,
	stormTextUpdateRequired,
	hasSnowedThisWinter,

	// Essence reactions:
	jinxedClothingDiscovered,
	essencePostCombatDiscovered,
	essenceOrgasmDiscovered,
	essenceBottledDiscovered,

	// Misc.:
	foundHappiness,
	visitedSubmission,
	mommyFound,

	daddyFound,
	rudeToDaddy,
	flirtingWithDaddy,
	talkedWithDaddy,
	daddySendingReward,

	// Gym:
	gymIntroduced,
	gymHadTour,
	gymIsMember,

	// Introductions:
	angelIntroduced,
	angelsOfficeIntroduced,
	bunnyIntroduced,
	loppyIntroduced,
	ashleyIntroduced,
	ralphIntroduced,
	nyanIntroduced,
	kateIntroduced,
	vickyIntroduced,
	vanessaIntroduced,

	roxyIntroduced,
	axelIntroduced,
	eponaIntroduced,
	elizabethIntroduced,

	// Red-light district:
	prostitutionLicenseObtained,

	// City hall:
	vanessaHelped,
	vanessaDailyHelped,
	vanessaDailyMassage,
	vanessaMassaged,
	vanessaFucked,
	vanessaAskedAboutCatalogue,
	vanessaAskedAboutSolitary,

	// Shopping arcade:
	ralphAskedAboutHundredKisses,

	nyanTalkedTo,
	nyanComplimented,
	nyanFlirtedWith,
	nyanKissed,
	nyanMakeOut,
	nyanSex,
	nyanGift,

	supplierDepotDoorUnlocked,
	suppliersEncountered,
	suppliersTriedConvincing,

	ashleySexToysDiscovered,
	ashleyAttitude,

	reactedToKatePregnancy,

	// Lilaya's Home:
	knowsDate,
	lilayaDateTalk,
	auntHomeJustEntered,
	hadSexWithLilaya,
	lilayaCondomBroke,
	reactedToPregnancyLilaya,
	waitingOnLilayaPregnancyResults,
	waitingOnLilayaBirthNews,
	essenceExtractionKnown,
	roseToldOnYou,
	lilayaReactedToPlayerAsDemon,

	readBook1,
	readBook2,
	readBook3,
	readBook4,

	arthursPackageObtained,

	givenLilayaPresent1,
	givenLilayaPresent2,
	givenLilayaPresent3,

	// Enforcer HQ:
	braxEncountered,
	accessToEnforcerHQ,
	braxTransformedPlayer,
	seenBraxAfterQuest,
	feminisedBrax,
	bimbofiedBrax,
	candiHarpyTransformation,

	// Harpy Nests:
	hasHarpyNestAccess,
	bimboEncountered,
	bimboPacified,
	dominantEncountered,
	dominantPacified,
	nymphoEncountered,
	nymphoPacified,
	punishedByAlexa,

	// Slaver Alley:
	finchIntroduced,
	statueTruthRevealed,

	// Zaranix:
	zaranixDiscoveredHome,
	zaranixMaidsHostile,
	zaranixKnockedOnDoor,
	zaranixKickedDownDoor,

	zaranixAmberSubdued,
	zaranixKatherineSubdued,
	zaranixKellySubdued,

	amberRepeatEncountered,
	katherineRepeatEncountered,
	kellyRepeatEncountered,
	zaranixRepeatEncountered,

	// Lumi:
	lumiMet,
	lumiDisabled,
	lumiPromisedDinner,

	// Slime Queen's Tower:
	slimeGuardsIntroduced,
	slimeGuardsBluffed,
	slimeGuardsDefeated,
	slimeRoyalGuardIntroduced,
	slimeRoyalGuardDefeated,
	slimeRoyalGuardDefeatReacted,
	slimeQueenHelped,
	slimeQueenConvinced,
	slimeQueenForced,

	// Gambling Den:
	axelMentionedVengar,
	axelExplainedVengar,
	roxyAddicted,
	playedPregnancyRouletteAsMother,
	playedPregnancyRouletteAsBreeder,

	// Nightlife:
	julesIntroduced,
	suckedJulesCock,
	passedJules,
	kalahariIntroduced,
	kalahariWantsSex,
	krugerIntroduced,

	// Submission:
	claireAskedTeleportation,
	claireWarning,
	clairePadsInvestigated,
	claireEnclosureEscaped,
	claireObtainedLightningGlobe,
	claireAskedWarehouseEscape,

	impCitadelEncountered,
	impCitadelArcanistEncountered,
	impCitadelArcanistAcceptedTF,
	impCitadelTreasurySearched,
	impCitadelLaboratorySearched,

	impCitadelPrisonerMale,
	impCitadelPrisonerFemale,
	impCitadelPrisonerAlpha,

	impFortressAlphaGuardsPacified,
	impFortressAlphaBossEncountered,
	impFortressAlphaPacified,
	impFortressAlphaDefeated,

	impFortressFemalesGuardsPacified,
	impFortressFemalesBossEncountered,
	impFortressFemalesPacified,
	impFortressFemalesDefeated,

	impFortressMalesGuardsPacified,
	impFortressMalesBossEncountered,
	impFortressMalesPacified,
	impFortressMalesDefeated,

	impFortressDemonBossEncountered,
	impFortressDemonDefeated,
	impFortressDemonImpsDefeated,

	elizabethAskedAboutUniforms,
	elizabethAskedAboutSurname,

	lyssiethQuestionAsked1,
	lyssiethQuestionAsked2,
	lyssiethQuestionAsked3,
	lyssiethQuestionAsked4,
	lyssiethQuestionAsked5,

	meraxisRepeatDemonTF,

	// Rat warrens:
	ratWarrensEntry,
	ratWarrensEntryWhore,
	ratWarrensHostile,

	ratWarrensClearedLeft,
	ratWarrensClearedCentre,
	ratWarrensClearedRight,

	;
}

repo_name: klorpa/Pedo

license: unknown


code:
package com.lilithsthrone.game.dialogue;

import java.util.HashSet;
import java.util.Set;

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

import com.lilithsthrone.game.Game;
import com.lilithsthrone.game.character.CharacterUtils;
import com.lilithsthrone.game.character.GameCharacter;
import com.lilithsthrone.game.character.npc.NPC;
import com.lilithsthrone.game.occupantManagement.SlaveJob;
import com.lilithsthrone.main.Main;
import com.lilithsthrone.utils.Util;
import com.lilithsthrone.utils.Vector2i;
import com.lilithsthrone.utils.XMLSaving;

/**
 * @since 0.1.0
 * @version 0.3.5
 * @author Innoxia
 */
public class DialogueFlags implements XMLSaving {

	public Set<DialogueFlagValue> values;

	public long ralphDiscountStartTime;
	public int ralphDiscount;
	public int scarlettPrice;
	public int eponaStamps;
	public long kalahariBreakStartTime;
	public long daddyResetTimer;
	public long candiSexTimer;
	public long ralphSexTimer;

	public long impFortressAlphaDefeatedTime;
	public long impFortressDemonDefeatedTime;
	public long impFortressFemalesDefeatedTime;
	public long impFortressMalesDefeatedTime;

	public int impCitadelImpWave;

	// Amount of dialogue choices you can make before offspring interaction ends:
	public int offspringDialogueTokens = 2;

	// Reindeer event related flags:
	private Set<String> reindeerEncounteredIDs = new HashSet<>();
	private Set<String> reindeerWorkedForIDs = new HashSet<>();
	private Set<String> reindeerFuckedIDs = new HashSet<>();

	// Enforcer warehouse guards defeated:
	public Set<String> warehouseDefeatedIDs = new HashSet<>();

	// Storage tiles checked:
	public Set<Vector2i> supplierStorageRoomsChecked = new HashSet<>();

	private String slaveTrader;

	private String managementCompanion;
	private SlaveJob slaveryManagerJobSelected;

	public DialogueFlags() {
		values = new HashSet<>();

		slaveTrader = null;

		managementCompanion = null;
		slaveryManagerJobSelected = SlaveJob.IDLE;

		ralphDiscountStartTime = -1;
		kalahariBreakStartTime = -1;
		daddyResetTimer = -1;
		candiSexTimer = -1;
		ralphSexTimer = -1;

		ralphDiscount = 0;

		eponaStamps = 0;

		scarlettPrice = 15000;

		impFortressAlphaDefeatedTime
			= impFortressDemonDefeatedTime
			= impFortressFemalesDefeatedTime
			= impFortressMalesDefeatedTime
			= -50000;

		impCitadelImpWave = 0;
	}

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

		CharacterUtils.createXMLElementWithValue(doc, element, "ralphDiscountStartTime", String.valueOf(ralphDiscountStartTime));
		CharacterUtils.createXMLElementWithValue(doc, element, "ralphDiscount", String.valueOf(ralphDiscount));
		CharacterUtils.createXMLElementWithValue(doc, element, "scarlettPrice", String.valueOf(scarlettPrice));
		CharacterUtils.createXMLElementWithValue(doc, element, "eponaStamps", String.valueOf(eponaStamps));
		CharacterUtils.createXMLElementWithValue(doc, element, "kalahariBreakStartTime", String.valueOf(kalahariBreakStartTime));
		CharacterUtils.createXMLElementWithValue(doc, element, "daddyResetTimer", String.valueOf(daddyResetTimer));

		CharacterUtils.createXMLElementWithValue(doc, element, "impFortressAlphaDefeatedTime", String.valueOf(impFortressAlphaDefeatedTime));
		CharacterUtils.createXMLElementWithValue(doc, element, "impFortressDemonDefeatedTime", String.valueOf(impFortressDemonDefeatedTime));
		CharacterUtils.createXMLElementWithValue(doc, element, "impFortressFemalesDefeatedTime", String.valueOf(impFortressFemalesDefeatedTime));
		CharacterUtils.createXMLElementWithValue(doc, element, "impFortressMalesDefeatedTime", String.valueOf(impFortressMalesDefeatedTime));

		CharacterUtils.createXMLElementWithValue(doc, element, "impCitadelImpWave", String.valueOf(impCitadelImpWave));

		CharacterUtils.createXMLElementWithValue(doc, element, "offspringDialogueTokens", String.valueOf(offspringDialogueTokens));
		CharacterUtils.createXMLElementWithValue(doc, element, "slaveTrader", slaveTrader);
		CharacterUtils.createXMLElementWithValue(doc, element, "slaveryManagerSlaveSelected", managementCompanion);

		Element valuesElement = doc.createElement("dialogueValues");
		element.appendChild(valuesElement);
		for(DialogueFlagValue value : values) {
			CharacterUtils.createXMLElementWithValue(doc, valuesElement, "dialogueValue", value.toString());
		}

		saveSet(element, doc, reindeerEncounteredIDs, "reindeerEncounteredIDs");
		saveSet(element, doc, reindeerWorkedForIDs, "reindeerWorkedForIDs");
		saveSet(element, doc, reindeerFuckedIDs, "reindeerFuckedIDs");

		saveSet(element, doc, warehouseDefeatedIDs, "warehouseDefeatedIDs");

		Element supplierStorageRoomsCheckedElement = doc.createElement("supplierStorageRoomsChecked");
		element.appendChild(supplierStorageRoomsCheckedElement);
		for(Vector2i value : supplierStorageRoomsChecked) {
			Element location = doc.createElement("location");
			supplierStorageRoomsCheckedElement.appendChild(location);
			CharacterUtils.addAttribute(doc, location, "x", String.valueOf(value.getX()));
			CharacterUtils.addAttribute(doc, location, "y", String.valueOf(value.getY()));
		}

		return element;
	}

	public static DialogueFlags loadFromXML(Element parentElement, Document doc) {
		DialogueFlags newFlags = new DialogueFlags();

		newFlags.ralphDiscountStartTime = Long.valueOf(((Element)parentElement.getElementsByTagName("ralphDiscountStartTime").item(0)).getAttribute("value"));
		newFlags.ralphDiscount = Integer.valueOf(((Element)parentElement.getElementsByTagName("ralphDiscount").item(0)).getAttribute("value"));
		newFlags.scarlettPrice = Integer.valueOf(((Element)parentElement.getElementsByTagName("scarlettPrice").item(0)).getAttribute("value"));

		newFlags.offspringDialogueTokens = Integer.valueOf(((Element)parentElement.getElementsByTagName("offspringDialogueTokens").item(0)).getAttribute("value"));
		newFlags.slaveTrader = ((Element)parentElement.getElementsByTagName("slaveTrader").item(0)).getAttribute("value");
		newFlags.managementCompanion = ((Element)parentElement.getElementsByTagName("slaveryManagerSlaveSelected").item(0)).getAttribute("value");

		try {
			newFlags.eponaStamps = Integer.valueOf(((Element)parentElement.getElementsByTagName("eponaStamps").item(0)).getAttribute("value"));
		} catch(Exception ex) {
		}

		try {
			newFlags.kalahariBreakStartTime = Long.valueOf(((Element)parentElement.getElementsByTagName("kalahariBreakStartTime").item(0)).getAttribute("value"));
		} catch(Exception ex) {
		}

		try {
			newFlags.daddyResetTimer = Long.valueOf(((Element)parentElement.getElementsByTagName("daddyResetTimer").item(0)).getAttribute("value"));
		} catch(Exception ex) {
		}

		try {
			if(!Main.isVersionOlderThan(Game.loadingVersion, "0.2.11.5")) {
				newFlags.impFortressAlphaDefeatedTime = Long.valueOf(((Element)parentElement.getElementsByTagName("impFortressAlphaDefeatedTime").item(0)).getAttribute("value"));
				newFlags.impFortressDemonDefeatedTime = Long.valueOf(((Element)parentElement.getElementsByTagName("impFortressDemonDefeatedTime").item(0)).getAttribute("value"));
				newFlags.impFortressFemalesDefeatedTime = Long.valueOf(((Element)parentElement.getElementsByTagName("impFortressFemalesDefeatedTime").item(0)).getAttribute("value"));
				newFlags.impFortressMalesDefeatedTime = Long.valueOf(((Element)parentElement.getElementsByTagName("impFortressMalesDefeatedTime").item(0)).getAttribute("value"));
			}
		} catch(Exception ex) {
		}

		try {
			newFlags.impCitadelImpWave = Integer.valueOf(((Element)parentElement.getElementsByTagName("impCitadelImpWave").item(0)).getAttribute("value"));
		} catch(Exception ex) {
		}

		for(int i=0; i<((Element) parentElement.getElementsByTagName("dialogueValues").item(0)).getElementsByTagName("dialogueValue").getLength(); i++){
			Element e = (Element) ((Element) parentElement.getElementsByTagName("dialogueValues").item(0)).getElementsByTagName("dialogueValue").item(i);

			try {
				newFlags.values.add(DialogueFlagValue.valueOf(e.getAttribute("value")));
			} catch(Exception ex) {
			}
		}

		if(Main.isVersionOlderThan(Game.loadingVersion, "0.2.6.1")) {
			newFlags.values.remove(DialogueFlagValue.axelIntroduced);
			newFlags.values.remove(DialogueFlagValue.roxyIntroduced);
			newFlags.values.remove(DialogueFlagValue.eponaIntroduced);
		}

		if(Main.isVersionOlderThan(Game.loadingVersion, "0.2.11.5")) { // Add defeated flags so that the fortress will reset.
			newFlags.values.add(DialogueFlagValue.impFortressAlphaDefeated);
			newFlags.values.add(DialogueFlagValue.impFortressDemonDefeated);
			newFlags.values.add(DialogueFlagValue.impFortressFemalesDefeated);
			newFlags.values.add(DialogueFlagValue.impFortressMalesDefeated);
		}

		loadSet(parentElement, doc, newFlags.reindeerEncounteredIDs, "reindeerEncounteredIDs");
		loadSet(parentElement, doc, newFlags.reindeerWorkedForIDs, "reindeerWorkedForIDs");
		loadSet(parentElement, doc, newFlags.reindeerFuckedIDs, "reindeerFuckedIDs");

		loadSet(parentElement, doc, newFlags.warehouseDefeatedIDs, "warehouseDefeatedIDs");

		if(parentElement.getElementsByTagName("supplierStorageRoomsChecked").item(0)!=null) {
			for(int i=0; i<((Element) parentElement.getElementsByTagName("supplierStorageRoomsChecked").item(0)).getElementsByTagName("location").getLength(); i++){
				Element e = (Element) ((Element) parentElement.getElementsByTagName("supplierStorageRoomsChecked").item(0)).getElementsByTagName("location").item(i);

				newFlags.supplierStorageRoomsChecked.add(
						new Vector2i(
								Integer.valueOf(e.getAttribute("x")),
								Integer.valueOf(e.getAttribute("y"))));
			}
		}

		return newFlags;
	}

	private static void saveSet(Element parentElement, Document doc, Set<String> set, String title) {
		Element valuesElement = doc.createElement(title);
		parentElement.appendChild(valuesElement);
		for(String value : set) {
			CharacterUtils.createXMLElementWithValue(doc, valuesElement, "value", value.toString());
		}
	}

	private static void loadSet(Element parentElement, Document doc, Set<String> set, String title) {
		try {
			if(parentElement.getElementsByTagName(title).item(0)!=null) {
				for(int i=0; i<((Element) parentElement.getElementsByTagName(title).item(0)).getElementsByTagName("value").getLength(); i++){
					Element e = (Element) ((Element) parentElement.getElementsByTagName(title).item(0)).getElementsByTagName("value").item(i);

					set.add(e.getAttribute("value"));
				}
			}
		} catch(Exception ex) {
		}
	}

	public boolean hasFlag(DialogueFlagValue flag) {
		return values.contains(flag);
	}

	public void setFlag(DialogueFlagValue flag, boolean flagMarker) {
		if(flagMarker) {
			values.add(flag);
		} else {
			values.remove(flag);
		}
	}

	public NPC getSlaveTrader() {
		if(slaveTrader==null || slaveTrader.isEmpty()) {
			return null;
		}
		try {
			return (NPC) Main.game.getNPCById(slaveTrader);
		} catch (Exception e) {
			Util.logGetNpcByIdError("getSlaveTrader()", slaveTrader);
			return null;
		}
	}

	public void setSlaveTrader(GameCharacter slaveTrader) {
		if(slaveTrader==null) {
			this.slaveTrader = null;
		} else {
			this.slaveTrader = slaveTrader.getId();
		}
	}

	public String getSlaveTraderId() {
		return slaveTrader;
	}

	public void setSlaveTraderId(String slaveTrader) {
		this.slaveTrader = slaveTrader;
	}

	public NPC getManagementCompanion() {
		if(managementCompanion==null
				|| managementCompanion.isEmpty()) {
			return null;
		}
		try {
			return (NPC) Main.game.getNPCById(managementCompanion);
		} catch (Exception e) {
			Util.logGetNpcByIdError("getSlaveryManagerSlaveSelected()", managementCompanion);
			return null;
		}
	}

	public void setManagementCompanion(GameCharacter managementCompanion) {
		if(managementCompanion==null) {
			this.managementCompanion = null;
		} else {
			this.managementCompanion = managementCompanion.getId();
		}
	}

	public String getManagementCompanionId() {
		return managementCompanion;
	}

	public void setManagementCompanionId(String managementCompanion) {
		this.managementCompanion = managementCompanion;
	}

	// Reindeer event:

	public SlaveJob getSlaveryManagerJobSelected() {
		return slaveryManagerJobSelected;
	}

	public void setSlaveryManagerJobSelected(SlaveJob slaveryManagerJobSelected) {
		this.slaveryManagerJobSelected = slaveryManagerJobSelected;
	}

	public void addReindeerEncountered(String reindeerID) {
		reindeerEncounteredIDs.add(reindeerID);
	}

	public boolean hasEncounteredReindeer(String reindeerID) {
		return reindeerEncounteredIDs.contains(reindeerID);
	}

	public boolean hasEncounteredAnyReindeers() {
		return !reindeerEncounteredIDs.isEmpty();
	}

	public void addReindeerDailyWorkedFor(String reindeerID) {
		reindeerWorkedForIDs.add(reindeerID);
	}

	public boolean hasWorkedForReindeer(String reindeerID) {
		return reindeerWorkedForIDs.contains(reindeerID);
	}

	public void addReindeerDailyFucked(String reindeerID) {
		reindeerFuckedIDs.add(reindeerID);
	}

	public boolean hasFuckedReindeer(String reindeerID) {
		return reindeerFuckedIDs.contains(reindeerID);
	}

	public void dailyReindeerReset(String reindeerID) {
		reindeerWorkedForIDs.remove(reindeerID);
	}

	public void resetNyanActions() {
		this.setFlag(DialogueFlagValue.nyanTalkedTo, false);
		this.setFlag(DialogueFlagValue.nyanComplimented, false);
		this.setFlag(DialogueFlagValue.nyanFlirtedWith, false);
		this.setFlag(DialogueFlagValue.nyanKissed, false);
		this.setFlag(DialogueFlagValue.nyanMakeOut, false);
		this.setFlag(DialogueFlagValue.nyanSex, false);
		this.setFlag(DialogueFlagValue.nyanGift, false);
	}
}


code:
package com.lilithsthrone.game.dialogue;

import com.lilithsthrone.game.dialogue.responses.Response;
import com.lilithsthrone.main.Main;

/**
 * @since 0.1.0
 * @version 0.3.1
 * @author Innoxia
 */
public abstract class DialogueNode {

	private String label;
	private String description;

	private boolean travelDisabled;
	private boolean continuesDialogue;

	public DialogueNode(String label, String description, boolean travelDisabled) {
		this(label, description, travelDisabled, false);
	}

	public DialogueNode(String label, String description, boolean travelDisabled, boolean continuesDialogue) {
		this.label = label;
		this.description = description;

		this.travelDisabled = travelDisabled;
		this.continuesDialogue = continuesDialogue;
	}

	/**
	 * If this DialogueNode is accessed through a Response, then this method may be overwritten by the Response's own getSecondsPassed method.
	 *  Due to this, it cannot be guaranteed that this method is used when determining how much time passes in a scene.
	 *
	 * @return The number of seconds that pass when entering into this dialogue node.
	 */
	public int getSecondsPassed() {
		return 0;
	}

	/**
	 * Content that is set above the main content. It is not affected by fade-in effects.

	 * Append to Main.game.getTextStartStringBuilder() instead of using this!
	 */
//	@Deprecated
	public String getHeaderContent() {
		return null;
	}

	/** The main content for this DialogueNode. If enabled in the options, it is affected by fade-in effects. */
	public abstract String getContent();

	@Override
	public String toString() {
		return label + ":" + description.substring(0, description.length() <= 20 ? description.length() : 20);
	}

	/**
	 * @return The label for the button proceeding to this dialogue node.

	 * Note: In almost all instances, this is overridden by the Response class's getTitle() method.
	 */
	public String getLabel() {
		if(Main.game.isStarted() && (label==null || label.isEmpty())) {
			return Main.game.getPlayerCell().getPlace().getName();
		}
		return label;
	}

	public DialogueNode setLabel(String label) {
		this.label = label;
		return this;
	}

	/**
	 * @return The description for the action proceeding to this dialogue node, to be displayed in the action tooltip.

	 * Note: In almost all instances, this is overridden by the Response class's getTooltipText() method.
	 */
	public String getDescription() {
		return description;
	}

	/**
	 * @return True if this dialogue node should append to the current dialogue, instead of clearing the screen and displaying it as a new scene.
	 */
	public boolean isContinuesDialogue() {
		return continuesDialogue;
	}

	/**
	 * @return True if this dialogue should display the actions title as part of a continuation of the scene, in this format:

	 * ...sunt in culpa qui officia deserunt mollit anim id est laborum.

	 *

	 * > Accept

	 * Lorem ipsum dolor sit amet, consectetur adipiscing elit...
	 */
	public boolean isDisplaysActionTitleOnContinuesDialogue() {
		return true;
	}

	/**
	 * Index starts at 0.
	 * @return The title to be displayed on the response tab. Only indices that are defined as returning a title are displayed, so just return null as the fallback option.
	 */
	public String getResponseTabTitle(int index) {
		return null;
	}

	/**
	 * The responses that lead on from this dialogue node.
	 * @param responseTab The tab in which the response is to be found.
	 * @param index The index of the response.
	 * @return A response, if a response is returned at the specified responseTab & index, or, if not, then null.
	 */
	public abstract Response getResponse(int responseTab, int index);

	/**
	 * @return True if this dialogue node disables map movement.
	 */
	public boolean isTravelDisabled() {
		return travelDisabled;
	}

	/**
	 * @return True if this dialogue node disables inventory management. By default, this is disabled if {@code isTravelDisabled()} returns true.
	 */
	public boolean isInventoryDisabled() {
		return isTravelDisabled();
	}

	/**
	 * @return The type of dialogue that this is. Will almost always be {@code DialogueNodeType.NORMAL}.
	 */
	public DialogueNodeType getDialogueNodeType() {
		return DialogueNodeType.NORMAL;
	}

	/**
	 * @return True if health and aura regeneration is disabled in this dialogue node.
	 */
	public boolean isRegenerationDisabled() {
		return false;
	}

	/**
	 * @return The author of the scene.
	 */
	public String getAuthor(){
		return "Innoxia";
	}

	/**
	 * @return True if the header should not be run through the parsing engine.
	 */
	public boolean disableHeaderParsing() {
		return false;
	}

	/**
	 * @return True if this dialogue node should be re-run through the parser when it is restored (i.e. loaded from a saved dialogue node).
	 */
	public boolean reloadOnRestore() {
		return false;
	}

	/**
	 * This method is called before the getContent() method is called.
	 */
	public void applyPreParsingEffects() {
	}
}


code:
package com.lilithsthrone.game.dialogue;

/**
 * @since 0.1.0
 * @version 0.2.5
 * @author Innoxia
 */
public enum DialogueNodeType {

	NORMAL("normal"),

	STATUS_EFFECT_MESSAGE("statusEffectMessage"),

	INVENTORY("inventory"),

	PHONE("phone"),

	CHARACTERS_PRESENT("characters present"),

	OCCUPANT_MANAGEMENT("slavery management"),

	OPTIONS("options");

	private String name;

	private DialogueNodeType(String name) {
		this.name = name;
	}

	public String getName() {
		return name;
	}
}