code:
package com.lilithsthrone.game.dialogue;

import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import com.lilithsthrone.game.PropertyValue;
import com.lilithsthrone.game.character.GameCharacter;
import com.lilithsthrone.game.character.attributes.AffectionLevel;
import com.lilithsthrone.game.character.attributes.ObedienceLevel;
import com.lilithsthrone.game.character.body.BodyPartInterface;
import com.lilithsthrone.game.character.body.Eye;
import com.lilithsthrone.game.character.body.Hair;
import com.lilithsthrone.game.character.body.Skin;
import com.lilithsthrone.game.character.body.Vagina;
import com.lilithsthrone.game.character.body.types.BodyCoveringType;
import com.lilithsthrone.game.character.body.types.FaceType;
import com.lilithsthrone.game.character.body.valueEnums.BodyMaterial;
import com.lilithsthrone.game.character.body.valueEnums.PiercingType;
import com.lilithsthrone.game.character.effects.Perk;
import com.lilithsthrone.game.character.effects.PerkManager;
import com.lilithsthrone.game.character.markings.TattooCounterType;
import com.lilithsthrone.game.character.markings.TattooType;
import com.lilithsthrone.game.character.npc.NPC;
import com.lilithsthrone.game.character.npc.misc.Elemental;
import com.lilithsthrone.game.dialogue.eventLog.SlaveryEventLogEntry;
import com.lilithsthrone.game.dialogue.responses.Response;
import com.lilithsthrone.game.dialogue.responses.ResponseEffectsOnly;
import com.lilithsthrone.game.dialogue.utils.BodyChanging;
import com.lilithsthrone.game.dialogue.utils.CharacterModificationUtils;
import com.lilithsthrone.game.dialogue.utils.CharactersPresentDialogue;
import com.lilithsthrone.game.dialogue.utils.InventoryInteraction;
import com.lilithsthrone.game.dialogue.utils.UtilText;
import com.lilithsthrone.game.occupantManagement.SlaveJob;
import com.lilithsthrone.game.occupantManagement.SlaveJobHours;
import com.lilithsthrone.game.occupantManagement.SlaveJobSetting;
import com.lilithsthrone.game.occupantManagement.SlavePermission;
import com.lilithsthrone.game.occupantManagement.SlavePermissionSetting;
import com.lilithsthrone.main.Main;
import com.lilithsthrone.rendering.SVGImages;
import com.lilithsthrone.utils.Color;
import com.lilithsthrone.utils.Util;
import com.lilithsthrone.world.Cell;
import com.lilithsthrone.world.WorldType;
import com.lilithsthrone.world.places.GenericPlace;
import com.lilithsthrone.world.places.PlaceType;
import com.lilithsthrone.world.places.PlaceUpgrade;

/**
 * @since 0.1.8?
 * @version 0.2.11
 * @author Innoxia
 */
public class OccupantManagementDialogue {

	private static StringBuilder miscDialogueSB = new StringBuilder();
	private static int dayNumber = 1;
	private static DecimalFormat decimalFormat = new DecimalFormat("#0.00");
	private static boolean slaveListManagementOverview = false;

	static {
		decimalFormat.setRoundingMode(RoundingMode.HALF_EVEN);
	}

	public static NPC characterSelected() {
		return Main.game.getDialogueFlags().getSlaveryManagerSlaveSelected();
	}

	public static DialogueNode getSlaveryOverviewDialogue() {
		dayNumber = Main.game.getDayNumber();
		return OCCUPANT_OVERVIEW;
	}

	public static DialogueNode getSlaveryManagementInspectSlaveDialogue(NPC slave) {
		Main.game.getDialogueFlags().setSlaveryManagerSlaveSelected(slave);
		CharactersPresentDialogue.resetContent(slave);
		return SLAVE_MANAGEMENT_INSPECT;
	}

	public static DialogueNode getSlaveryManagementSlaveJobsDialogue(NPC slave) {
		Main.game.getDialogueFlags().setSlaveryManagerSlaveSelected(slave);
		CharactersPresentDialogue.resetContent(slave);
		return SLAVE_MANAGEMENT_JOBS;
	}

	public static DialogueNode getSlaveryManagementSlavePermissionsDialogue(NPC slave) {
		Main.game.getDialogueFlags().setSlaveryManagerSlaveSelected(slave);
		CharactersPresentDialogue.resetContent(slave);
		return SLAVE_MANAGEMENT_PERMISSIONS;
	}

	public static DialogueNode getSlaveryManagementDialogue(NPC slaveTrader) {
		Main.game.getDialogueFlags().setSlaveTrader(slaveTrader);
		slaveListManagementOverview = true;
		return SLAVE_LIST_MANAGEMENT;
	}

	public static DialogueNode getSlaveryRoomListDialogue(NPC slaveTrader) {
		Main.game.getDialogueFlags().setSlaveTrader(slaveTrader);
		slaveListManagementOverview = false;
		return SLAVE_LIST;
	}

	public static int getDayNumber() {
		return dayNumber;
	}

	public static void incrementDayNumber(int increment) {
		dayNumber += increment;
		dayNumber = Math.max(1,
				dayNumber);
	}

	private static Response getSlaveryResponse(int index) {
		if (index == 1) {
			return new Response("Room List", "View the room management screen.", ROOM_MANAGEMENT) {
				@Override
				public void effects() {
					Main.game.getDialogueFlags().setSlaveryManagerSlaveSelected(null);
				}
			};

		} else if (index == 2) {
			return new Response("Slave List", "Enter the slave management screen.", SLAVE_LIST_MANAGEMENT) {
				@Override
				public DialogueNode getNextDialogue() {
					return OccupantManagementDialogue.getSlaveryManagementDialogue(Main.game.getDialogueFlags().getSlaveTrader());
				}
				@Override
				public void effects() {
					Main.game.getDialogueFlags().setSlaveryManagerSlaveSelected(null);
				}
			};

		} else if (index == 5) {
			if(Main.game.getOccupancyUtil().getGeneratedBalance()==0) {
				return new Response("Collect: "+UtilText.formatAsMoneyUncolored(Main.game.getOccupancyUtil().getGeneratedBalance(), "span"), "Your current balance is 0...",  null);

			} else if(Main.game.getOccupancyUtil().getGeneratedBalance()>0) {
				return new Response("Collect: "+UtilText.formatAsMoney(Main.game.getOccupancyUtil().getGeneratedBalance(), "span"), "Collect the money that you've earned through your slaves' activities.",  OCCUPANT_OVERVIEW) {
					@Override
					public DialogueNode getNextDialogue() {
						return Main.game.getCurrentDialogueNode();
					}
					@Override
					public void effects() {
						Main.game.getOccupancyUtil().payOutBalance();
					}
				};

			} else {
				if(Main.game.getPlayer().getMoney()<Math.abs(Main.game.getOccupancyUtil().getGeneratedBalance())) {
					return new Response("Pay: "+UtilText.formatAsMoneyUncolored(Math.abs(Main.game.getOccupancyUtil().getGeneratedBalance()), "span"),
							"You don't have enough money to pay off the accumulated debt from the upkeep of your slaves and rooms.",  null);
				}

				return new Response("Pay: "+UtilText.formatAsMoney(Math.abs(Main.game.getOccupancyUtil().getGeneratedBalance()), "span", Color.GENERIC_BAD), "Pay off the accumulated debt from the upkeep of your slaves and rooms.",  OCCUPANT_OVERVIEW) {
					@Override
					public DialogueNode getNextDialogue() {
						return Main.game.getCurrentDialogueNode();
					}
					@Override
					public void effects() {
						Main.game.getOccupancyUtil().payOutBalance();
					}
				};
			}

		} else if (index == 0) {
			return new Response("Back", "Exit the room upgrades screen.", OCCUPANT_OVERVIEW) {
				@Override
				public DialogueNode getNextDialogue() {
					return Main.game.getActiveWorld().getCell(Main.game.getPlayer().getLocation()).getPlace().getDialogue(true);
				}
				@Override
				public void effects() {
					Main.game.getDialogueFlags().setSlaveryManagerSlaveSelected(null);
					Main.game.getDialogueFlags().setSlaveTrader(null);
				}
			};

		} else {
			return null;
		}
	}

	public static final DialogueNode OCCUPANT_OVERVIEW = new DialogueNode("Slavery Overview", ".", true) {

		@Override
		public DialogueNodeType getDialogueNodeType() {
			return DialogueNodeType.OCCUPANT_MANAGEMENT;
		}

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

			int income = Main.game.getPlayer().getSlaveryTotalDailyIncome();
			int upkeep = Main.game.getPlayer().getSlaveryTotalDailyUpkeep();

			// Overview:
			UtilText.nodeContentSB.append(
					"
"
						+ "
Totals
"
						+ "
"
							+ "
"
								+ "Slaves"
							+ "
"
							+ "
"
								+ "Income"
							+"
"
							+ "
"
								+ "Upkeep"
							+"
"
							+ "
"
								+ "Profit"
							+"
"
							+ "
"
								+ "Funds"
							+"
"
							+ "
"
								+ "Payments"
							+"
"
							+ "
"
								+ "Balance"
							+"
"
						+ "
"
						+ "
"
							+ "
"
								+ Main.game.getPlayer().getSlavesOwned().size()
							+ "
"
							+ "
"
								+ UtilText.formatAsMoney(income)+"/day"
							+"
"
							+ "
"
								+ UtilText.formatAsMoney(upkeep, "b", Color.GENERIC_BAD)+"/day"
							+"
"
							+ "
"
								+ UtilText.formatAsMoney(income-upkeep, "b", (income-upkeep<0?Color.GENERIC_BAD:Color.TEXT))+"/day"
							+"
"
							+ "
"
								+ UtilText.formatAsMoney(Main.game.getOccupancyUtil().getGeneratedIncome(), "b")
							+ "
"
							+ "
"
								+ UtilText.formatAsMoney(Main.game.getOccupancyUtil().getGeneratedUpkeep(), "b", Color.GENERIC_BAD)
							+ "
"
							+ "
"
								+ (Main.game.getOccupancyUtil().getGeneratedBalance()<0
										? UtilText.formatAsMoney(Main.game.getOccupancyUtil().getGeneratedBalance(), "b", Color.GENERIC_BAD)
										: UtilText.formatAsMoney(Main.game.getOccupancyUtil().getGeneratedBalance(), "b"))
							+ "
"
						+ "
"
					+"
");

			// Logs:
			UtilText.nodeContentSB.append(
					"
"
						+ "
Activity Log
"
									+ "
Previous
"
									+ "Day: "+dayNumber+(dayNumber==Main.game.getDayNumber()?" (Today)":"")+(dayNumber==Main.game.getDayNumber()-1?" (Yesterday)":"")+""
									+ "
Next
"

						+ "
"
							+ "
"
								+ "Time"
							+ "
"
							+ "
"
								+ "Slave"
							+ "
"
							+ "
"
								+ "Event"
							+"
"
							+ "
"
								+ "Description"
							+"
"
							+ "
"
								+ "Effects"
							+"
"
						+ "
"
						+ "
");

			int count=0;
			if(Main.game.getSlaveryEventLog().get(dayNumber)!=null) {
				for(SlaveryEventLogEntry entry : Main.game.getSlaveryEventLog().get(dayNumber)) {
					if(count%2==0) {
						UtilText.nodeContentSB.append("
");
					} else {
						UtilText.nodeContentSB.append("
");
					}

					UtilText.nodeContentSB.append(
								"
"
									+ String.format("%02d", entry.getTime()) + ":00
"
								+ "
"
								+ "
"
									 + entry.getSlaveName()
								+ "
"
								+ "
"
									+ entry.getName()
								+ "
"
								+ "
"
									+ entry.getDescription()
								+ "
"
								+ "
");
//								+ "
");
//
//					boolean effectsAdded = false;
//					if(entry.getEffects()!=null) {
//						for(String s : entry.getEffects()) {
//							if(!s.isEmpty()) {
//								UtilText.nodeContentSB.append(s+"
");
//								effectsAdded = true;
//							}
//						}
//					}
//					if(!effectsAdded) {
//						UtilText.nodeContentSB.append("[style.colorDisabled(-)]");
//					}
//
//					UtilText.nodeContentSB.append("
"
//							+"
");
					count++;
				}
			}
			if(count==0) {
				UtilText.nodeContentSB.append("
[style.colorDisabled(No events for this day...)]
");
			}

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

			return UtilText.nodeContentSB.toString();
		}

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

	public static final DialogueNode ROOM_MANAGEMENT = new DialogueNode("Room Management", ".", true) {

		@Override
		public DialogueNodeType getDialogueNodeType() {
			return DialogueNodeType.OCCUPANT_MANAGEMENT;
		}

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

			Cell cell = Main.game.getPlayerCell();
			GenericPlace place = cell.getPlace();
			List<NPC> charactersPresent = Main.game.getCharactersTreatingCellAsHome(cell);
			float affectionChange = place.getHourlyAffectionChange();
			float obedienceChange = place.getHourlyObedienceChange();
			UtilText.nodeContentSB.append(
					"
"
						+ "
Current Location
"
						+ getRoomHeader()
						+ getRoomEntry(!place.isAbleToBeUpgraded(), true, cell, charactersPresent, affectionChange, obedienceChange)
					+"
");

			// Lilaya's home:
			UtilText.nodeContentSB.append(getWorldRooms(WorldType.LILAYAS_HOUSE_GROUND_FLOOR));
			UtilText.nodeContentSB.append(getWorldRooms(WorldType.LILAYAS_HOUSE_FIRST_FLOOR));

			// Slaver alley:
			UtilText.nodeContentSB.append(getWorldRooms(WorldType.SLAVER_ALLEY));

			return UtilText.nodeContentSB.toString();
		}

		@Override
		public Response getResponse(int responseTab, int index) {
			if(index==0) {
				return new Response("Back", "Return to the previous screen.", OCCUPANT_OVERVIEW);

			} else {
				return null;
			}
		}
	};

	private static String getRoomHeader() {
		return "
"
					+ "
"
						+ "Room Name"
					+ "
"
					+ "
"
						+ "Occupants"
					+ "
"
					+ "
"
						+ "Capacity"
					+"
"
					+ "
"
						+ "Affection"
					+"
"
					+ "
"
						+ "Obedience"
					+"
"
					+ "
"
						+ "Upkeep"
					+"
"
					+ "
"
						+ "Actions"
					+"
"
				+ "
";
	}

	private static String getRoomEntry(boolean disabled, boolean currentLocation, Cell cell, List<NPC> occupants, float affectionChange, float obedienceChange) {
		miscDialogueSB.setLength(0);

		GenericPlace place = cell.getPlace();

		miscDialogueSB.append(
				"
"
						+ "
"
							+ ""+place.getName()+"
"
						+ "
"
						+ "
");

		int i=0;
		for(NPC occupant : occupants) {
			if(occupant.isSlave()) {
				miscDialogueSB.append(""+occupant.getName()+""+(i+1==occupants.size()?"":"
"));
				i++;
			}
		}
		if(i==0) {
			miscDialogueSB.append("Empty");
		}

		miscDialogueSB.append("
"
						+ "
"
							+ i+"/"+place.getCapacity()
						+"
"
						+ "
"
							+ "0?Color.GENERIC_GOOD:Color.GENERIC_BAD)).toWebHexString()+";'>"+(affectionChange>0?"+":"")
								+decimalFormat.format(affectionChange)+"/hour"
						+"
"
						+ "
"
							+ "0?Color.GENERIC_GOOD:Color.GENERIC_BAD)).toWebHexString()+";'>"+(obedienceChange>0?"+":"")
								+decimalFormat.format(obedienceChange)+"/hour"
						+"
"
						+ "
"
							+ (disabled
									?"[style.colorDisabled(N/A)]"
									:(place.getUpkeep()>0
										?UtilText.formatAsMoney(-place.getUpkeep(), "span", Color.GENERIC_BAD)
										:(place.getUpkeep()==0
											?UtilText.formatAsMoney(-place.getUpkeep(), "span", Color.TEXT_GREY)
											:UtilText.formatAsMoney(-place.getUpkeep(), "span", Color.GENERIC_GOOD))))+"/day"
						+"
"
						+ "
"
							+ (disabled
									?"

"+SVGImages.SVG_IMAGE_PROVIDER.getSlaveInspectDisabled()+"

"
									:"

"+SVGImages.SVG_IMAGE_PROVIDER.getSlaveInspect()+"

")
						+"
"
					+ "
");

		return miscDialogueSB.toString();
	}

	private static List<Cell> importantCells = new ArrayList<>();

	public static void resetImportantCells() {
		importantCells = new ArrayList<>();
	}

	public static List<Cell> getImportantCells() {
		if(importantCells.isEmpty()) {
			WorldType[] importantWorlds = new WorldType[] {WorldType.LILAYAS_HOUSE_GROUND_FLOOR, WorldType.LILAYAS_HOUSE_FIRST_FLOOR};
			for(WorldType wt : importantWorlds) {
				Cell[][] cellGrid = Main.game.getWorlds().get(wt).getCellGrid();
				for(int i = 0; i< cellGrid.length; i++) {
					for(int j = 0; j < cellGrid[0].length; j++) {
						if(cellGrid[i][j].getPlace().getPlaceType()!=PlaceType.LILAYA_HOME_CORRIDOR
								&& cellGrid[i][j].getPlace().getPlaceType()!=PlaceType.GENERIC_IMPASSABLE) {
							importantCells.add(cellGrid[i][j]);
						}
					}
				}
			}
		}

		return importantCells;
	}

	private static String getWorldRooms(WorldType worldType) {
		StringBuilder worldRoomSB = new StringBuilder();

		worldRoomSB.append(
				"
"
					+ "
"+worldType.getName()+"
"
					+ getRoomHeader());

		Cell[][] cellGrid = Main.game.getWorlds().get(worldType).getCellGrid();
		List<Cell> sortingCells = new ArrayList<>();
		for(int i = 0; i< cellGrid.length; i++) {
			for(int j = 0; j < cellGrid[0].length; j++) {
				if(!cellGrid[i][j].getPlace().getPlaceUpgrades().isEmpty()) {
//					importantCells.add(cellGrid[i][j]);
					sortingCells.add(cellGrid[i][j]);
				}
			}
		}

		sortingCells.sort(Comparator.comparing(Cell::getPlaceName));

		for(Cell c : sortingCells) {
			GenericPlace place = c.getPlace();
			worldRoomSB.append(getRoomEntry(!place.isAbleToBeUpgraded(), false, c, Main.game.getCharactersTreatingCellAsHome(c), place.getHourlyAffectionChange(), place.getHourlyObedienceChange()));
		}

		worldRoomSB.append("
");

		return worldRoomSB.toString();
	}

	public static Cell cellToInspect;

	public static final DialogueNode ROOM_UPGRADES = new DialogueNode("Room Management", ".", true) {

		@Override
		public DialogueNodeType getDialogueNodeType() {
			return DialogueNodeType.OCCUPANT_MANAGEMENT;
		}

		@Override
		public String getLabel() {
			return cellToInspect.getPlace().getName()+" Management";
		}

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

			UtilText.nodeContentSB.append(
					"
"
							+ "
Overview (Total Values for this Room)
"
							+"
"
								+ "
"
									+ "Name"
								+ "
"
								+ "
"
									+ "Occupants"
								+ "
"
								+ "
"
									+ "Capacity"
								+"
"
								+ "
"
									+ "Affection"
								+"
"
								+ "
"
									+ "Obedience"
								+"
"
								+ "
"
									+ "Upkeep"
								+"
"
							+ "
");

			GenericPlace place = cellToInspect.getPlace();
			float affectionChange = place.getHourlyAffectionChange();
			float obedienceChange = place.getHourlyObedienceChange();

			UtilText.nodeContentSB.append(
					"
"
							+ "
"
								+ "<form style='float:left; width:85%; margin:0; padding:0;'><input type='text' id='nameInput' value='"+ UtilText.parseForHTMLDisplay(cellToInspect.getPlace().getName())+ "' style='width:100%; margin:0; padding:0;'></form>"
								+ "
"
									+ "✓"
								+ "
"
							+ "
"
							+ "
");

			int i=0;
			List<NPC> occupants = Main.game.getCharactersTreatingCellAsHome(cellToInspect);
			for(NPC occupant : occupants) {
				if(occupant.isSlave()) {
					UtilText.nodeContentSB.append(""+occupant.getName()+""+(i+1==occupants.size()?"":"
"));
					i++;
				}
			}
			if(i==0) {
				UtilText.nodeContentSB.append("Empty");
			}

			UtilText.nodeContentSB.append(
					"
"
							+ "
"
								+ i+"/"+place.getCapacity()
							+"
"
							+ "
"
								+ "0?Color.GENERIC_GOOD:Color.GENERIC_BAD)).toWebHexString()+";'>"+(affectionChange>0?"+":"")
									+decimalFormat.format(affectionChange)+"/hour"
							+"
"
							+ "
"
								+ "0?Color.GENERIC_GOOD:Color.GENERIC_BAD)).toWebHexString()+";'>"+(obedienceChange>0?"+":"")
									+decimalFormat.format(obedienceChange)+"/hour"
							+"
"
							+ "
"
								+ (place.getUpkeep()>0
											?UtilText.formatAsMoney(-place.getUpkeep(), "span", Color.GENERIC_BAD)
											:UtilText.formatAsMoney(-place.getUpkeep(), "span", Color.GENERIC_GOOD))+"/day"
							+"
"
						+ "
"
						+ "
");

			// Normal upgrades:
			UtilText.nodeContentSB.append("
"
											+ "
Modifications
"
											+ getRoomUpgradeHeader());

			List<PlaceUpgrade> coreUpgrades = new ArrayList<>();
			for(PlaceUpgrade upgrade : place.getPlaceType().getAvailablePlaceUpgrades(place.getPlaceUpgrades())) {
				if(upgrade.isCoreRoomUpgrade()) {
					coreUpgrades.add(upgrade);
				} else {
					UtilText.nodeContentSB.append(getUpgradeEntry(cellToInspect, upgrade));
					i++;
				}
			}
			if(i==0) {
				UtilText.nodeContentSB.append(
						"
"
								+ "No Modifications Available"
						+ "
");
			}

			UtilText.nodeContentSB.append("
");

			// Core upgrades:
			UtilText.nodeContentSB.append("
"
					+ "
Core Modifications
"
					+"
Purchasing a [style.boldArcane(core modification)] will remove [style.boldBad(all)] other modifications in this room!
"
					+ getRoomUpgradeHeader());

//			for (PlaceUpgrade upgrade : place.getPlaceUpgrades()) {
//				if(upgrade.isCoreRoomUpgrade()) {
//					UtilText.nodeContentSB.append(getUpgradeEntry(cellToInspect, upgrade));
//				}
//			}

			i = 0;
			for (PlaceUpgrade upgrade : coreUpgrades) {
				UtilText.nodeContentSB.append(getUpgradeEntry(cellToInspect, upgrade));
				i++;
			}
			if(i==0) {
				UtilText.nodeContentSB.append(
						"
"
								+ "No Core Modifications Available"
						+ "
");
			}

			UtilText.nodeContentSB.append("
"
					+ "

");

			return UtilText.nodeContentSB.toString();
		}

		@Override
		public Response getResponse(int responseTab, int index) {
			if (index == 5) {
				if(Main.game.getOccupancyUtil().getGeneratedBalance()==0) {
					return new Response("Collect: "+UtilText.formatAsMoneyUncolored(Main.game.getOccupancyUtil().getGeneratedBalance(), "span"), "Your current balance is 0...",  null);

				} else if(Main.game.getOccupancyUtil().getGeneratedBalance()>0) {
					return new Response("Collect: "+UtilText.formatAsMoney(Main.game.getOccupancyUtil().getGeneratedBalance(), "span"),
							"Collect the money that you've earned through your slaves' activities.",  ROOM_UPGRADES) {
						@Override
						public DialogueNode getNextDialogue() {
							return Main.game.getCurrentDialogueNode();
						}
						@Override
						public void effects() {
							Main.game.getOccupancyUtil().payOutBalance();
						}
					};

				} else {
					if(Main.game.getPlayer().getMoney()<Math.abs(Main.game.getOccupancyUtil().getGeneratedBalance())) {
						return new Response("Pay: "+UtilText.formatAsMoneyUncolored(Math.abs(Main.game.getOccupancyUtil().getGeneratedBalance()), "span"),
								"You don't have enough money to pay off the accumulated debt from the upkeep of your slaves and rooms.",  null);
					}

					return new Response("Pay: "+UtilText.formatAsMoney(Math.abs(Main.game.getOccupancyUtil().getGeneratedBalance()), "span", Color.GENERIC_BAD),
							"Pay off the accumulated debt from the upkeep of your slaves and rooms.",  ROOM_UPGRADES) {
						@Override
						public DialogueNode getNextDialogue() {
							return Main.game.getCurrentDialogueNode();
						}
						@Override
						public void effects() {
							Main.game.getOccupancyUtil().payOutBalance();
						}
					};
				}

			} else if(index==0) {
				return new Response("Back", "Return to the previous screen.", ROOM_UPGRADES) {
					@Override
					public DialogueNode getNextDialogue() {
						return Main.game.getDefaultDialogueNoEncounter();
					}
				};
			} else {
				return null;
			}
		}
	};

	public static final DialogueNode ROOM_UPGRADES_MANAGEMENT = new DialogueNode("Room Management", ".", true) {

		@Override
		public DialogueNodeType getDialogueNodeType() {
			return DialogueNodeType.OCCUPANT_MANAGEMENT;
		}

		@Override
		public String getLabel() {
			return cellToInspect.getPlace().getName()+" Management";
		}

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

		@Override
		public Response getResponse(int responseTab, int index) {
			if (index == 5) {
				if(Main.game.getOccupancyUtil().getGeneratedBalance()==0) {
					return new Response("Collect: "+UtilText.formatAsMoneyUncolored(Main.game.getOccupancyUtil().getGeneratedBalance(), "span"), "Your current balance is 0...",  null);

				} else if(Main.game.getOccupancyUtil().getGeneratedBalance()>0) {
					return new Response("Collect: "+UtilText.formatAsMoney(Main.game.getOccupancyUtil().getGeneratedBalance(), "span"),
							"Collect the money that you've earned through your slaves' activities.",  ROOM_UPGRADES_MANAGEMENT) {
						@Override
						public DialogueNode getNextDialogue() {
							return Main.game.getCurrentDialogueNode();
						}
						@Override
						public void effects() {
							Main.game.getOccupancyUtil().payOutBalance();
						}
					};

				} else {
					if(Main.game.getPlayer().getMoney()<Math.abs(Main.game.getOccupancyUtil().getGeneratedBalance())) {
						return new Response("Pay: "+UtilText.formatAsMoneyUncolored(Math.abs(Main.game.getOccupancyUtil().getGeneratedBalance()), "span"),
								"You don't have enough money to pay off the accumulated debt from the upkeep of your slaves and rooms.",  null);
					}

					return new Response("Pay: "+UtilText.formatAsMoney(Math.abs(Main.game.getOccupancyUtil().getGeneratedBalance()), "span", Color.GENERIC_BAD),
							"Pay off the accumulated debt from the upkeep of your slaves and rooms.",  ROOM_UPGRADES_MANAGEMENT) {
						@Override
						public DialogueNode getNextDialogue() {
							return Main.game.getCurrentDialogueNode();
						}
						@Override
						public void effects() {
							Main.game.getOccupancyUtil().payOutBalance();
						}
					};
				}

			} else if(index==0) {
				return new Response("Back", "Return to the previous screen.", ROOM_MANAGEMENT);
			} else {
				return null;
			}
		}
	};

	private static String getRoomUpgradeHeader() {
		return "
"
					+ "
"
						+ "Upgrade"
					+ "
"
					+ "
"
						+ "Capacity"
					+"
"
					+ "
"
						+ "Affection"
					+"
"
					+ "
"
						+ "Obedience"
					+"
"
					+ "
"
						+ "Upkeep"
					+"
"
					+ "
"
						+ "Cost"
					+"
"
					+ "
"
						+ "Actions"
					+"
"
				+ "
";
	}

	private static String getUpgradeEntry(Cell cell, PlaceUpgrade upgrade) {
		miscDialogueSB.setLength(0);
		GenericPlace place = cell.getPlace();
		float affectionChange = upgrade.getHourlyAffectionGain();
		float obedienceChange = upgrade.getHourlyObedienceGain();
		boolean owned = place.getPlaceUpgrades().contains(upgrade);
		boolean availableForPurchase = upgrade.isPrerequisitesMet(place) && upgrade.isAvailable(cell) && (owned?Main.game.getPlayer().getMoney()>=upgrade.getRemovalCost():Main.game.getPlayer().getMoney()>=upgrade.getInstallCost());
		boolean canBuy = availableForPurchase;

		miscDialogueSB.append(
				"
"
						+ "
"
							+ "
"+SVGImages.SVG_IMAGE_PROVIDER.getInformationIcon()+"
"
						+ "
"
						+ "
"
							+ (owned
									?""+Util.capitalizeSentence(upgrade.getName())+""
									:(!availableForPurchase
											?""+Util.capitalizeSentence(upgrade.getName())+""
											:""+Util.capitalizeSentence(upgrade.getName())+""))
//							+ "
"+SVGImages.SVG_IMAGE_PROVIDER.getInformationIcon()+"
"
//							+"

"
						+ "
"
						+ "
"
							+ (upgrade.getCapacity()>0
									?"+"+upgrade.getCapacity()+""
									:(upgrade.getCapacity()<0
											?""+upgrade.getCapacity()+""
											:"[style.colorDisabled(0)]"))
						+ "
"
						+ "
"
							+ (affectionChange>0
									?"+"+decimalFormat.format(affectionChange)+ "/hour"
									:(affectionChange<0
											?""+decimalFormat.format(affectionChange)+ "/hour"
											:"[style.colorDisabled(0)]/hour"))
						+"
"
						+ "
"
							+ (obedienceChange>0
									?"+"+decimalFormat.format(obedienceChange)+ "/hour"
									:(obedienceChange<0
											?""+decimalFormat.format(obedienceChange)+ "/hour"
											:"[style.colorDisabled(0)]/hour"))
						+"
"
						+ "
"
							+ (upgrade.getUpkeep()>0
									?UtilText.formatAsMoney(upgrade.getUpkeep(), "b", Color.GENERIC_BAD)
									:UtilText.formatAsMoney(upgrade.getUpkeep(), "b", Color.GENERIC_GOOD))+"/day"
						+"
"
						+ "
"
							+ (owned
									?(upgrade.getRemovalCost()<0
											?UtilText.formatAsMoney(upgrade.getRemovalCost(), "b", Color.GENERIC_GOOD)
											:(upgrade.getRemovalCost() < Main.game.getPlayer().getMoney()
													?UtilText.formatAsMoney(upgrade.getRemovalCost(), "b")
													:UtilText.formatAsMoney(upgrade.getRemovalCost(), "b", Color.GENERIC_BAD)))
									:(upgrade.getInstallCost()<0
											?UtilText.formatAsMoney(upgrade.getInstallCost(), "b", Color.GENERIC_GOOD)
											:(upgrade.getInstallCost() < Main.game.getPlayer().getMoney()
													?UtilText.formatAsMoney(upgrade.getInstallCost(), "b")
													:UtilText.formatAsMoney(upgrade.getInstallCost(), "b", Color.GENERIC_BAD))))
						+"
"
						+ "
");

		if(owned) {
			if(Main.game.getPlayer().getMoney()<upgrade.getRemovalCost() || upgrade.isCoreRoomUpgrade()) {
				miscDialogueSB.append("

"+SVGImages.SVG_IMAGE_PROVIDER.getTransactionSellDisabled()+"

");
			} else {
				miscDialogueSB.append("

"+SVGImages.SVG_IMAGE_PROVIDER.getTransactionSell()+"

");
			}

		} else {
			if(Main.game.getPlayer().getMoney()<upgrade.getInstallCost() || Main.game.getOccupancyUtil().getGeneratedBalance()<0) {
				canBuy = false;
			}
			if(canBuy) {
				if(!upgrade.getPrerequisites().isEmpty()) {
					for(PlaceUpgrade prereq : upgrade.getPrerequisites()) {
						if(!place.getPlaceUpgrades().contains(prereq)) {
							canBuy = false;
							break;
						}
					}
				}
			}

			if(canBuy) {
				miscDialogueSB.append("

"+SVGImages.SVG_IMAGE_PROVIDER.getTransactionBuy()+"

");
			} else {
				miscDialogueSB.append("

"+SVGImages.SVG_IMAGE_PROVIDER.getTransactionBuyDisabled()+"

");
			}
		}

		miscDialogueSB.append(
				"
"
//					+ "
"
//						+ ""
//							+(!owned
//								?"[style.colorDisabled("+upgrade.getDescriptionForPurchase()+")]"
//								:upgrade.getDescriptionAfterPurchase())
//						+""
//					+ "
"
//					+ (upgrade.isCoreRoomUpgrade() && !owned
//							?"
This is a [style.boldArcane(core modification)], and will [style.boldBad(remove all other modifications in this room when purchased)].
"
//							:"")
					);

		if(!canBuy) {
			miscDialogueSB.append("
"
				+ ""
				+ "[style.colorBad("+ getPurchaseAvailabilityTooltipText(OccupantManagementDialogue.cellToInspect, upgrade)+")]"
				+""
			+ "
");
		}

		miscDialogueSB.append("
");

		return miscDialogueSB.toString();
	}

	private static StringBuilder purchaseAvailability = new StringBuilder();
	public static String getPurchaseAvailabilityTooltipText(Cell cell, PlaceUpgrade upgrade) {
		GenericPlace place = cell.getPlace();
		boolean owned = place.getPlaceUpgrades().contains(upgrade);

		purchaseAvailability.setLength(0);

		if(owned) {
			if(Main.game.getPlayer().getMoney()<upgrade.getRemovalCost()) {
				purchaseAvailability.append("You cannot afford to remove this modification.");
			}

		} else {
			if(Main.game.getOccupancyUtil().getGeneratedBalance()<0) {
				purchaseAvailability.append("You cannot purchase any modifications while your slavery balance is negative.");
			}

			if(Main.game.getPlayer().getMoney()<upgrade.getInstallCost()) {
				purchaseAvailability.append("You cannot afford this modification.");
			}

			if(!upgrade.getPrerequisites().isEmpty()) {
				purchaseAvailability.append("You need to purchase the following first:");
				for(PlaceUpgrade prereq : upgrade.getPrerequisites()) {
					if(place.getPlaceUpgrades().contains(prereq)) {
						purchaseAvailability.append("
"+prereq.getName()+"");
					} else {
						purchaseAvailability.append("
"+prereq.getName()+"");
					}
				}
			}
		}

		String availabilityDescription = upgrade.getAvailabilityDescription(OccupantManagementDialogue.cellToInspect);
		if(availabilityDescription!=null && availabilityDescription.length()>0) {
			purchaseAvailability.append("
"+availabilityDescription+"");
		}

		return purchaseAvailability.toString();
	}

	private static final DialogueNode SLAVE_LIST = new DialogueNode("Slave & Occupant Management", ".", true) {

		@Override
		public DialogueNodeType getDialogueNodeType() {
			return DialogueNodeType.OCCUPANT_MANAGEMENT;
		}

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

			if(Main.game.getDialogueFlags().getSlaveTrader()!=null) {
				// Append for sale first:
				UtilText.nodeContentSB.append("
"
						+ "
Slaves For Sale
"

						+ getSlaveryHeader());
				int i=0;
				List<NPC> npcsPresent = new ArrayList<>(Main.game.getCharactersTreatingCellAsHome(Main.game.getPlayerCell()));

				npcsPresent.removeIf((npc) -> !npc.isSlave());

				for(NPC slave : npcsPresent) {
					if(!slave.getOwner().isPlayer()) {
						AffectionLevel affection = AffectionLevel.getAffectionLevelFromValue(slave.getAffection(Main.game.getPlayer()));
						ObedienceLevel obedience = ObedienceLevel.getObedienceLevelFromValue(slave.getObedienceValue());
						float affectionChange = slave.getDailyAffectionChange();
						float obedienceChange = slave.getDailyObedienceChange();
						GenericPlace place = Main.game.getPlayerCell().getPlace();

						UtilText.nodeContentSB.append(getSlaveryEntry(false, place, slave, affection, affectionChange, obedience, obedienceChange, i%2==0));
						i++;
					}
				}
				if(i==0) {
					UtilText.nodeContentSB.append("

No slaves for sale!

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

			} else { // Don't show occupants if trading slaves.
				// Friendly occupants:
				UtilText.nodeContentSB.append("
"
						+ "
Friendly Occupants
"

						+ getOccupantHeader());

				if(Main.game.getPlayer().getFriendlyOccupants().isEmpty()) {
					UtilText.nodeContentSB.append(
							"
"
									+"
You do not have anyone living with you...
"
							+ "
");

				} else {
					int i = 0;
					for(String id : Main.game.getPlayer().getFriendlyOccupants()) {
						try {
							NPC occupant = (NPC) Main.game.getNPCById(id);
							if(occupant.getHomeWorldLocation()!=WorldType.DOMINION) {
								AffectionLevel affection = AffectionLevel.getAffectionLevelFromValue(occupant.getAffection(Main.game.getPlayer()));
								ObedienceLevel obedience = ObedienceLevel.getObedienceLevelFromValue(occupant.getObedienceValue());
								float affectionChange = occupant.getDailyAffectionChange();
								float obedienceChange = occupant.getDailyObedienceChange();
								GenericPlace place = Main.game.getPlayerCell().getPlace();

								UtilText.nodeContentSB.append(getOccupantEntry(place, occupant, affection, affectionChange, obedience, obedienceChange, i%2==0));
								i++;
							}
						} catch (Exception e) {
							Util.logGetNpcByIdError("SLAVE_LIST.getResponse()", id);
						}
					}
				}

				UtilText.nodeContentSB.append(
						"
");
			}

			// Your slaves:
			UtilText.nodeContentSB.append("
"
					+ "
Slaves Owned
"

					+ getSlaveryHeader());

			if(Main.game.getPlayer().getSlavesOwned().isEmpty()) {
				UtilText.nodeContentSB.append(
						"
"
								+"
You do not own any slaves...
"
						+ "
");

			} else {
				int i = 0;
				for(String id : Main.game.getPlayer().getSlavesOwned()) {
					try {
						NPC slave = (NPC) Main.game.getNPCById(id);
						AffectionLevel affection = AffectionLevel.getAffectionLevelFromValue(slave.getAffection(Main.game.getPlayer()));
						ObedienceLevel obedience = ObedienceLevel.getObedienceLevelFromValue(slave.getObedienceValue());
						float affectionChange = slave.getDailyAffectionChange();
						float obedienceChange = slave.getDailyObedienceChange();
						GenericPlace place = Main.game.getPlayerCell().getPlace();

						UtilText.nodeContentSB.append(getSlaveryEntry(true, place, slave, affection, affectionChange, obedience, obedienceChange, i%2==0));
						i++;
					} catch (Exception e) {
						System.err.println("Main.game.getNPCById("+id+") returning null 2nd instance in method: SLAVE_LIST.getResponse()");
					}
				}
			}

			UtilText.nodeContentSB.append(
					"
");

			return UtilText.nodeContentSB.toString();
		}

		@Override
		public Response getResponse(int responseTab, int index) {
			if(characterSelected()!=null && characterSelected().isSlave()) {
				if (index == 1) {
					if(characterSelected() == null) {
						return new Response("Inspect", "You haven't selected anyone...", null);

					}
					return new Response("Inspect", "Enter the slave management screen.", SLAVE_MANAGEMENT_INSPECT);

				} else if (index == 2) {
					if(characterSelected() == null) {
						return new Response("Job", "You haven't selected anyone...", null);

					} else if(!characterSelected().getOwner().isPlayer()) {
						return new Response("Job", "You cannot manage the job of a slave you do not own!", null);
					}
					return new Response("Job", "Set this slave's job and work hours.", SLAVE_MANAGEMENT_JOBS);

				} else if (index == 3) {
					if(characterSelected() == null) {
						return new Response("Permissions", "You haven't selected anyone...", null);

					} else if(!characterSelected().getOwner().isPlayer()) {
						return new Response("Permissions", "You cannot manage the permissions of a slave you do not own!", null);
					}
					return new Response("Permissions", "Set this slave's permissions.", SLAVE_MANAGEMENT_PERMISSIONS);

				} else if (index == 4) {
					if(characterSelected() == null) {
						return new Response("Inventory", "You haven't selected anyone...", null);

					} else if(!characterSelected().getOwner().isPlayer()) {
						return new Response("Inventory", "You cannot manage the inventory of a slave you do not own!", null);
					}

					if(characterSelected().getOwner().isPlayer()) {
						return new ResponseEffectsOnly("Inventory", UtilText.parse(characterSelected(), "Manage [npc.namePos] inventory.")){
							@Override
							public void effects() {
								Main.mainController.openInventory(characterSelected(), InventoryInteraction.FULL_MANAGEMENT);
							}
						};
					} else {
						return new Response("Inventory", UtilText.parse(characterSelected(), "You can't manage [npc.namePos] inventory, as you don't own [npc.herHim]!"), null);
					}

				} else if(index == 5) {

					if(characterSelected() == null) {
						return new Response("Send to Kate", "You haven't selected anyone...", null);

					} else if(!characterSelected().getOwner().isPlayer()) {
						return new Response("Send to Kate", "You send slaves that you do not own to Kate!", null);

					} else if(Main.game.getDialogueFlags().hasFlag(DialogueFlagValue.kateIntroduced)) {
						return new Response("Send to Kate",
								UtilText.parse(characterSelected(), "Send [npc.name] to Kate's beauty salon, 'Succubi's secrets', to get [npc.her] appearance changed."),
								OccupantManagementDialogue.SLAVE_MANAGEMENT_COSMETICS_HAIR) {
									@Override
									public void effects() {
										BodyChanging.setTarget(characterSelected());
									}
								};
					} else {
						return new Response("Send to Kate", "You haven't met Kate yet!", null);
					}

				} else if (index == 6) {
					if(characterSelected() == null) {
						return new Response("Perk Tree", "You haven't selected anyone...", null);

					} else if(!characterSelected().getOwner().isPlayer()) {
						return new Response("Perk Tree", "You can't manage the perks of slaves that you do not own!", null);
					}
					return new Response("Perk Tree", "Spend your slave's perk points.", SLAVE_MANAGEMENT_PERKS);

				} else if(index == 0) {
					return new Response("Back", "Exit the slave management screen.", SLAVE_LIST) {
						@Override
						public void effects() {
							Main.game.getDialogueFlags().setSlaveryManagerSlaveSelected(null);
						}
						@Override
						public DialogueNode getNextDialogue() {
							if(slaveListManagementOverview) {
								return SLAVE_LIST_MANAGEMENT;
							} else {
								return Main.game.getDefaultDialogueNoEncounter();
							}
						}
					};

				} else {
					return null;
				}

			} else { // Friendly occupant:
				if (index == 1) {
					if(characterSelected() == null) {
						return new Response("Inspect", "You haven't selected anyone...", null);

					}
					return new Response("Inspect", UtilText.parse(characterSelected(), "inspect [npc.name]."), SLAVE_MANAGEMENT_INSPECT);

				} else if (index == 2) {
					return new Response("Job", "You cannot manage the job of a free-willed occupant. This option is only available for slaves.", null);

				} else if (index == 3) {
					return new Response("Permissions", "You cannot manage the permissions of a free-willed occupant. This option is only available for slaves.", null);

				} else if (index == 4) {
					if(characterSelected() == null) {
						return new Response("Inventory", "You haven't selected anyone...", null);

					}
					return new ResponseEffectsOnly("Inventory", UtilText.parse(characterSelected(), "Manage [npc.namePos] inventory.")){
						@Override
						public void effects() {
							Main.mainController.openInventory(characterSelected(), InventoryInteraction.FULL_MANAGEMENT);
						}
					};

				} else if(index == 5) {

					if(characterSelected() == null) {
						return new Response("Send to Kate", "You haven't selected anyone...", null);

					} else if(Main.game.getDialogueFlags().hasFlag(DialogueFlagValue.kateIntroduced)) {
						return new Response("Send to Kate",
								UtilText.parse(characterSelected(), "Send [npc.name] to Kate's beauty salon, 'Succubi's Secrets', to get [npc.her] appearance changed."),
								OccupantManagementDialogue.SLAVE_MANAGEMENT_COSMETICS_HAIR) {
									@Override
									public void effects() {
										BodyChanging.setTarget(characterSelected());
									}
								};
					} else {
						return new Response("Send to Kate", "You haven't met Kate yet!", null);
					}

				} else if (index == 6) {
					if(characterSelected() == null) {
						return new Response("Perk Tree", "You haven't selected anyone...", null);

					}
					return new Response("Perk Tree", UtilText.parse(characterSelected(), "Assign [npc.namePos] perk points."), SLAVE_MANAGEMENT_PERKS);

				} else if(index == 0) {
					return new Response("Back", "Exit the occupant management screen.", SLAVE_LIST) {
						@Override
						public void effects() {
							Main.game.getDialogueFlags().setSlaveryManagerSlaveSelected(null);
						}
						@Override
						public DialogueNode getNextDialogue() {
							if(slaveListManagementOverview) {
								return SLAVE_LIST_MANAGEMENT;
							} else {
								return Main.game.getDefaultDialogueNoEncounter();
							}
						}
					};

				} else {
					return null;
				}
			}
		}
	};

	public static final DialogueNode SLAVE_LIST_MANAGEMENT = new DialogueNode("Slave Management", ".", true) {

		@Override
		public DialogueNodeType getDialogueNodeType() {
			return DialogueNodeType.OCCUPANT_MANAGEMENT;
		}

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

		@Override
		public Response getResponse(int responseTab, int index) {
			if(index == 0) {
				return new Response("Back", "Exit the slave management screen.", OCCUPANT_OVERVIEW);

			} else {
				return SLAVE_LIST.getResponse(responseTab, index);
			}
		}
	};

	private static String getSlaveryHeader() {
		return "
"
					+ "
"
					+ "Slave"
				+ "
"
				+ "
"
					+ "Location"
				+ "
"
				+ "
"
					+ "Affection"
				+"
"
				+ "
"
					+ "Obedience"
				+"
"
				+ "
"
					+ "Value"
				+"
"
				+ "
"
					+ "Actions"
				+"
"
			+ "
";
	}

	private static String getOccupantHeader() {
		return "
"
					+ "
"
					+ "Occupant"
				+ "
"
				+ "
"
					+ "Location"
				+ "
"
				+ "
"
					+ "Affection"
				+"
"
				+ "
"
					+ "Obedience"
				+"
"
				+ "
"
					+ "Value"
				+"
"
				+ "
"
					+ "Actions"
				+"
"
			+ "
";
	}

	private static String getSlaveryEntry(boolean slaveOwned, GenericPlace place, NPC slave, AffectionLevel affection, float affectionChange, ObedienceLevel obedience, float obedienceChange, boolean alternateBackground) {
		miscDialogueSB.setLength(0);

		miscDialogueSB.append(
				"
"
						+ "
"
							+ ""+slave.getName()+"
"
							+ ""
								+Util.capitalizeSentence((slave.isFeminine()?slave.getSubspecies().getSingularFemaleName(slave):slave.getSubspecies().getSingularMaleName(slave)))+"
"
							+ ""+Util.capitalizeSentence(slave.getGender().getName())+""
						+ "
"
						+ "
"
							+ ""+slave.getLocationPlace().getName()+""
							+",
"
							+ ""+slave.getWorldLocation().getName()+""
						+ "
"
						+ "
"
							+ ""+slave.getAffection(Main.game.getPlayer())+ ""
							+ "
0?Color.GENERIC_GOOD:Color.GENERIC_BAD)).toWebHexString()+";'>"+(affectionChange>0?"+":"")
								+decimalFormat.format(affectionChange)+"/day"
							+ "
"
							+ ""+Util.capitalizeSentence(affection.getName())+""
						+"
"
						+ "
"
							+ ""+slave.getObedienceValue()+ ""
							+ "
0?Color.GENERIC_GOOD:Color.GENERIC_BAD)).toWebHexString()+";'>"+(obedienceChange>0?"+":"")
								+decimalFormat.format(obedienceChange)+"/day"
							+ "
"
							+ ""+Util.capitalizeSentence(obedience.getName())+""
						+"
"
						+ "
"
							+ (Main.game.getDialogueFlags().getSlaveTrader()!=null
								?(slaveOwned
										?UtilText.formatAsMoney((int) (slave.getValueAsSlave()*Main.game.getDialogueFlags().getSlaveTrader().getBuyModifier()), "b", Color.GENERIC_ARCANE)
										:UtilText.formatAsMoney((int) (slave.getValueAsSlave()*Main.game.getDialogueFlags().getSlaveTrader().getSellModifier()), "b", Color.GENERIC_ARCANE))
								:UtilText.formatAsMoney(slave.getValueAsSlave()))+"
"
							+ ""+Util.capitalizeSentence(slave.getSlaveJob().getName(slave))+"
"
							+ UtilText.formatAsMoney(slave.getSlaveJob().getFinalDailyIncomeAfterModifiers(slave))+"/day"
						+"
");

		if(slaveOwned) {
			miscDialogueSB.append("
"
					+ "

"+SVGImages.SVG_IMAGE_PROVIDER.getSlaveInspect()+"

"

					+ "

"+SVGImages.SVG_IMAGE_PROVIDER.getSlaveJob()+"

"

					+ "

"+SVGImages.SVG_IMAGE_PROVIDER.getSlavePermissions()+"

"

					+"

"+SVGImages.SVG_IMAGE_PROVIDER.getInventoryIcon()+"

"

					+ "<div "+((place.getCapacity()<=Main.game.getCharactersTreatingCellAsHome(Main.game.getPlayerCell()).size())
								|| !place.isSlaveCell()
								|| (slave.getLocation().equals(Main.game.getPlayer().getLocation()) && slave.getWorldLocation().equals(Main.game.getPlayer().getWorldLocation()))
										?" id='"+slave.getId()+"_TRANSFER_DISABLED' class='square-button big disabled'>
"+SVGImages.SVG_IMAGE_PROVIDER.getSlaveTransferDisabled()+"

"
										:" id='"+slave.getId()+"_TRANSFER' class='square-button big'>
"+SVGImages.SVG_IMAGE_PROVIDER.getSlaveTransfer()+"

"));

			if(Main.game.getDialogueFlags().getSlaveTrader()==null) {
				miscDialogueSB.append("

"+SVGImages.SVG_IMAGE_PROVIDER.getTransactionSellDisabled()+"

");
			} else {
				miscDialogueSB.append("

"+SVGImages.SVG_IMAGE_PROVIDER.getTransactionSell()+"

");
			}

			if(Main.game.getDialogueFlags().hasFlag(DialogueFlagValue.kateIntroduced)) {
				miscDialogueSB.append("

"+SVGImages.SVG_IMAGE_PROVIDER.getSlaveCosmetics()+"

");
			} else {
				miscDialogueSB.append("

"+SVGImages.SVG_IMAGE_PROVIDER.getSlaveCosmeticsDisabled()+"

");
			}

		} else { // Slave trader's slave:
			miscDialogueSB.append("
"
					+ "

"+SVGImages.SVG_IMAGE_PROVIDER.getSlaveInspect()+"

"

					+ "

"+SVGImages.SVG_IMAGE_PROVIDER.getSlaveJobDisabled()+"

"

					+ "

"+SVGImages.SVG_IMAGE_PROVIDER.getSlavePermissionsDisabled()+"

"

					+"

"+SVGImages.SVG_IMAGE_PROVIDER.getInventoryIconDisabled()+"

"

					+ "

"+SVGImages.SVG_IMAGE_PROVIDER.getSlaveTransferDisabled()+"

");

			if(Main.game.getPlayer().getMoney() < ((int) (slave.getValueAsSlave()*Main.game.getDialogueFlags().getSlaveTrader().getSellModifier()))) {
				miscDialogueSB.append("

"+SVGImages.SVG_IMAGE_PROVIDER.getTransactionBuyDisabled()+"

");
			} else {
				miscDialogueSB.append("

"+SVGImages.SVG_IMAGE_PROVIDER.getTransactionBuy()+"

");
			}

			miscDialogueSB.append("

"+SVGImages.SVG_IMAGE_PROVIDER.getSlaveCosmeticsDisabled()+"

");
		}

		miscDialogueSB.append("

");

		return miscDialogueSB.toString();
	}

	private static String getOccupantEntry(GenericPlace place, NPC occupant, AffectionLevel affection, float affectionChange, ObedienceLevel obedience, float obedienceChange, boolean alternateBackground) {
		miscDialogueSB.setLength(0);

		miscDialogueSB.append(
				"
"
						+ "
"
							+ ""+occupant.getName()+"
"
							+ ""
								+Util.capitalizeSentence((occupant.isFeminine()?occupant.getSubspecies().getSingularFemaleName(occupant):occupant.getSubspecies().getSingularMaleName(occupant)))+"
"
							+ ""+Util.capitalizeSentence(occupant.getGender().getName())+""
						+ "
"
						+ "
"
							+ (occupant.getWorldLocation()==WorldType.EMPTY
								?"At Work"
										+",
"
										+ ""+WorldType.DOMINION.getName()+""
								:""+occupant.getLocationPlace().getName()+""
									+",
"
									+ ""+occupant.getWorldLocation().getName()+"")
						+ "
"
						+ "
"
							+ ""+occupant.getAffection(Main.game.getPlayer())+ ""
							+ "
0?Color.GENERIC_GOOD:Color.GENERIC_BAD)).toWebHexString()+";'>"+(affectionChange>0?"+":"")
								+decimalFormat.format(affectionChange)+"/day"
							+ "
"
							+ ""+Util.capitalizeSentence(affection.getName())+""
						+"
"
						+ "
"
							+ ""+occupant.getObedienceValue()+ ""
							+ "
0?Color.GENERIC_GOOD:Color.GENERIC_BAD)).toWebHexString()+";'>"+(obedienceChange>0?"+":"")
								+decimalFormat.format(obedienceChange)+"/day"
							+ "
"
							+ ""+Util.capitalizeSentence(obedience.getName())+""
						+"
"
						+ "
"
							+ ""+Util.capitalizeSentence(occupant.getSlaveJob().getName(occupant))+"
"
							+ UtilText.formatAsMoney(occupant.getSlaveJob().getFinalDailyIncomeAfterModifiers(occupant))+"/day"
						+"
"

				+ "
"

				+ "

"+SVGImages.SVG_IMAGE_PROVIDER.getSlaveInspect()+"

"

				+ "

"+SVGImages.SVG_IMAGE_PROVIDER.getSlaveJobDisabled()+"

"

				+ "

"+SVGImages.SVG_IMAGE_PROVIDER.getSlavePermissionsDisabled()+"

"

				+"

"+SVGImages.SVG_IMAGE_PROVIDER.getInventoryIcon()+"

"

				+ "<div "+((place.getCapacity()<=Main.game.getCharactersTreatingCellAsHome(Main.game.getPlayerCell()).size())
							|| place.isSlaveCell()
							|| occupant.getHomeWorldLocation()==WorldType.DOMINION
							|| (occupant.getLocation().equals(Main.game.getPlayer().getLocation()) && occupant.getWorldLocation().equals(Main.game.getPlayer().getWorldLocation()))
									?" id='"+occupant.getId()+"_TRANSFER_DISABLED' class='square-button big disabled'>
"+SVGImages.SVG_IMAGE_PROVIDER.getSlaveTransferDisabled()+"

"
									:" id='"+occupant.getId()+"_TRANSFER' class='square-button big'>
"+SVGImages.SVG_IMAGE_PROVIDER.getSlaveTransfer()+"

"));

		if(Main.game.getDialogueFlags().hasFlag(DialogueFlagValue.kateIntroduced)) {
			miscDialogueSB.append("

"+SVGImages.SVG_IMAGE_PROVIDER.getSlaveCosmetics()+"

");
		} else {
			miscDialogueSB.append("

"+SVGImages.SVG_IMAGE_PROVIDER.getSlaveCosmeticsDisabled()+"

");
		}

		miscDialogueSB.append("

");

		return miscDialogueSB.toString();
	}

	private static StringBuilder headerSB = new StringBuilder();
	private static String getSlaveInformationHeader(NPC character) {
		headerSB.setLength(0);
		AffectionLevel affection = AffectionLevel.getAffectionLevelFromValue(character.getAffection(Main.game.getPlayer()));
		ObedienceLevel obedience = ObedienceLevel.getObedienceLevelFromValue(character.getObedienceValue());
		float affectionChange = character.getDailyAffectionChange();
		float obedienceChange = character.getDailyObedienceChange();

		headerSB.append(
				"
"

					// Core naming information:
					+"
"
						+ "
"
							+ "Slave's Name and Surname"
						+ "
"
						+ "
"
							+ UtilText.parse(character, "What [npc.she] calls you")
						+ "
"
					+ "
"
					+"
"
						+ "
"
							+ "<form style='float:left; width:39%; margin:0; padding:0;'><input type='text' id='slaveNameInput' value='"+ UtilText.parseForHTMLDisplay(character.getName())+ "' style='width:100%; margin:0; padding:0;'></form>"
							+ "
"
								+ "✓"
							+ "
"
							+ "<form style='float:left; width:40%; margin:0; padding:0;'><input type='text' id='slaveSurnameInput' value='"+ UtilText.parseForHTMLDisplay(character.getSurname())+ "' style='width:100%; margin:0; padding:0;'></form>"
							+ "
"
								+ "✓"
							+ "
"
						+ "
"
						+ "
"
							+ "<form style='float:left; width:50%; margin:0; padding:0;'><input type='text' id='slaveToPlayerNameInput' value='"+ UtilText.parseForHTMLDisplay(character.getPetName(Main.game.getPlayer()))
								+ "' style='width:100%; margin:0; padding:0;'></form>"
							+ "
"
								+ "✓"
							+ "
"
							+ "
"
								+ "All Slaves"
						+ "
"
					+ "
"

					// Extra core information:
					+"
"
						+ "
"
							+ "Location"
						+ "
"
						+ "
"
							+ "Affection"
						+"
"
						+ "
"
							+ "Obedience"
						+"
"
						+ "
"
							+ "Income"
						+"
"
						+ "
"
							+ "Value"
						+ "
"
					+ "
"
					+"
"
						+"
"
							+ ""+character.getLocationPlace().getName()+""
							+",
"
							+ ""+character.getWorldLocation().getName()+""
						+ "
"
						+ "
"
							+ ""+character.getAffection(Main.game.getPlayer())+ "" //TODO
							+ "
0?Color.GENERIC_GOOD:Color.GENERIC_BAD)).toWebHexString()+";'>"+(affectionChange>0?"+":"")
								+decimalFormat.format(affectionChange)+"/day"
							+ "
"
							+ ""+Util.capitalizeSentence(affection.getName())+""
						+"
"
						+ "
"
							+ ""+character.getObedienceValue()+ ""
							+ "
0?Color.GENERIC_GOOD:Color.GENERIC_BAD)).toWebHexString()+";'>"+(obedienceChange>0?"+":"")
								+decimalFormat.format(obedienceChange)+"/day"
							+ "
"
							+ ""+Util.capitalizeSentence(obedience.getName())+""
						+"
"
						+ "
"
							+ UtilText.formatAsMoney(character.getSlaveJob().getFinalDailyIncomeAfterModifiers(character))+"/day"
						+"
"
						+ "
"
							+ UtilText.formatAsMoney(character.getValueAsSlave())
						+"
"
					+ "
");

		// Job:
		headerSB.append("
"
				+ "Job: "+Util.capitalizeSentence(character.getSlaveJob().getName(character))+"
");
		int count=0;
		for(SlaveJobSetting setting : character.getSlaveJobSettings()) {
			headerSB.append((count==0?"":", ")+""+setting.getName()+"");
			count++;
		}
		if(count>0) {
			headerSB.append(".");
		}
		headerSB.append("
");

		// Permissions:
		headerSB.append("
"
				+ "Permissions:
");
		int permissionCount=0;
		for(SlavePermission permission : SlavePermission.values()) {
			for(SlavePermissionSetting setting : permission.getSettings()) {
				if(character.getSlavePermissionSettings().get(permission).contains(setting)) {
					headerSB.append((permissionCount==0?"":", ")+""+setting.getName()+"");
					permissionCount++;
				}
			}
		}
		headerSB.append(".
");

		headerSB.append("
"
				+ "
");

		return headerSB.toString();
	}

	/**
	 * Use getSlaveryManagementDetailedDialogue(NPC slave) to initialize this!!!
	 */
	public static final DialogueNode SLAVE_MANAGEMENT_INSPECT = new DialogueNode("Slave Management", ".", true) {

		@Override
		public DialogueNodeType getDialogueNodeType() {
			return DialogueNodeType.OCCUPANT_MANAGEMENT;
		}

		@Override
		public String getLabel() {
			return UtilText.parse(characterSelected(), "Inspecting [npc.Name]");
		}

		@Override
		public String getContent() {
			NPC character = characterSelected();

			UtilText.nodeContentSB.setLength(0);

			if(character.isSlave() && character.getOwner().isPlayer()) {
				UtilText.nodeContentSB.append(getSlaveInformationHeader(character));
			}

			UtilText.nodeContentSB.append(
					"
"
							+ "
Inspection
"
							+ "
"
								+character.getCharacterInformationScreen()
							+"
"
					+"
"
					+ "

");

			return UtilText.parse(character, UtilText.nodeContentSB.toString());
		}

		@Override
		public Response getResponse(int responseTab, int index) {
			if (index == 1) {
				return new Response("Inspect", UtilText.parse(characterSelected(), "You are already inspecting [npc.name]."), null);
			}

			return SLAVE_LIST.getResponse(responseTab, index);
		}

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

	public static final DialogueNode SLAVE_MANAGEMENT_JOBS = new DialogueNode("Slave Management", ".", true) {

		@Override
		public DialogueNodeType getDialogueNodeType() {
			return DialogueNodeType.OCCUPANT_MANAGEMENT;
		}

		@Override
		public String getLabel() {
			return UtilText.parse(characterSelected(), "[npc.NamePos] Job");
		}

		@Override
		public String getContent() {
			NPC character = characterSelected();
			ObedienceLevel obedience = ObedienceLevel.getObedienceLevelFromValue(character.getObedienceValue());
			float affectionChange = character.getDailyAffectionChange();
			float obedienceChange = character.getDailyObedienceChange();

			UtilText.nodeContentSB.setLength(0);

			UtilText.nodeContentSB.append(getSlaveInformationHeader(character));

			// Job hours
			UtilText.nodeContentSB.append(
					"
"
						+ "
Work Hours
"
						+ "
");
							for(int i=0 ; i< 24; i++) {
								UtilText.nodeContentSB.append("
"+String.format("%02d", i)+":00
");
							}

			UtilText.nodeContentSB.append(
							"
Presets
"
							+ "
");
								for(SlaveJobHours preset : SlaveJobHours.values()) {
									UtilText.nodeContentSB.append("
"+preset.getName()+"
");
								}
			UtilText.nodeContentSB.append(
							"
"
						+ "
"
					+ "
");

			// Jobs:
			// TODO description box explaining that setting just influence random events
			UtilText.nodeContentSB.append(
					"
"
						+ "
Jobs
"
						+"
"
							+ "
"
								+ "Job"
							+ "
"
							+ "
"
								+ "Workers"
							+"
"
							+ "
"
								+ "Affection"
							+"
"
							+ "
"
								+ "Obedience"
							+"
"
							+ "
"
								+ "Income"
										+ " (+Obedience Bonus)"
							+"
"
							+ "
"
								+ "Actions"
							+"
"
						+ "
");

			for(SlaveJob job : SlaveJob.values()) {
				affectionChange = job.getAffectionGain(character);
				obedienceChange = job.getObedienceGain(character);
				int income = job.getFinalHourlyIncomeAfterModifiers(character);
				boolean isCurrentJob = character.getSlaveJob() == job;

				UtilText.nodeContentSB.append(
						"
"
							+ "
"
								+ "
"+SVGImages.SVG_IMAGE_PROVIDER.getInformationIcon()+"
"
							+ "
"
							+"
"
								+ (isCurrentJob
									? "[style.boldGood("+Util.capitalizeSentence(job.getName(character))+")]"
									: (job.isAvailable(character)
										? Util.capitalizeSentence(job.getName(character))
										: "[style.colorBad("+Util.capitalizeSentence(job.getName(character))+")]"))
							+ "
"
							+ "
"
								+ Main.game.getPlayer().getSlavesWorkingJob(job)+"/"+(job.getSlaveLimit()<0?"∞":job.getSlaveLimit())
							+"
"
							+ "
"
								+ (affectionChange>0
										?"+"+decimalFormat.format(affectionChange)+ ""
										:(affectionChange<0
												?""+decimalFormat.format(affectionChange)+ ""
												:"[style.colorDisabled(0)]"))+"/hour"
							+"
"
							+ "
"
								+ (obedienceChange>0
										?"+"+decimalFormat.format(obedienceChange)+ ""
										:(obedienceChange<0
												?""+decimalFormat.format(obedienceChange)+ ""
												:"[style.colorDisabled(0)]"))+"/hour"
							+"
"
							+ "
"
								+ UtilText.formatAsMoney(job.getIncome())
								+ " + ("
								+ (job.getObedienceIncomeModifier()>0
										?"[style.colorObedience("+job.getObedienceIncomeModifier()+")]"
										:"[style.colorDisabled("+job.getObedienceIncomeModifier()+")]")
										+ "*"+character.getObedienceValue()+")"
								+ " = "+UtilText.formatAsMoney(income, "b", (income>0?null:Color.GENERIC_BAD))+"/hour"
							+"
"
							+ "
"
								+ (!job.isAvailable(character) || isCurrentJob
										?"

"
											+(isCurrentJob?SVGImages.SVG_IMAGE_PROVIDER.getResponseOption():SVGImages.SVG_IMAGE_PROVIDER.getResponseOptionDisabled())+"

"
										:"

"+SVGImages.SVG_IMAGE_PROVIDER.getResponseOptionDisabled()+"

")
							+"
"
							+ (!isCurrentJob && !job.isAvailable(character)
								?"
"
										+ ""
											+ "[style.colorBad("+job.getAvailabilityText(character)+")]"
										+""
									+ "
"
								:""));

				// Job Settings:
				if(isCurrentJob) {
					for(SlaveJobSetting setting : job.getMutualSettings()) {
						boolean settingActive = character.getSlaveJobSettings().contains(setting);

						UtilText.nodeContentSB.append("
"
														+"
"
															+ setting.getName()
														+ "
"
														+"
"
															+ ""+setting.getDescription()+""
														+ "
"
														+ "
"
															+ (!isCurrentJob
																	?"

"+SVGImages.SVG_IMAGE_PROVIDER.getResponseUnlockedDisabled()+"

"
																	: (settingActive
																			?"

"+SVGImages.SVG_IMAGE_PROVIDER.getResponseUnlocked()+"

"
																			:"

"+SVGImages.SVG_IMAGE_PROVIDER.getResponseUnlockedDisabled()+"

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

					for(Entry<String, List<SlaveJobSetting>> entry : job.getMutuallyExclusiveSettings().entrySet()) {
						UtilText.nodeContentSB.append("
"
														+ "
"
															+ Util.capitalizeSentence(entry.getKey())
														+"
");

						for(SlaveJobSetting setting : entry.getValue()) {
							boolean settingActive = character.getSlaveJobSettings().contains(setting);

							UtilText.nodeContentSB.append("
"
																+ setting.getName()
															+ "
"
															+"
"
																+ ""+setting.getDescription()+""
															+ "
"
															+ "
"
																+ (!isCurrentJob
																		?"

"+SVGImages.SVG_IMAGE_PROVIDER.getResponseOptionDisabled()+"

"
																		: (settingActive
																				?"

"+SVGImages.SVG_IMAGE_PROVIDER.getResponseOption()+"

"
																				:"

"+SVGImages.SVG_IMAGE_PROVIDER.getResponseOptionDisabled()+"

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

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

			UtilText.nodeContentSB.append("

");
			return UtilText.parse(character, UtilText.nodeContentSB.toString());
		}

		@Override
		public Response getResponse(int responseTab, int index) {
			if (index == 2) {
				return new Response("Job", "You are already viewing the jobs screen.", null);

			}

			return SLAVE_LIST.getResponse(responseTab, index);
		}

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

	public static final DialogueNode SLAVE_MANAGEMENT_PERMISSIONS = new DialogueNode("Slave Management", ".", true) {

		@Override
		public DialogueNodeType getDialogueNodeType() {
			return DialogueNodeType.OCCUPANT_MANAGEMENT;
		}

		@Override
		public String getLabel() {
			return UtilText.parse(characterSelected(), "[npc.Name] - Permissions");
		}

		@Override
		public String getContent() {
			NPC character = characterSelected();

			UtilText.nodeContentSB.setLength(0);

			UtilText.nodeContentSB.append(getSlaveInformationHeader(character));

			// Permissions:
			UtilText.nodeContentSB.append(
					"
"
						+ "
Permissions
");

			for(SlavePermission permission : SlavePermission.values()) {
				UtilText.nodeContentSB.append(
						"
"
								+ "
"+permission.getName()+"
");

				// Job Settings:
				for(SlavePermissionSetting setting : permission.getSettings()) {
					boolean settingActive = character.getSlavePermissionSettings().get(permission).contains(setting);

					UtilText.nodeContentSB.append("
"
													+"
"
														+ setting.getName()
													+ "
"
													+"
"
														+ ""+setting.getDescription()+""
													+ "
"
													+ "
"
														+ (permission.isMutuallyExclusiveSettings()
																? (settingActive
																	?"

"+SVGImages.SVG_IMAGE_PROVIDER.getResponseOption()+"

"
																	:"

"+SVGImages.SVG_IMAGE_PROVIDER.getResponseOptionDisabled()+"

")
																:(settingActive
																		?"

"+SVGImages.SVG_IMAGE_PROVIDER.getResponseUnlocked()+"

"
																		:"

"+SVGImages.SVG_IMAGE_PROVIDER.getResponseUnlockedDisabled()+"

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

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

");

			return UtilText.parse(character, UtilText.nodeContentSB.toString());
		}

		@Override
		public Response getResponse(int responseTab, int index) {
			if (index == 3) {
				return new Response("Permissions", "You are already viewing the permissions screen.", null);

			}

			return SLAVE_LIST.getResponse(responseTab, index);
		}

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

	private static Map<BodyCoveringType, List<String>> CoveringsNamesMap;

	private static Response getCosmeticsResponse(int responseTab, int index) {
		if (index == 1) {
			if(!BodyChanging.getTarget().getBodyMaterial().isAbleToWearMakeup()) {
				return new Response("Makeup", UtilText.parse(BodyChanging.getTarget(), "As [npc.namePos] body is made of "+Main.game.getPlayer().getBodyMaterial().getName()+", Kate is unable to apply any makeup!"), null);

			} else {
				return new Response("Makeup",
						"Kate offers a wide range of different cosmetic services, and several pages of the brochure are devoted to images displaying different styles and colors of lipstick, nail polish, and other forms of makeup.",
						SLAVE_MANAGEMENT_COSMETICS_MAKEUP);
			}

		} else if (index == 2) {
			return new Response("Hair",
					"There's a double-page spread of all the different dyes, styles, and lengths of hair that Kate's able to work with.",
					SLAVE_MANAGEMENT_COSMETICS_HAIR);

		} else if (index == 3) {
				return new Response("Piercings",
						"Kate offers a wide range of different piercings.",
						SLAVE_MANAGEMENT_COSMETICS_PIERCINGS);

		} else if (index == 4) {
				return new Response("Eyes",
						"There's a special page near the front of the brochure, advertising Kate's ability to recolour a person's eyes."
						+ " Just like skin recolourings, this is quite demanding on her aura, and is therefore very expensive.", SLAVE_MANAGEMENT_COSMETICS_EYES);

		} else if (index == 5) {
			return new Response("Coverings",
					"There's a special page in the middle of the brochure, advertising Kate's special ability to harness the arcane in order to recolour a person's skin or fur."
					+ " Apparently, this is quite demanding on her aura, and is therefore very expensive.",
					SLAVE_MANAGEMENT_COSMETICS_COVERINGS){
				@Override
				public void effects() {

					CoveringsNamesMap = new LinkedHashMap<>();

					if(BodyChanging.getTarget().getBodyMaterial()==BodyMaterial.SLIME) {
						CoveringsNamesMap.put(BodyCoveringType.SLIME, Util.newArrayListOfValues("SLIME"));
					} else {
						for(BodyPartInterface bp : BodyChanging.getTarget().getAllBodyParts()){
							if(bp.getBodyCoveringType(BodyChanging.getTarget())!=null
									&& !(bp instanceof Hair)
									&& !(bp instanceof Eye)) {

								String name = bp.getName(BodyChanging.getTarget());
								if(bp instanceof Skin) {
									name = "torso";
								} else if(bp instanceof Vagina) {
									name = "vagina";
								}

								if(CoveringsNamesMap.containsKey(bp.getBodyCoveringType(BodyChanging.getTarget()))) {
									CoveringsNamesMap.get(bp.getBodyCoveringType(BodyChanging.getTarget())).add(name);
								} else {
									CoveringsNamesMap.put(bp.getBodyCoveringType(BodyChanging.getTarget()), Util.newArrayListOfValues(name));
								}
							}
						}
						CoveringsNamesMap.put(BodyCoveringType.ANUS, Util.newArrayListOfValues("anus"));
						CoveringsNamesMap.put(BodyCoveringType.MOUTH, Util.newArrayListOfValues("mouth"));
						CoveringsNamesMap.put(BodyCoveringType.NIPPLES, Util.newArrayListOfValues("nipples"));
						CoveringsNamesMap.put(BodyCoveringType.TONGUE, Util.newArrayListOfValues("tongue"));
					}
				}
			};

		} else if (index == 6) {
			return new Response("Other", "Kate can offer other miscellaneous services, such as anal bleaching.", SLAVE_MANAGEMENT_COSMETICS_OTHER);

		} else if (index == 7) {
			return new Response("Tattoos", "Most of the brochure is taken up with drawings and photographs displaying Kate's considerable artistic talents."
					+ " She's even able to apply arcane-enchanted tattoos, but they look to be very expensive...", SLAVE_MANAGEMENT_TATTOOS);

		} else if (index == 0) {
			return new Response("Back", "Return to the slave management screen.",  SLAVE_LIST) {
				@Override
				public DialogueNode getNextDialogue() {
					if(slaveListManagementOverview) {
						return SLAVE_LIST_MANAGEMENT;
					} else {
						return SLAVE_LIST;
					}
				}
				@Override
				public void effects() {
					Main.game.getDialogueFlags().setSlaveryManagerSlaveSelected(null);
				}
			};

		} else {
			return null;
		}
	}

	public static final DialogueNode SLAVE_MANAGEMENT_COSMETICS_MAKEUP = new DialogueNode("Slave Management", ".", true) {

		@Override
		public DialogueNodeType getDialogueNodeType() {
			return DialogueNodeType.OCCUPANT_MANAGEMENT;
		}

		@Override
		public String getLabel() {
			return UtilText.parse(characterSelected(), "[npc.Name] - Makeup");
		}

		@Override
		public String getHeaderContent() {

			return UtilText.parse(BodyChanging.getTarget(),
					"
"
						+ "You currently have "+UtilText.formatAsMoney(Main.game.getPlayer().getMoney(), "span")
					+ "
"
					+CharacterModificationUtils.getKatesDivCoveringsNew(
							true, BodyCoveringType.MAKEUP_BLUSHER, "Blusher", "Blusher (also called rouge) is used to color the cheeks so as to provide a more youthful appearance, and to emphasize the cheekbones.", true, true)

					+CharacterModificationUtils.getKatesDivCoveringsNew(
							true, BodyCoveringType.MAKEUP_LIPSTICK, "Lipstick", "Lipstick is used to provide color, texture, and protection to the wearer's lips.", true, true)

					+CharacterModificationUtils.getKatesDivCoveringsNew(
							true, BodyCoveringType.MAKEUP_EYE_LINER, "Eyeliner", "Eyeliner is applied around the contours of the eyes to help to define shape or highlight different features.", true, true)

					+CharacterModificationUtils.getKatesDivCoveringsNew(
							true, BodyCoveringType.MAKEUP_EYE_SHADOW, "Eye shadow", "Eye shadow is used to make the wearer's eyes stand out or look more attractive.", true, true)

					+CharacterModificationUtils.getKatesDivCoveringsNew(
							true, BodyCoveringType.MAKEUP_NAIL_POLISH_HANDS, "Nail polish", "Nail polish is used to color and protect the nails on [npc.namePos] [npc.hands].", true, true)

					+CharacterModificationUtils.getKatesDivCoveringsNew(
							true, BodyCoveringType.MAKEUP_NAIL_POLISH_FEET, "Toenail polish", "Toenail polish is used to color and protect the nails on [npc.namePos] [npc.feet].", true, true)
					);
		}

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

		@Override
		public Response getResponse(int responseTab, int index) {
			if (index == 1) {
				return new Response("Makeup",
						UtilText.parse(BodyChanging.getTarget(), "You are already changing [npc.namePos] makeup!"),
						null);

			} else {
				return getCosmeticsResponse(responseTab, index);
			}
		}
	};

	public static final DialogueNode SLAVE_MANAGEMENT_COSMETICS_HAIR = new DialogueNode("Slave Management", ".", true) {

		@Override
		public DialogueNodeType getDialogueNodeType() {
			return DialogueNodeType.OCCUPANT_MANAGEMENT;
		}

		@Override
		public String getLabel() {
			return UtilText.parse(characterSelected(), "[npc.Name] - Hair");
		}

		@Override
		public String getHeaderContent() {

			return UtilText.parse(BodyChanging.getTarget(),
					"
"
						+ "You currently have "+UtilText.formatAsMoney(Main.game.getPlayer().getMoney(), "span")
					+ "
"
					+CharacterModificationUtils.getKatesDivHairLengths(true, "Hair Length", "Hair length determines what hair styles [npc.namePos] able to have. The longer [npc.her] [npc.hair], the more styles are available.")

					+CharacterModificationUtils.getKatesDivHairStyles(true, "Hair Style", "Hair style availability is determined by [npc.namePos] [npc.hair] length.")

					+(BodyChanging.getTarget().getBodyMaterial()!=BodyMaterial.SLIME
						?CharacterModificationUtils.getKatesDivCoveringsNew(
								true, BodyChanging.getTarget().getCovering(BodyChanging.getTarget().getHairCovering()).getType(),
								UtilText.parse(BodyChanging.getTarget(), "[npc.Hair] Color"),
								"All hair recolourings are permanent, so if you want to change your colour again at a later time, you'll have to visit Kate again.", true, true)
						:"")
					);
		}

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

		@Override
		public Response getResponse(int responseTab, int index) {
			if (index == 2) {
				return new Response("Hair",
						UtilText.parse(BodyChanging.getTarget(), "You are already changing [npc.namePos] hair!"),
						null);

			} else {
				return getCosmeticsResponse(responseTab, index);
			}
		}
	};

	public static final DialogueNode SLAVE_MANAGEMENT_COSMETICS_PIERCINGS = new DialogueNode("Slave Management", ".", true) {

		@Override
		public DialogueNodeType getDialogueNodeType() {
			return DialogueNodeType.OCCUPANT_MANAGEMENT;
		}

		@Override
		public String getLabel() {
			return UtilText.parse(characterSelected(), "[npc.Name] - Piercings");
		}

		@Override
		public String getHeaderContent() {

			return UtilText.parse(BodyChanging.getTarget(),
					"
"
						+ "You currently have "+UtilText.formatAsMoney(Main.game.getPlayer().getMoney(), "span")
					+ "
"

				+CharacterModificationUtils.getKatesDivPiercings(PiercingType.EAR, "Ear Piercing", "Ears are the most common area of the body that are pierced, and enable the equipping of earrings and other ear-related jewelry.")

				+CharacterModificationUtils.getKatesDivPiercings(PiercingType.NOSE, "Nose Piercing", "Having a nose piercing will allow [npc.name] to equip jewelry such as nose rings or studs.")

				+CharacterModificationUtils.getKatesDivPiercings(PiercingType.LIP, "Lip Piercing", "A lip piercing will allow [npc.name] to wear lip rings.")

				+CharacterModificationUtils.getKatesDivPiercings(PiercingType.NAVEL, "Navel Piercing", "Getting [npc.her] navel (belly button) pierced will allow [npc.name] to equip navel-related jewelry.")

				+CharacterModificationUtils.getKatesDivPiercings(PiercingType.TONGUE, "Tongue Piercing", "Getting a tongue piercing will allow [npc.name] to equip tongue bars.")

				+CharacterModificationUtils.getKatesDivPiercings(PiercingType.NIPPLE, "Nipple Piercing", "Nipple piercings will allow [npc.name] to equip nipple bars.")

				+CharacterModificationUtils.getKatesDivPiercings(PiercingType.PENIS, "Penis Piercing", "Having a penis piercing will allow [npc.name] to equip penis-related jewelry.")

				+CharacterModificationUtils.getKatesDivPiercings(PiercingType.VAGINA, "Vagina Piercing", "Having a vagina piercing will allow [npc.name] to equip vagina-related jewelry."));
		}

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

		@Override
		public Response getResponse(int responseTab, int index) {
			if (index == 3) {
				return new Response("Piercings",
						UtilText.parse(BodyChanging.getTarget(), "You are already changing [npc.namePos] piercings!"),
						null);

			} else {
				return getCosmeticsResponse(responseTab, index);
			}
		}
	};

	public static final DialogueNode SLAVE_MANAGEMENT_COSMETICS_EYES = new DialogueNode("Slave Management", ".", true) {

		@Override
		public DialogueNodeType getDialogueNodeType() {
			return DialogueNodeType.OCCUPANT_MANAGEMENT;
		}

		@Override
		public String getLabel() {
			return UtilText.parse(characterSelected(), "[npc.Name] - Eyes");
		}

		@Override
		public String getHeaderContent() {

			return UtilText.parse(BodyChanging.getTarget(),
					"
"
						+ "You currently have "+UtilText.formatAsMoney(Main.game.getPlayer().getMoney(), "span")
					+ "
"

					+CharacterModificationUtils.getKatesDivCoveringsNew(
							true, BodyChanging.getTarget().getEyeCovering(), "Irises", "The iris is the colored part of the eye that's responsible for controlling the diameter and size of the pupil.", true, true)

					+CharacterModificationUtils.getKatesDivCoveringsNew(
							true, BodyCoveringType.EYE_PUPILS, "Pupils", "The pupil is a hole located in the center of the iris that allows light to strike the retina.", true, true)

					+CharacterModificationUtils.getKatesDivCoveringsNew(
							true, BodyCoveringType.EYE_SCLERA, "Sclera", "The sclera is the (typically white) part of the eye that surrounds the iris.", true, true));
		}

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

		@Override
		public Response getResponse(int responseTab, int index) {
			if (index == 4) {
				return new Response("Eyes",
						UtilText.parse(BodyChanging.getTarget(), "You are already changing [npc.namePos] eyes!"),
						null);

			} else {
				return getCosmeticsResponse(responseTab, index);
			}
		}
	};

	public static final DialogueNode SLAVE_MANAGEMENT_COSMETICS_COVERINGS = new DialogueNode("Slave Management", ".", true) {

		@Override
		public DialogueNodeType getDialogueNodeType() {
			return DialogueNodeType.OCCUPANT_MANAGEMENT;
		}

		@Override
		public String getLabel() {
			return UtilText.parse(characterSelected(), "[npc.Name] - Coverings");
		}

		@Override
		public String getHeaderContent() {
			UtilText.nodeContentSB.setLength(0);
			UtilText.nodeContentSB.append("
"
									+ "You currently have "+UtilText.formatAsMoney(Main.game.getPlayer().getMoney(), "span")
								+ "
");

			for(Entry<BodyCoveringType, List<String>> entry : CoveringsNamesMap.entrySet()){
				BodyCoveringType bct = entry.getKey();

				String title = Util.capitalizeSentence(bct.getName(BodyChanging.getTarget()));
				String description = "This is the "+bct.getName(BodyChanging.getTarget())+" that's currently covering [npc.namePos] "+Util.stringsToStringList(entry.getValue(), false)+".";

				if(bct == BodyCoveringType.ANUS) {
					title = "Anus";
					description = "This is the skin that's currently covering [npc.namePos] anal rim. The secondary color determines what [npc.her] anus's inner-walls look like.";

				} else if(bct == BodyCoveringType.VAGINA) {
					title = "Vagina";
					description = "This is the skin that's currently covering [npc.namePos] labia. The secondary color determines what [npc.her] vagina's inner-walls look like.";

				} else if(bct == BodyCoveringType.PENIS) {
					title = "Penis";
					description = "This is the skin that's currently covering [npc.namePos] penis. The secondary color determines what the inside of [npc.her] urethra looks like (if it's fuckable).";

				} else if(bct == BodyCoveringType.NIPPLES) {
					title = "Nipples";
					description = "This is the skin that's currently covering [npc.namePos] nipples and areolae. The secondary color determines what [npc.her] nipples' inner-walls look like (if they are fuckable).";

				} else if(bct == BodyCoveringType.MOUTH) {
					title = "Lips & Throat";
					if(BodyChanging.getTarget().getFaceType() == FaceType.HARPY) {
						description = "This is the color of [npc.namePos] beak. The secondary color determines what the insides of [npc.her] mouth and throat look like.";
					} else {
						description = "This is the skin that's currently covering [npc.namePos] lips. The secondary color determines what the insides of [npc.her] mouth and throat look like.";
					}

				} else if(bct == BodyCoveringType.TONGUE) {
					title = "Tongue";
					description = "This is the skin that's currently covering [npc.namePos] tongue.";
				}

				UtilText.nodeContentSB.append(CharacterModificationUtils.getKatesDivCoveringsNew(
						true,
						bct,
						title,
						description,
						true,
						true));
			}

			return UtilText.parse(BodyChanging.getTarget(), UtilText.nodeContentSB.toString());
		}

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

		@Override
		public Response getResponse(int responseTab, int index) {
			if (index == 5) {
				return new Response("Coverings",
						UtilText.parse(BodyChanging.getTarget(), "You are already changing [npc.namePos] coverings!"),
						null);

			} else {
				return getCosmeticsResponse(responseTab, index);
			}
		}
	};

	public static final DialogueNode SLAVE_MANAGEMENT_COSMETICS_OTHER = new DialogueNode("Slave Management", ".", true) {

		@Override
		public DialogueNodeType getDialogueNodeType() {
			return DialogueNodeType.OCCUPANT_MANAGEMENT;
		}

		@Override
		public String getLabel() {
			return UtilText.parse(characterSelected(), "[npc.Name] - Other");
		}

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

			UtilText.nodeContentSB.append("
"
						+ "You currently have "+UtilText.formatAsMoney(Main.game.getPlayer().getMoney(), "span")
					+ "
"
					+CharacterModificationUtils.getKatesDivAnalBleaching("Anal bleaching", "Anal bleaching is the process of lightening the color of the skin around the anus, to make it more uniform with the surrounding area.")

					+(Main.game.isFacialHairEnabled()
							? CharacterModificationUtils.getKatesDivFacialHair("Facial hair", "The body hair found on [npc.namePos] face."
									+ (Main.game.isFemaleFacialHairEnabled() ? "" : " Feminine characters cannot grow facial hair."))
							:"")

					+(Main.game.isPubicHairEnabled()
							?CharacterModificationUtils.getKatesDivPubicHair("Pubic hair", "The body hair found in the genital area; located on and around [npc.namePos] sex organs and crotch.")
							:"")

					+(Main.game.isBodyHairEnabled()
							?CharacterModificationUtils.getKatesDivUnderarmHair("Underarm hair", "The body hair found in [npc.namePos] armpits.")
							:"")

					+(Main.game.isAssHairEnabled()
							?CharacterModificationUtils.getKatesDivAssHair("Ass hair", "The body hair found around [npc.namePos] asshole.")
							:"")
					);

			for(BodyCoveringType bct : BodyCoveringType.values()) {
				if((Main.game.isFacialHairEnabled() && BodyChanging.getTarget().getFacialHairType().getType()==bct)
						|| (Main.game.isBodyHairEnabled() && BodyChanging.getTarget().getUnderarmHairType().getType()==bct)
						|| (Main.game.isAssHairEnabled() && BodyChanging.getTarget().getAssHairType().getType()==bct)
						|| (Main.game.isPubicHairEnabled() && BodyChanging.getTarget().getPubicHairType().getType()==bct)) {
					UtilText.nodeContentSB.append(CharacterModificationUtils.getKatesDivCoveringsNew(
							true, bct, "Body hair", "Your body hair.", true, true));

				}
			}

			return UtilText.parse(BodyChanging.getTarget(), UtilText.nodeContentSB.toString());
		}

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

		@Override
		public Response getResponse(int responseTab, int index) {
			if (index == 6) {
				return new Response("Other",
						UtilText.parse(BodyChanging.getTarget(), "You are already changing [npc.namePos] other features!"),
						null);

			} else {
				return getCosmeticsResponse(responseTab, index);
			}
		}
	};

	public static final DialogueNode SLAVE_MANAGEMENT_TATTOOS = new DialogueNode("Succubi's Secrets", "-", true) {

		@Override
		public String getContent() {
			return CharacterModificationUtils.getKatesDivTattoos();
		}

		@Override
		public Response getResponse(int responseTab, int index) {
			if (index == 7) {
				return new Response("Tattoos",
						UtilText.parse(BodyChanging.getTarget(), "You are already managing [npc.namePos] tattoos!"),
						null);

			} else if(index==11) {
				return new Response("Confirmations: ",
						"Toggle tattoo removal confirmations."
							+ " When turned on, it will take two clicks to remove tattoos."
							+ " When turned off, it will only take one click.",
							SLAVE_MANAGEMENT_TATTOOS) {
					@Override
					public String getTitle() {
						return "Confirmations: "+(Main.getProperties().hasValue(PropertyValue.tattooRemovalConfirmations)
									?"ON"
									:"OFF");
					}

					@Override
					public void effects() {
						Main.getProperties().setValue(PropertyValue.tattooRemovalConfirmations, !Main.getProperties().hasValue(PropertyValue.tattooRemovalConfirmations));
						Main.getProperties().savePropertiesAsXML();
					}
				};

			} else {
				return getCosmeticsResponse(responseTab, index);
			}
		}

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

	public static final DialogueNode SLAVE_MANAGEMENT_TATTOOS_ADD = new DialogueNode("Succubi's Secrets", "-", true) {

		@Override
		public String getLabel() {
			return "Succubi's Secrets - "+Util.capitalizeSentence(CharacterModificationUtils.tattooInventorySlot.getName()) +" Tattoo";
		}

		@Override
		public String getContent() {
			return CharacterModificationUtils.getKatesDivTattoosAdd();
		}

		@Override
		public Response getResponse(int responseTab, int index) {
			int value = CharacterModificationUtils.tattoo.getValue();

			if (index == 1) {
				if(Main.game.getPlayer().getMoney()<value) {
					return new Response("Apply ("+UtilText.formatAsMoneyUncolored(value, "span")+")",
							UtilText.parse(BodyChanging.getTarget(), "You don't have enough money to give [npc.name] a tattoo!"),  null);

				} else if(CharacterModificationUtils.tattoo.getType().equals(TattooType.NONE)
						&& CharacterModificationUtils.tattoo.getWriting().getText().isEmpty()
						&& CharacterModificationUtils.tattoo.getCounter().getType()==TattooCounterType.NONE) {
					return new Response("Apply ("+UtilText.formatAsMoneyUncolored(value, "span")+")", "You need to select a tattoo type, add some writing, or add a counter in order to make a tattoo!", null);

				} else {
					return new Response("Apply ("+UtilText.formatAsMoney(value, "span")+")",
							UtilText.parse(BodyChanging.getTarget(), "Pay Kate to give [npc.name] this tattoo!"), SLAVE_MANAGEMENT_TATTOOS) {
						@Override
						public void effects() {
							Main.game.getTextStartStringBuilder().append(Main.game.getPlayer().incrementMoney(-value));

							Main.mainController.getWebEngine().executeScript("document.getElementById('hiddenPField').innerHTML=document.getElementById('tattoo_name').value;");
							CharacterModificationUtils.tattoo.getWriting().setText(Main.mainController.getWebEngine().getDocument().getElementById("hiddenPField").getTextContent());
							CharacterModificationUtils.tattoo.setName(CharacterModificationUtils.tattoo.getType().getName());
							BodyChanging.getTarget().addTattoo(CharacterModificationUtils.tattooInventorySlot, CharacterModificationUtils.tattoo);
						}
					};
				}

			} else if(index==0) {
				return new Response("Back", "Decide not to get this tattoo and return to the main selection screen.", SLAVE_MANAGEMENT_TATTOOS);
			}

			return null;
		}

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

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

		@Override
		public DialogueNodeType getDialogueNodeType() {
			return DialogueNodeType.OCCUPANT_MANAGEMENT;
		}

		@Override
		public String getLabel() {
			return UtilText.parse(characterSelected(), "[npc.NamePos] Perk Tree");
		}

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

			UtilText.nodeContentSB.append(UtilText.parse(characterSelected(),
					"
"
						+ "Perks (circular icons) apply permanent boosts to [npc.namePos] attributes.
"
						+ "Traits (square icons) provide unique effects for [npc.name]."
							+ " Unlike perks, traits will have no effect on [npc.name] until they're slotted into [npc.her] 'Active Traits' bar.
"
						+ "Perks require perk points to unlock. [npc.Name] earns one perk point each time [npc.she] levels up, and an extra two perk points every five levels."
					+ "
"
					+ "
"
					+ "
Active Traits
"));

			UtilText.nodeContentSB.append(
					"
"
						+ "
"+characterSelected().getHistory().getAssociatedPerk().getSVGString()+"
"
					+ "
");

			for(int i=0;i<GameCharacter.MAX_TRAITS;i++) {
				Perk p = null;
				if(i<characterSelected().getTraits().size()) {
					p = characterSelected().getTraits().get(i);
				}
				if(p!=null) {
					UtilText.nodeContentSB.append("
"
							+ "
"+p.getSVGString()+"
"
							+ "
");

				} else {
					UtilText.nodeContentSB.append("

");

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

			if(!(characterSelected() instanceof Elemental)) {
				UtilText.nodeContentSB.append("
"
							+ "Please note that this perk tree is a work-in-progress. This is not the final version, and is just a proof of concept!"
						+ "
");
			}

			UtilText.nodeContentSB.append(PerkManager.MANAGER.getPerkTreeDisplay(characterSelected()));

			return UtilText.nodeContentSB.toString();
		}

		@Override
		public Response getResponse(int responseTab, int index) {
			if (index == 6) {
				return new Response("Perks", UtilText.parse(characterSelected(), "You are already assigning [npc.namePos] perk points."), null);

			} else if(index==7) {
				return new Response("Reset perks", "Reset all of [npc.namePos] perks and traits, refunding all points spent. (This is a temporary action while the perk tree is still under development.)", SLAVE_MANAGEMENT_PERKS) {
					@Override
					public void effects() {
						characterSelected().resetPerksMap();
					}
				};
			}

			return SLAVE_LIST.getResponse(responseTab, index);
		}

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